From 6a40164dad6c5a5a849ccf2a7eba072e100f4b0e Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 23 May 2018 15:01:08 +0000 Subject: [PATCH 01/24] Payment Request modifications --- .../payment_request/payment_request.js | 11 +- .../payment_request/payment_request.json | 141 ++++++++++++++---- .../payment_request/test_payment_request.js | 23 +++ 3 files changed, 147 insertions(+), 28 deletions(-) create mode 100644 erpnext/accounts/doctype/payment_request/test_payment_request.js diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index afc3804ff7..dd63a4a1d9 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -13,6 +13,14 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){ } }) } + + frm.set_query("payment_plan", function() { + return { + "filters": { + "payment_gateway": ["=", frm.doc.payment_gateway_account] + } + }; + }); }) frappe.ui.form.on("Payment Request", "refresh", function(frm) { @@ -31,7 +39,7 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { }); }); } - + if(!frm.doc.payment_gateway_account && frm.doc.status == "Initiated") { frm.add_custom_button(__('Make Payment Entry'), function(){ frappe.call({ @@ -48,4 +56,3 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { }).addClass("btn-primary"); } }); - diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index e21afa4789..13a18f125c 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,11 +43,12 @@ "reqd": 1, "search_index": 0, "set_only_once": 1, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -73,11 +75,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -106,11 +109,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -137,11 +141,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -168,11 +173,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -198,11 +204,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -230,11 +237,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -262,11 +270,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -295,11 +304,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -328,11 +338,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -360,11 +371,78 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_a_subscription", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is a subscription", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.is_a_subscription", + "fieldname": "payment_plan", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Plan", + "length": 0, + "no_copy": 0, + "options": "Payment Plan", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -390,11 +468,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -421,11 +500,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -453,11 +533,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -484,11 +565,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -515,11 +597,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -547,16 +630,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "payment_gateway_account.payment_gateway", + "fetch_from": "payment_gateway_account.payment_gateway", "fieldname": "payment_gateway", "fieldtype": "Read Only", "hidden": 0, @@ -580,16 +664,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "payment_gateway_account.payment_account", + "fetch_from": "payment_gateway_account.payment_account", "fieldname": "payment_account", "fieldtype": "Read Only", "hidden": 0, @@ -613,11 +698,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -644,11 +730,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -676,11 +763,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -708,11 +796,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -739,7 +828,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 } ], @@ -753,7 +842,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-16 22:43:28.136835", + "modified": "2018-05-23 11:36:49.975929", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.js b/erpnext/accounts/doctype/payment_request/test_payment_request.js new file mode 100644 index 0000000000..070b595fc6 --- /dev/null +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Payment Request", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Payment Request + () => frappe.tests.make('Payment Request', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); From e25d2ee66d96d2ecc7c164891cdf64d822d4bc05 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 23 May 2018 15:42:31 +0000 Subject: [PATCH 02/24] Update Stripe Documentation --- .../img/setup/integrations/payment_plan.png | Bin 0 -> 27145 bytes .../subscription_payment_request.png | Bin 0 -> 20124 bytes .../docs/user/manual/en/setting-up/index.txt | 17 +++++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 erpnext/docs/assets/img/setup/integrations/payment_plan.png create mode 100644 erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png create mode 100644 erpnext/docs/user/manual/en/setting-up/index.txt diff --git a/erpnext/docs/assets/img/setup/integrations/payment_plan.png b/erpnext/docs/assets/img/setup/integrations/payment_plan.png new file mode 100644 index 0000000000000000000000000000000000000000..6187fc5c2b2fe4e8d6e5b65eaab3048f39661be2 GIT binary patch literal 27145 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 PCpcM2C5cio{m=gen%=Kn literal 0 HcmV?d00001 diff --git a/erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png b/erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png new file mode 100644 index 0000000000000000000000000000000000000000..528159efc06c7ca2970fd2eb58db3a5dae448023 GIT binary patch literal 20124 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 literal 0 HcmV?d00001 diff --git a/erpnext/docs/user/manual/en/setting-up/index.txt b/erpnext/docs/user/manual/en/setting-up/index.txt new file mode 100644 index 0000000000..2733e91ae2 --- /dev/null +++ b/erpnext/docs/user/manual/en/setting-up/index.txt @@ -0,0 +1,17 @@ +setup-wizard +users-and-permissions +settings +data +email +print +setting-up-taxes +pos-setting +price-lists +authorization-rule +sms-setting +stock-reconciliation-for-non-serialized-item +territory +third-party-backups +workflows +company-setup +articles From 30da37fbb801009f59dd9c0b34f8b3b3aac3e3e3 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 23 May 2018 16:40:41 +0000 Subject: [PATCH 03/24] Move Stripe to ERPNext --- ...rpnext_integrations.py => integrations.py} | 5 + .../doctype/payment_plan/__init__.py | 0 .../doctype/payment_plan/payment_plan.js | 8 + .../doctype/payment_plan/payment_plan.json | 195 +++++++++++ .../doctype/payment_plan/payment_plan.py | 10 + .../doctype/payment_plan/test_payment_plan.js | 23 ++ .../doctype/payment_plan/test_payment_plan.py | 10 + .../doctype/stripe_settings/__init__.py | 0 .../stripe_settings/stripe_settings.js | 8 + .../stripe_settings/stripe_settings.json | 315 ++++++++++++++++++ .../stripe_settings/stripe_settings.py | 163 +++++++++ .../stripe_settings/test_stripe_settings.js | 23 ++ .../stripe_settings/test_stripe_settings.py | 10 + .../includes/integrations/stripe_checkout.js | 85 +++++ .../pages/integrations/stripe_checkout.css | 113 +++++++ .../pages/integrations/stripe_checkout.html | 56 ++++ .../pages/integrations/stripe_checkout.py | 64 ++++ requirements.txt | 1 + 18 files changed, 1089 insertions(+) rename erpnext/config/{erpnext_integrations.py => integrations.py} (84%) create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js create mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py create mode 100644 erpnext/templates/includes/integrations/stripe_checkout.js create mode 100644 erpnext/templates/pages/integrations/stripe_checkout.css create mode 100644 erpnext/templates/pages/integrations/stripe_checkout.html create mode 100644 erpnext/templates/pages/integrations/stripe_checkout.py diff --git a/erpnext/config/erpnext_integrations.py b/erpnext/config/integrations.py similarity index 84% rename from erpnext/config/erpnext_integrations.py rename to erpnext/config/integrations.py index e27b7cd04f..14d917f704 100644 --- a/erpnext/config/erpnext_integrations.py +++ b/erpnext/config/integrations.py @@ -7,6 +7,11 @@ def get_data(): "label": _("Payments"), "icon": "fa fa-star", "items": [ + { + "type": "doctype", + "name": "Stripe Settings", + "description": _("Stripe payment gateway settings"), + }, { "type": "doctype", "name": "GoCardless Settings", diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/__init__.py b/erpnext/erpnext_integrations/doctype/payment_plan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js new file mode 100644 index 0000000000..50a41ecc2d --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Payment Plan', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json new file mode 100644 index 0000000000..7550cb4135 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json @@ -0,0 +1,195 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:payment_plan_id", + "beta": 0, + "creation": "2018-05-23 10:17:31.108746", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_plan", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_plan_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "ID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_gateway", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Gateway", + "length": 0, + "no_copy": 0, + "options": "Payment Gateway Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Monthly", + "description": "", + "fieldname": "recurrence", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Recurrence", + "length": 0, + "no_copy": 0, + "options": "Daily\nWeekly\nMonthly\nEvery 3 Months\nEvery 6 Months\nYearly", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-05-23 18:25:48.200621", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "Payment Plan", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "payment_plan", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py new file mode 100644 index 0000000000..d61adec880 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PaymentPlan(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js new file mode 100644 index 0000000000..330e71c2ab --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Payment Plan", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Payment Plan + () => frappe.tests.make('Payment Plan', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py new file mode 100644 index 0000000000..5b97868bdf --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestPaymentPlan(unittest.TestCase): + pass diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py b/erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js new file mode 100644 index 0000000000..e5cddd3bf3 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Stripe Settings', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json new file mode 100644 index 0000000000..4129e74d74 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json @@ -0,0 +1,315 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:gateway_name", + "beta": 0, + "creation": "2017-03-09 17:18:29.458397", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gateway_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Gateway Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "publishable_key", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Publishable Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "secret_key", + "fieldtype": "Password", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Secret Key", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "header_img", + "fieldtype": "Attach Image", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Header Image", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "redirect_url", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Redirect URL", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-05-23 18:15:55.584782", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "Stripe Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py new file mode 100644 index 0000000000..227f13897c --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from six.moves.urllib.parse import urlencode +from frappe.utils import get_url, call_hook_method, cint, flt +from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +import stripe + +class StripeSettings(Document): + supported_currencies = [ + "AED", "ALL", "ANG", "ARS", "AUD", "AWG", "BBD", "BDT", "BIF", "BMD", "BND", + "BOB", "BRL", "BSD", "BWP", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF", + "DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GIP", "GMD", "GNF", "GTQ", "GYD", + "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JPY", "KES", "KHR", "KMF", + "KRW", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "MAD", "MDL", "MNT", "MOP", "MRO", "MUR", "MVR", + "MWK", "MXN", "MYR", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP", "PKR", + "PLN", "PYG", "QAR", "RUB", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLL", "SOS", "STD", "SVC", + "SZL", "THB", "TOP", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST", + "XAF", "XOF", "XPF", "YER", "ZAR" + ] + + currency_wise_minimum_charge_amount = { + 'JPY': 50, 'MXN': 10, 'DKK': 2.50, 'HKD': 4.00, 'NOK': 3.00, 'SEK': 3.00, + 'USD': 0.50, 'AUD': 0.50, 'BRL': 0.50, 'CAD': 0.50, 'CHF': 0.50, 'EUR': 0.50, + 'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50 + } + + def on_update(self): + create_payment_gateway('Stripe-' + self.gateway_name, settings='Stripe Settings', controller=self.gateway_name) + call_hook_method('payment_gateway_enabled', gateway='Stripe-' + self.gateway_name) + if not self.flags.ignore_mandatory: + self.validate_stripe_credentails() + + def validate_stripe_credentails(self): + if self.publishable_key and self.secret_key: + header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} + try: + make_get_request(url="https://api.stripe.com/v1/charges", headers=header) + except Exception: + frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) + + def validate_transaction_currency(self, currency): + if currency not in self.supported_currencies: + frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + + def validate_minimum_transaction_amount(self, currency, amount): + if currency in self.currency_wise_minimum_charge_amount: + if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0): + frappe.throw(_("For currency {0}, the minimum transaction amount should be {1}").format(currency, + self.currency_wise_minimum_charge_amount.get(currency, 0.0))) + + def get_payment_url(self, **kwargs): + return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs))) + + def create_request(self, data): + self.data = frappe._dict(data) + stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False) + stripe.default_http_client = stripe.http_client.RequestsClient() + + try: + self.integration_request = create_request_log(self.data, "Host", "Stripe") + if frappe.db.get_value("Payment Request", self.data.reference_docname, 'is_a_subscription'): + self.payment_plan = frappe.db.get_value("Payment Request", self.data.reference_docname, 'payment_plan') + return self.create_subscription_on_stripe() + else: + return self.create_charge_on_stripe() + + except Exception: + frappe.log_error(frappe.get_traceback()) + return{ + "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "status": 401 + } + + def create_charge_on_stripe(self): + try: + charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description) + + if charge.captured == True: + self.integration_request.db_set('status', 'Completed', update_modified=False) + self.flags.status_changed_to = "Completed" + + else: + frappe.log_error(str(resp), 'Stripe Payment not completed') + + except: + frappe.log_error(frappe.get_traceback()) + # failed + pass + + return self.finalize_request() + + def create_subscription_on_stripe(self): + items = [ + { + "plan": self.payment_plan + } + ] + + try: + customer = stripe.Customer.create(description=self.data.payer_name, email=self.data.payer_email, source=self.data.stripe_token_id) + subscription = stripe.Subscription.create(customer=customer, items=items) + + if subscription.status == "active": + self.integration_request.db_set('status', 'Completed', update_modified=False) + self.flags.status_changed_to = "Completed" + + else: + self.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error(str(resp), 'Stripe Payment not completed') + + except: + self.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error(frappe.get_traceback()) + # failed + pass + + return self.finalize_request() + + def finalize_request(self): + redirect_to = self.data.get('redirect_to') or None + redirect_message = self.data.get('redirect_message') or None + status = self.integration_request.status + + if self.flags.status_changed_to == "Completed": + if self.data.reference_doctype and self.data.reference_docname: + custom_redirect_to = None + try: + custom_redirect_to = frappe.get_doc(self.data.reference_doctype, + self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) + except Exception: + frappe.log_error(frappe.get_traceback()) + + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = 'payment-success' + + if self.redirect_url: + redirect_url = self.redirect_url + redirect_to = None + else: + redirect_url = 'payment-failed' + + if redirect_to: + redirect_url += '?' + urlencode({'redirect_to': redirect_to}) + if redirect_message: + redirect_url += '&' + urlencode({'redirect_message': redirect_message}) + + return { + "redirect_to": redirect_url, + "status": status + } + +def get_gateway_controller(doc): + payment_request = frappe.get_doc("Payment Request", doc) + gateway_controller = frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller") + return gateway_controller diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js new file mode 100644 index 0000000000..b491ba5737 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Stripe Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Stripe Settings + () => frappe.tests.make('Stripe Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py new file mode 100644 index 0000000000..3403a6279e --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestStripeSettings(unittest.TestCase): + pass diff --git a/erpnext/templates/includes/integrations/stripe_checkout.js b/erpnext/templates/includes/integrations/stripe_checkout.js new file mode 100644 index 0000000000..1ce3b12b7a --- /dev/null +++ b/erpnext/templates/includes/integrations/stripe_checkout.js @@ -0,0 +1,85 @@ +var stripe = Stripe("{{ publishable_key }}"); + +var elements = stripe.elements(); + +var style = { + base: { + color: '#32325d', + lineHeight: '18px', + fontFamily: '"Helvetica Neue", Helvetica, sans-serif', + fontSmoothing: 'antialiased', + fontSize: '16px', + '::placeholder': { + color: '#aab7c4' + } + }, + invalid: { + color: '#fa755a', + iconColor: '#fa755a' + } +}; + +var card = elements.create('card', { + hidePostalCode: true, + style: style +}); + +card.mount('#card-element'); + +function setOutcome(result) { + + if (result.token) { + $('#submit').prop('disabled', true) + $('#submit').html(__('Processing...')) + frappe.call({ + method:"erpnext.templates.pages.integrations.stripe_checkout.make_payment", + freeze:true, + headers: {"X-Requested-With": "XMLHttpRequest"}, + args: { + "stripe_token_id": result.token.id, + "data": JSON.stringify({{ frappe.form_dict|json }}), + "reference_doctype": "{{ reference_doctype }}", + "reference_docname": "{{ reference_docname }}" + }, + callback: function(r) { + if (r.message.status == "Completed") { + $('#submit').hide() + $('.success').show() + setTimeout(function() { + window.location.href = r.message.redirect_to + }, 2000); + } else { + $('#submit').hide() + $('.error').show() + setTimeout(function() { + window.location.href = r.message.redirect_to + }, 2000); + } + } + }); + + } else if (result.error) { + $('.error').html() = result.error.message; + $('.error').show() + } +} + +card.on('change', function(event) { + var displayError = document.getElementById('card-errors'); + if (event.error) { + displayError.textContent = event.error.message; + } else { + displayError.textContent = ''; + } +}); + +frappe.ready(function() { + $('#submit').off("click").on("click", function(e) { + e.preventDefault(); + var extraDetails = { + name: $('input[name=cardholder-name]').val(), + email: $('input[name=cardholder-email]').val() + } + stripe.createToken(card, extraDetails).then(setOutcome); + }) +}); diff --git a/erpnext/templates/pages/integrations/stripe_checkout.css b/erpnext/templates/pages/integrations/stripe_checkout.css new file mode 100644 index 0000000000..04efc79530 --- /dev/null +++ b/erpnext/templates/pages/integrations/stripe_checkout.css @@ -0,0 +1,113 @@ +.StripeElement { + background-color: white; + height: 40px; + padding: 10px 12px; + border-radius: 4px; + border: 1px solid transparent; + box-shadow: 0 1px 3px 0 #e6ebf1; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +.StripeElement--focus { + box-shadow: 0 1px 3px 0 #cfd7df; +} + +.StripeElement--invalid { + border-color: #fa755a; +} + +.StripeElement--webkit-autofill { + background-color: #fefde5 !important; +} + +.stripe #payment-form { + margin-top: 80px; +} + +.stripe button { + float: right; + display: block; + background: #5e64ff; + color: white; + box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); + border-radius: 4px; + border: 0; + margin-top: 20px; + font-size: 15px; + font-weight: 400; + max-width: 40%; + height: 40px; + line-height: 38px; + outline: none; +} + +.stripe button:hover, .stripe button:focus { + background: #2b33ff; + border-color: #0711ff; +} + +.stripe button:active { + background: #5e64ff; +} + +.stripe button:disabled { + background: #515e80; +} + +.stripe .group { + background: white; + box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); + border-radius: 4px; + margin-bottom: 20px; +} + +.stripe label { + position: relative; + color: #8898AA; + font-weight: 300; + height: 40px; + line-height: 40px; + margin-left: 20px; + display: block; +} + +.stripe .group label:not(:last-child) { + border-bottom: 1px solid #F0F5FA; +} + +.stripe label>span { + width: 20%; + text-align: right; + float: left; +} + +.current-card { + margin-left: 20px; +} + +.field { + background: transparent; + font-weight: 300; + border: 0; + color: #31325F; + outline: none; + padding-right: 10px; + padding-left: 10px; + cursor: text; + width: 70%; + height: 40px; + float: right; +} + +.field::-webkit-input-placeholder { + color: #CFD7E0; +} + +.field::-moz-placeholder { + color: #CFD7E0; +} + +.field:-ms-input-placeholder { + color: #CFD7E0; +} diff --git a/erpnext/templates/pages/integrations/stripe_checkout.html b/erpnext/templates/pages/integrations/stripe_checkout.html new file mode 100644 index 0000000000..b52428667b --- /dev/null +++ b/erpnext/templates/pages/integrations/stripe_checkout.html @@ -0,0 +1,56 @@ +{% extends "templates/web.html" %} + +{% block title %} Payment {% endblock %} + +{%- block header -%} +{% endblock %} + +{% block script %} + + +{% endblock %} + +{%- block page_content -%} + +
+
+ +

{{description}}

+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+
+
+ + +{% endblock %} diff --git a/erpnext/templates/pages/integrations/stripe_checkout.py b/erpnext/templates/pages/integrations/stripe_checkout.py new file mode 100644 index 0000000000..e9c1ff3fab --- /dev/null +++ b/erpnext/templates/pages/integrations/stripe_checkout.py @@ -0,0 +1,64 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, cint, fmt_money +import json +from erpnext.erpnext_integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller + +no_cache = 1 +no_sitemap = 1 + +expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', + 'payer_name', 'payer_email', 'order_id', 'currency') + +def get_context(context): + context.no_cache = 1 + + # all these keys exist in form_dict + if not (set(expected_keys) - set(list(frappe.form_dict))): + for key in expected_keys: + context[key] = frappe.form_dict[key] + + gateway_controller = get_gateway_controller(context.reference_docname) + context.publishable_key = get_api_key(context.reference_docname, gateway_controller) + context.image = get_header_image(context.reference_docname, gateway_controller) + + context['amount'] = fmt_money(amount=context['amount'], currency=context['currency']) + + if frappe.db.get_value(context.reference_doctype, context.reference_docname, "is_a_subscription"): + payment_plan = frappe.db.get_value(context.reference_doctype, context.reference_docname, "payment_plan") + recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence") + + context['amount'] = context['amount'] + " " + _(recurrence) + + else: + frappe.redirect_to_message(_('Some information is missing'), + _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + +def get_api_key(doc, gateway_controller): + publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key") + if cint(frappe.form_dict.get("use_sandbox")): + publishable_key = frappe.conf.sandbox_publishable_key + + return publishable_key + +def get_header_image(doc, gateway_controller): + header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img") + return header_image + +@frappe.whitelist(allow_guest=True) +def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None): + data = json.loads(data) + + data.update({ + "stripe_token_id": stripe_token_id + }) + + gateway_controller = get_gateway_controller(reference_docname) + data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data) + frappe.db.commit() + return data diff --git a/requirements.txt b/requirements.txt index 13055ac2ec..2da865c277 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ python-stdnum braintree gocardless_pro woocommerce +stripe From 79380c6a74dfe745d66b3f8509115c5d62f64ef2 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 23 May 2018 16:45:36 +0000 Subject: [PATCH 04/24] Travis correction --- .../doctype/stripe_settings/stripe_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py index 227f13897c..7015a5137e 100644 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py @@ -86,7 +86,7 @@ class StripeSettings(Document): self.flags.status_changed_to = "Completed" else: - frappe.log_error(str(resp), 'Stripe Payment not completed') + frappe.log_error(charge.failure_message, 'Stripe Payment not completed') except: frappe.log_error(frappe.get_traceback()) From a27673ecfe4c29d94852c99c2659151a48bcec88 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 23 May 2018 16:47:59 +0000 Subject: [PATCH 05/24] Travis correction --- .../doctype/stripe_settings/stripe_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py index 7015a5137e..de48d34abb 100644 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py @@ -112,7 +112,7 @@ class StripeSettings(Document): else: self.integration_request.db_set('status', 'Failed', update_modified=False) - frappe.log_error(str(resp), 'Stripe Payment not completed') + frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') except: self.integration_request.db_set('status', 'Failed', update_modified=False) From 6c2ad66522a6649d3aa70e60f853b4ca54c4e87d Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 24 May 2018 07:23:18 +0000 Subject: [PATCH 06/24] Codacy corrections --- .../doctype/payment_plan/payment_plan.js | 4 +--- .../doctype/payment_plan/payment_plan.py | 1 - .../doctype/payment_plan/test_payment_plan.py | 1 - .../doctype/stripe_settings/stripe_settings.js | 2 -- .../doctype/stripe_settings/stripe_settings.py | 10 +++------- .../doctype/stripe_settings/test_stripe_settings.py | 1 - .../templates/pages/integrations/stripe_checkout.css | 2 +- .../templates/pages/integrations/stripe_checkout.py | 2 +- 8 files changed, 6 insertions(+), 17 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js index 50a41ecc2d..e8bc875fb0 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js @@ -2,7 +2,5 @@ // For license information, please see license.txt frappe.ui.form.on('Payment Plan', { - refresh: function(frm) { - - } + }); diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py index d61adec880..ec2954ee08 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class PaymentPlan(Document): diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py index 5b97868bdf..b5935e4b5c 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py +++ b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestPaymentPlan(unittest.TestCase): diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js index e5cddd3bf3..fbc8e7fbfb 100644 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js @@ -2,7 +2,5 @@ // For license information, please see license.txt frappe.ui.form.on('Stripe Settings', { - refresh: function(frm) { - } }); diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py index de48d34abb..cc71a58dd8 100644 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py @@ -8,7 +8,7 @@ from frappe.model.document import Document from frappe import _ from six.moves.urllib.parse import urlencode from frappe.utils import get_url, call_hook_method, cint, flt -from frappe.integrations.utils import make_get_request, make_post_request, create_request_log, create_payment_gateway +from frappe.integrations.utils import make_get_request, create_request_log, create_payment_gateway import stripe class StripeSettings(Document): @@ -88,10 +88,8 @@ class StripeSettings(Document): else: frappe.log_error(charge.failure_message, 'Stripe Payment not completed') - except: + except Exception: frappe.log_error(frappe.get_traceback()) - # failed - pass return self.finalize_request() @@ -114,11 +112,9 @@ class StripeSettings(Document): self.integration_request.db_set('status', 'Failed', update_modified=False) frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') - except: + except Exception: self.integration_request.db_set('status', 'Failed', update_modified=False) frappe.log_error(frappe.get_traceback()) - # failed - pass return self.finalize_request() diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py index 3403a6279e..0c5aa83c65 100644 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py +++ b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py @@ -3,7 +3,6 @@ # See license.txt from __future__ import unicode_literals -import frappe import unittest class TestStripeSettings(unittest.TestCase): diff --git a/erpnext/templates/pages/integrations/stripe_checkout.css b/erpnext/templates/pages/integrations/stripe_checkout.css index 04efc79530..a42808aa7f 100644 --- a/erpnext/templates/pages/integrations/stripe_checkout.css +++ b/erpnext/templates/pages/integrations/stripe_checkout.css @@ -18,7 +18,7 @@ } .StripeElement--webkit-autofill { - background-color: #fefde5 !important; + background-color: #fefde5; } .stripe #payment-form { diff --git a/erpnext/templates/pages/integrations/stripe_checkout.py b/erpnext/templates/pages/integrations/stripe_checkout.py index e9c1ff3fab..f1f86e90a0 100644 --- a/erpnext/templates/pages/integrations/stripe_checkout.py +++ b/erpnext/templates/pages/integrations/stripe_checkout.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint, fmt_money +from frappe.utils import cint, fmt_money import json from erpnext.erpnext_integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller From a45ea63b5d5b99247aeccf79f03b84a7b4f1f38c Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 24 May 2018 13:14:07 +0000 Subject: [PATCH 07/24] Codacy correction --- .../erpnext_integrations/doctype/payment_plan/payment_plan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js index e8bc875fb0..d37bed9283 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js @@ -2,5 +2,5 @@ // For license information, please see license.txt frappe.ui.form.on('Payment Plan', { - + }); From ba6f6d976d88b8bfd5ebc02e8dc33cb108b34fd5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 5 Jun 2018 19:49:57 +0000 Subject: [PATCH 08/24] Stripe integration corrections --- .../payment_request/payment_request.js | 5 ++ erpnext/config/integrations.py | 5 -- .../doctype/payment_plan/payment_plan.py | 50 +++++++++++++++++++ .../pages/integrations/stripe_checkout.py | 8 ++- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index dd63a4a1d9..379dc86123 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -56,3 +56,8 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { }).addClass("btn-primary"); } }); + +frappe.ui.form.on("Payment Request", "is_a_subscription", function(frm) { + frm.toggle_reqd("payment_gateway_account", frm.doc.is_a_subscription); + frm.toggle_reqd("payment_plan", frm.doc.is_a_subscription); +}); diff --git a/erpnext/config/integrations.py b/erpnext/config/integrations.py index 14d917f704..e27b7cd04f 100644 --- a/erpnext/config/integrations.py +++ b/erpnext/config/integrations.py @@ -7,11 +7,6 @@ def get_data(): "label": _("Payments"), "icon": "fa fa-star", "items": [ - { - "type": "doctype", - "name": "Stripe Settings", - "description": _("Stripe payment gateway settings"), - }, { "type": "doctype", "name": "GoCardless Settings", diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py index ec2954ee08..0e71fbca5f 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py +++ b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py @@ -4,6 +4,56 @@ from __future__ import unicode_literals from frappe.model.document import Document +import frappe +from frappe import _ +from frappe.integrations.utils import create_request_log +import stripe class PaymentPlan(Document): pass + + +def create_stripe_subscription(gateway_controller, data): + stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) + stripe_settings.data = frappe._dict(data) + + stripe.api_key = stripe_settings.get_password(fieldname="secret_key", raise_exception=False) + stripe.default_http_client = stripe.http_client.RequestsClient() + + try: + stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") + stripe_settings.payment_plan = frappe.db.get_value("Payment Request", stripe_settings.data.reference_docname, 'payment_plan') + return create_subscription_on_stripe(stripe_settings) + + except Exception: + frappe.log_error(frappe.get_traceback()) + return{ + "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "status": 401 + } + + +def create_subscription_on_stripe(stripe_settings): + items = [ + { + "plan": stripe_settings.payment_plan + } + ] + + try: + customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id) + subscription = stripe.Subscription.create(customer=customer, items=items) + + if subscription.status == "active": + stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False) + stripe_settings.flags.status_changed_to = "Completed" + + else: + stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') + + except Exception: + stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error(frappe.get_traceback()) + + return stripe_settings.finalize_request() diff --git a/erpnext/templates/pages/integrations/stripe_checkout.py b/erpnext/templates/pages/integrations/stripe_checkout.py index f1f86e90a0..6faf8f1cc9 100644 --- a/erpnext/templates/pages/integrations/stripe_checkout.py +++ b/erpnext/templates/pages/integrations/stripe_checkout.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.utils import cint, fmt_money import json from erpnext.erpnext_integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller +from erpnext.erpnext_integrations.doctype.payment_plan.payment_plan import create_stripe_subscription no_cache = 1 no_sitemap = 1 @@ -59,6 +60,11 @@ def make_payment(stripe_token_id, data, reference_doctype=None, reference_docnam }) gateway_controller = get_gateway_controller(reference_docname) - data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data) + + if frappe.db.get_value("Payment Request", reference_docname, 'is_a_subscription'): + data = create_stripe_subscription(gateway_controller, data) + else: + data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data) + frappe.db.commit() return data From f7ca908ea90ab943827c04cf25ccf5cd5a4ad9b5 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 14:53:47 +0000 Subject: [PATCH 09/24] Corrections to Subscriptions --- .../doctype/subscription/subscription.json | 1848 ++++++++--------- .../doctype/subscription/subscription.py | 56 +- .../subscription_plan/subscription_plan.js | 7 + .../subscription_plan/subscription_plan.json | 303 ++- .../subscription_plan_detail.json | 35 +- 5 files changed, 1265 insertions(+), 984 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 5d64d9a1a0..70fa2c7bb1 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -1,925 +1,925 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "SUBC.####", - "beta": 0, - "creation": "2017-07-18 17:50:43.967266", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscriber", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Subscriber", - "length": 0, - "no_copy": 0, - "options": "Subscriber", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "quantity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscription_period", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subscription Period", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subscription Start Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cancelation_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cancelation Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "trial_period_start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Trial Period Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.trial_period_start", - "fieldname": "trial_period_end", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Trial Period End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_invoice_start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Current Invoice Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_invoice_end", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Current Invoice End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "description": "Number of days that the subscriber has to pay invoices generated by this subscription", - "fieldname": "days_until_due", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Days Until Due", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cancel_at_period_end", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cancel At End Of Period", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "sb_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Plans", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "plans", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Plans", - "length": 0, - "no_copy": 0, - "options": "Subscription Plan Detail", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "sb_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discounts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "apply_additional_discount", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Additional Discount On", - "length": 0, - "no_copy": 0, - "options": "\nGrand Total\nNet total", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "cb_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "additional_discount_percentage", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional DIscount Percentage", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "", - "fieldname": "additional_discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional DIscount Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.invoices", - "fieldname": "sb_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Invoices", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "", - "fieldname": "invoices", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Invoices", - "length": 0, - "no_copy": 0, - "options": "Subscription Invoice", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-11 19:34:44.582203", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Subscription", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "SUBC.####", + "beta": 0, + "creation": "2017-07-18 17:50:43.967266", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscriber", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Subscriber", + "length": 0, + "no_copy": 0, + "options": "Subscriber", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "quantity", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Quantity", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_period", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Period", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Start Date", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cancelation_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Cancelation Date", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "trial_period_start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Trial Period Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.trial_period_start", + "fieldname": "trial_period_end", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Trial Period End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_invoice_start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Invoice Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_invoice_end", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Invoice End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "Number of days that the subscriber has to pay invoices generated by this subscription", + "fieldname": "days_until_due", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Days Until Due", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cancel_at_period_end", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Cancel At End Of Period", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "sb_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Plans", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "plans", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Plans", + "length": 0, + "no_copy": 0, + "options": "Subscription Plan Detail", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sb_1", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Taxes", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tax_template", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sales Taxes and Charges Template", + "length": 0, + "no_copy": 0, + "options": "Sales Taxes and Charges Template", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "", + "fieldname": "sb_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Discounts", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "apply_additional_discount", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Apply Additional Discount On", + "length": 0, + "no_copy": 0, + "options": "\nGrand Total\nNet total", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "cb_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "additional_discount_percentage", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Additional DIscount Percentage", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "", + "fieldname": "additional_discount_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Additional DIscount Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:doc.invoices", + "fieldname": "sb_3", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "", + "fieldname": "invoices", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices", + "length": 0, + "no_copy": 0, + "options": "Subscription Invoice", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-07-11 19:34:44.582203", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Subscription", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "", + "track_changes": 1, + "track_seen": 0 + } \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 72f86f2f60..6b3c7d2c8e 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -8,14 +8,15 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt +from erpnext.utilities.product import get_price class Subscription(Document): def before_insert(self): # update start just before the subscription doc is created - self.update_subscription_period(self.start) + self.update_subscription_period(self.start, init=True) - def update_subscription_period(self, date=None): + def update_subscription_period(self, date=None, init=False): """ Subscription period is the period to be billed. This method updates the beginning of the billing period and end of the billing period. @@ -25,7 +26,7 @@ class Subscription(Document): as `current_invoice_end`. """ self.set_current_invoice_start(date) - self.set_current_invoice_end() + self.set_current_invoice_end(init=False) def set_current_invoice_start(self, date=None): """ @@ -40,7 +41,7 @@ class Subscription(Document): else: self.current_invoice_start = nowdate() - def set_current_invoice_end(self): + def set_current_invoice_end(self, init=False): """ This sets the date of the end of the current billing period. @@ -51,7 +52,9 @@ class Subscription(Document): current billing period where `x` is the billing interval from the `Subscription Plan` in the `Subscription`. """ - if self.is_trialling(): + if init==True: + return + elif self.is_trialling(): self.current_invoice_end = self.trial_period_end else: billing_cycle_info = self.get_billing_cycle_data() @@ -272,6 +275,10 @@ class Subscription(Document): discount_on = self.apply_additional_discount invoice.apply_additional_discount = discount_on if discount_on else 'Grand Total' + # Subscription period + invoice.from_date = self.current_invoice_start + invoice.to_date = self.current_invoice_end + invoice.flags.ignore_mandatory = True invoice.save() invoice.submit() @@ -283,28 +290,33 @@ class Subscription(Document): """ Returns the `Customer` linked to the `Subscriber` """ - return frappe.get_value('Subscriber', subscriber_name, 'customer') + return frappe.db.get_value('Subscriber', subscriber_name, 'customer') def get_items_from_plans(self, plans, prorate=0): """ Returns the `Item`s linked to `Subscription Plan` """ - plan_items = [plan.plan for plan in plans] - item_details = None + if prorate: + prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) - if plan_items: - item_details = frappe.db.sql( - 'select item as item_code, cost as rate from `tabSubscription Plan` where name in %s', - (plan_items,), as_dict=1 - ) + items = [] + for plan in plans: + subscription_plan = frappe.get_doc("Subscription Plan", plan.plan) + if subscription_plan.price_determination == "Fixed rate": + if not prorate: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': subscription_plan.cost}) + else: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (subscription_plan.cost * prorate_factor)}) + elif subscription_plan.price_determination == "Based on price list": + customer = self.get_customer(self.subscriber) + customer_group = frappe.db.get_value("Customer", customer, "customer_group") + rate = get_price(item_code=subscription_plan.item, price_list=subscription_plan.price_list, customer_group=customer_group, company=None, qty=plan.qty) + if not prorate: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': rate}) + else: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (rate * prorate_factor)}) - if prorate: - prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) - - for item in item_details: - item['rate'] = item['rate'] * prorate_factor - - return item_details + return items def process(self): """ @@ -329,7 +341,7 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if getdate(nowdate()) > getdate(self.current_invoice_end) and not self.has_outstanding_invoice(): + if getdate(nowdate()) > getdate(self.current_invoice_end) or (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and not self.has_outstanding_invoice(): self.generate_invoice() if self.current_invoice_is_past_due(): self.status = 'Past Due Date' @@ -363,7 +375,7 @@ class Subscription(Document): else: if self.is_not_outstanding(current_invoice): self.status = 'Active' - self.update_subscription_period(nowdate()) + self.update_subscription_period(add_days(self.current_invoice_end, 1)) else: self.set_status_grace_period() diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js index f5ea8047c6..5a45c2caa2 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js @@ -1,2 +1,9 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt + +frappe.ui.form.on('Subscription Plan', { + price_determination: function(frm) { + frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate'); + frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list'); + } +}) \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index ab58e7c3c6..0865998cba 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -15,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,42 +43,11 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "unique": 1 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -110,10 +80,172 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item", + "length": 0, + "no_copy": 0, + "options": "Item", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "price_determination", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Price Determination", + "length": 0, + "no_copy": 0, + "options": "\nFixed rate\nBased on price list", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.price_determination==\"Fixed rate\"", "fieldname": "cost", "fieldtype": "Currency", "hidden": 0, @@ -133,7 +265,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -141,6 +273,72 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.price_determination==\"Based on price list\"", + "fieldname": "price_list", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Price List", + "length": 0, + "no_copy": 0, + "options": "Price List", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -174,6 +372,38 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -216,7 +446,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-02-27 09:12:58.330140", + "modified": "2018-06-20 15:43:10.152762", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", @@ -225,7 +455,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -244,7 +473,7 @@ "write": 1 } ], - "quick_entry": 1, + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "show_name_in_global_search": 0, diff --git a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json index c112923397..ca54a167f5 100644 --- a/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json +++ b/erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json @@ -14,6 +14,39 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "qty", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Quantity", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -55,7 +88,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-25 07:35:07.736146", + "modified": "2018-06-20 15:35:13.514699", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan Detail", From 0134e13631342a7dc7aaa1bdd50ad2316e8a9985 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 17:38:13 +0000 Subject: [PATCH 10/24] Subscription integration --- .../payment_request/payment_request.js | 15 +- .../payment_request/payment_request.json | 51 ++- .../payment_request/payment_request.py | 35 +- .../doctype/subscription/subscription.py | 19 +- .../subscription_plan/subscription_plan.json | 130 +++++++- .../subscription_plan/subscription_plan.py | 13 + .../doctype/payment_plan/__init__.py | 0 .../doctype/payment_plan/payment_plan.js | 6 - .../doctype/payment_plan/payment_plan.json | 195 ----------- .../doctype/payment_plan/test_payment_plan.js | 23 -- .../doctype/payment_plan/test_payment_plan.py | 9 - .../doctype/stripe_settings/__init__.py | 0 .../stripe_settings/stripe_settings.js | 6 - .../stripe_settings/stripe_settings.json | 315 ------------------ .../stripe_settings/stripe_settings.py | 159 --------- .../stripe_settings/test_stripe_settings.js | 23 -- .../stripe_settings/test_stripe_settings.py | 9 - .../payment_plan.py => stripe_integration.py} | 20 +- .../includes/integrations/stripe_checkout.js | 85 ----- .../templates/pages/integrations/__init__.py | 0 .../integrations/gocardless_checkout.html | 16 - .../pages/integrations/gocardless_checkout.py | 76 ----- .../integrations/gocardless_confirmation.html | 16 - .../integrations/gocardless_confirmation.py | 85 ----- .../pages/integrations/stripe_checkout.css | 113 ------- .../pages/integrations/stripe_checkout.html | 56 ---- .../pages/integrations/stripe_checkout.py | 70 ---- requirements.txt | 1 - 28 files changed, 246 insertions(+), 1300 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js delete mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json delete mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js delete mode 100644 erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py rename erpnext/erpnext_integrations/{doctype/payment_plan/payment_plan.py => stripe_integration.py} (82%) delete mode 100644 erpnext/templates/includes/integrations/stripe_checkout.js delete mode 100644 erpnext/templates/pages/integrations/__init__.py delete mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.html delete mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.py delete mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.html delete mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.py delete mode 100644 erpnext/templates/pages/integrations/stripe_checkout.css delete mode 100644 erpnext/templates/pages/integrations/stripe_checkout.html delete mode 100644 erpnext/templates/pages/integrations/stripe_checkout.py diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index 379dc86123..a80042937b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -59,5 +59,18 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { frappe.ui.form.on("Payment Request", "is_a_subscription", function(frm) { frm.toggle_reqd("payment_gateway_account", frm.doc.is_a_subscription); - frm.toggle_reqd("payment_plan", frm.doc.is_a_subscription); + frm.toggle_reqd("subscription_plans", frm.doc.is_a_subscription); + + if (frm.doc.is_a_subscription) { + frappe.call({ + method: "get_subscription_details", + doc: frm.doc, + freeze: true, + callback: function(r){ + if(!r.exc) { + frm.refresh_field("subscription_plans"); + } + } + }); + } }); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 13a18f125c..4148dce3b4 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -307,6 +307,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_a_subscription", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is a Subscription", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -380,9 +412,11 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "collapsible_depends_on": "", "columns": 0, - "fieldname": "is_a_subscription", - "fieldtype": "Check", + "depends_on": "eval:doc.is_a_subscription", + "fieldname": "subscription_section", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -390,7 +424,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Is a subscription", + "label": "Subscription Section", "length": 0, "no_copy": 0, "permlevel": 0, @@ -413,9 +447,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.is_a_subscription", - "fieldname": "payment_plan", - "fieldtype": "Link", + "fieldname": "subscription_plans", + "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -423,10 +456,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Payment Plan", + "label": "Subscription Plans", "length": 0, "no_copy": 0, - "options": "Payment Plan", + "options": "Subscription Plan Detail", "permlevel": 0, "precision": "", "print_hide": 0, @@ -842,7 +875,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-23 11:36:49.975929", + "modified": "2018-06-20 17:06:43.850174", "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 a633cc31a6..8af0bcc728 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -12,8 +12,9 @@ from erpnext.accounts.utils import get_account_currency from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, get_company_defaults from frappe.integrations.utils import get_payment_gateway_controller from frappe.utils.background_jobs import enqueue +from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription -class PaymentRequest(Document): +class PaymentRequest(Document): def validate(self): self.validate_reference_document() self.validate_payment_request() @@ -33,6 +34,25 @@ class PaymentRequest(Document): if self.payment_account and ref_doc.currency != frappe.db.get_value("Account", self.payment_account, "account_currency"): frappe.throw(_("Transaction currency must be same as Payment Gateway currency")) + def on_update(self): + self.validate_subscription_details() + + def validate_subscription_details(self): + if self.is_a_subscription: + amount = 0 + for subscription_plan in self.subscription_plans: + plan = frappe.get_doc("Subscription Plan", subscription_plan.plan) + if plan.payment_gateway != self.payment_gateway_account: + frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(plan.name))) + + rate = plan.get_plan_rate() + frappe.log_error(rate) + + amount += rate + + if amount != self.grand_total: + frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.".format(self.grand_total, amount))) + def on_submit(self): send_mail = self.payment_gateway_validation() ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) @@ -235,6 +255,19 @@ class PaymentRequest(Document): return redirect_to + def create_subscription(self, payment_provider, gateway_controller, data): + if payment_provider == "stripe": + return create_stripe_subscription(gateway_controller, data) + + def get_subscription_details(self): + if self.reference_doctype == "Sales Invoice": + subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice='{0}'""".format(self.reference_name), as_dict=1) + self.subscription_plans = [] + for subscription in subscriptions: + plans = frappe.get_doc("Subscription", subscription.sub_name).plans + for plan in plans: + self.append('subscription_plans', plan) + @frappe.whitelist(allow_guest=True) def make_payment_request(**args): """Make payment request""" diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 6b3c7d2c8e..00dc61876d 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -8,7 +8,6 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt -from erpnext.utilities.product import get_price class Subscription(Document): @@ -300,21 +299,13 @@ class Subscription(Document): prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) items = [] + customer = self.get_customer(self.subscriber) for plan in plans: subscription_plan = frappe.get_doc("Subscription Plan", plan.plan) - if subscription_plan.price_determination == "Fixed rate": - if not prorate: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': subscription_plan.cost}) - else: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (subscription_plan.cost * prorate_factor)}) - elif subscription_plan.price_determination == "Based on price list": - customer = self.get_customer(self.subscriber) - customer_group = frappe.db.get_value("Customer", customer, "customer_group") - rate = get_price(item_code=subscription_plan.item, price_list=subscription_plan.price_list, customer_group=customer_group, company=None, qty=plan.qty) - if not prorate: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': rate}) - else: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (rate * prorate_factor)}) + if not prorate: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': subscription_plan.get_plan_rate(customer)}) + else: + items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (subscription_plan.get_plan_rate(customer) * prorate_factor)}) return items diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 0865998cba..453521d04d 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -434,6 +434,134 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_plan_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Plan", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_plan_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Plan", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_16", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_gateway", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Gateway", + "length": 0, + "no_copy": 0, + "options": "Payment Gateway Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -446,7 +574,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-06-20 15:43:10.152762", + "modified": "2018-06-20 16:59:54.082358", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 4b8c8fcedd..da16ee0e8a 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.utilities.product import get_price class SubscriptionPlan(Document): def validate(self): @@ -13,3 +14,15 @@ class SubscriptionPlan(Document): def validate_interval_count(self): if self.billing_interval_count < 1: frappe.throw('Billing Interval Count cannot be less than 1') + + def get_plan_rate(self, quantity=1, customer=None): + if self.price_determination == "Fixed rate": + return self.cost + + elif self.price_determination == "Based on price list": + if customer: + customer_group = frappe.db.get_value("Customer", customer, "customer_group") + else: + customer_group = None + + return get_price(item_code=self.item, price_list=self.price_list, customer_group=customer_group, company=None, qty=quantity).price_list_rate diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/__init__.py b/erpnext/erpnext_integrations/doctype/payment_plan/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js deleted file mode 100644 index d37bed9283..0000000000 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.js +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Payment Plan', { - -}); diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json b/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json deleted file mode 100644 index 7550cb4135..0000000000 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:payment_plan_id", - "beta": 0, - "creation": "2018-05-23 10:17:31.108746", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_plan", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_plan_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_gateway", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payment Gateway", - "length": 0, - "no_copy": 0, - "options": "Payment Gateway Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Monthly", - "description": "", - "fieldname": "recurrence", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Recurrence", - "length": 0, - "no_copy": 0, - "options": "Daily\nWeekly\nMonthly\nEvery 3 Months\nEvery 6 Months\nYearly", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-05-23 18:25:48.200621", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Payment Plan", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "payment_plan", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js deleted file mode 100644 index 330e71c2ab..0000000000 --- a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payment Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payment Plan - () => frappe.tests.make('Payment Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py b/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py deleted file mode 100644 index b5935e4b5c..0000000000 --- a/erpnext/erpnext_integrations/doctype/payment_plan/test_payment_plan.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestPaymentPlan(unittest.TestCase): - pass diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py b/erpnext/erpnext_integrations/doctype/stripe_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js deleted file mode 100644 index fbc8e7fbfb..0000000000 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.js +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Stripe Settings', { - -}); diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json deleted file mode 100644 index 4129e74d74..0000000000 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.json +++ /dev/null @@ -1,315 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:gateway_name", - "beta": 0, - "creation": "2017-03-09 17:18:29.458397", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gateway_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payment Gateway Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "publishable_key", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Publishable Key", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "secret_key", - "fieldtype": "Password", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Secret Key", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "header_img", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Header Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "redirect_url", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Redirect URL", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-05-23 18:15:55.584782", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Stripe Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py deleted file mode 100644 index cc71a58dd8..0000000000 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/stripe_settings.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe import _ -from six.moves.urllib.parse import urlencode -from frappe.utils import get_url, call_hook_method, cint, flt -from frappe.integrations.utils import make_get_request, create_request_log, create_payment_gateway -import stripe - -class StripeSettings(Document): - supported_currencies = [ - "AED", "ALL", "ANG", "ARS", "AUD", "AWG", "BBD", "BDT", "BIF", "BMD", "BND", - "BOB", "BRL", "BSD", "BWP", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", "CRC", "CVE", "CZK", "DJF", - "DKK", "DOP", "DZD", "EGP", "ETB", "EUR", "FJD", "FKP", "GBP", "GIP", "GMD", "GNF", "GTQ", "GYD", - "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "ISK", "JMD", "JPY", "KES", "KHR", "KMF", - "KRW", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "MAD", "MDL", "MNT", "MOP", "MRO", "MUR", "MVR", - "MWK", "MXN", "MYR", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "PAB", "PEN", "PGK", "PHP", "PKR", - "PLN", "PYG", "QAR", "RUB", "SAR", "SBD", "SCR", "SEK", "SGD", "SHP", "SLL", "SOS", "STD", "SVC", - "SZL", "THB", "TOP", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VND", "VUV", "WST", - "XAF", "XOF", "XPF", "YER", "ZAR" - ] - - currency_wise_minimum_charge_amount = { - 'JPY': 50, 'MXN': 10, 'DKK': 2.50, 'HKD': 4.00, 'NOK': 3.00, 'SEK': 3.00, - 'USD': 0.50, 'AUD': 0.50, 'BRL': 0.50, 'CAD': 0.50, 'CHF': 0.50, 'EUR': 0.50, - 'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50 - } - - def on_update(self): - create_payment_gateway('Stripe-' + self.gateway_name, settings='Stripe Settings', controller=self.gateway_name) - call_hook_method('payment_gateway_enabled', gateway='Stripe-' + self.gateway_name) - if not self.flags.ignore_mandatory: - self.validate_stripe_credentails() - - def validate_stripe_credentails(self): - if self.publishable_key and self.secret_key: - header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} - try: - make_get_request(url="https://api.stripe.com/v1/charges", headers=header) - except Exception: - frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) - - def validate_transaction_currency(self, currency): - if currency not in self.supported_currencies: - frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) - - def validate_minimum_transaction_amount(self, currency, amount): - if currency in self.currency_wise_minimum_charge_amount: - if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0): - frappe.throw(_("For currency {0}, the minimum transaction amount should be {1}").format(currency, - self.currency_wise_minimum_charge_amount.get(currency, 0.0))) - - def get_payment_url(self, **kwargs): - return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs))) - - def create_request(self, data): - self.data = frappe._dict(data) - stripe.api_key = self.get_password(fieldname="secret_key", raise_exception=False) - stripe.default_http_client = stripe.http_client.RequestsClient() - - try: - self.integration_request = create_request_log(self.data, "Host", "Stripe") - if frappe.db.get_value("Payment Request", self.data.reference_docname, 'is_a_subscription'): - self.payment_plan = frappe.db.get_value("Payment Request", self.data.reference_docname, 'payment_plan') - return self.create_subscription_on_stripe() - else: - return self.create_charge_on_stripe() - - except Exception: - frappe.log_error(frappe.get_traceback()) - return{ - "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), - "status": 401 - } - - def create_charge_on_stripe(self): - try: - charge = stripe.Charge.create(amount=cint(flt(self.data.amount)*100), currency=self.data.currency, source=self.data.stripe_token_id, description=self.data.description) - - if charge.captured == True: - self.integration_request.db_set('status', 'Completed', update_modified=False) - self.flags.status_changed_to = "Completed" - - else: - frappe.log_error(charge.failure_message, 'Stripe Payment not completed') - - except Exception: - frappe.log_error(frappe.get_traceback()) - - return self.finalize_request() - - def create_subscription_on_stripe(self): - items = [ - { - "plan": self.payment_plan - } - ] - - try: - customer = stripe.Customer.create(description=self.data.payer_name, email=self.data.payer_email, source=self.data.stripe_token_id) - subscription = stripe.Subscription.create(customer=customer, items=items) - - if subscription.status == "active": - self.integration_request.db_set('status', 'Completed', update_modified=False) - self.flags.status_changed_to = "Completed" - - else: - self.integration_request.db_set('status', 'Failed', update_modified=False) - frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') - - except Exception: - self.integration_request.db_set('status', 'Failed', update_modified=False) - frappe.log_error(frappe.get_traceback()) - - return self.finalize_request() - - def finalize_request(self): - redirect_to = self.data.get('redirect_to') or None - redirect_message = self.data.get('redirect_message') or None - status = self.integration_request.status - - if self.flags.status_changed_to == "Completed": - if self.data.reference_doctype and self.data.reference_docname: - custom_redirect_to = None - try: - custom_redirect_to = frappe.get_doc(self.data.reference_doctype, - self.data.reference_docname).run_method("on_payment_authorized", self.flags.status_changed_to) - except Exception: - frappe.log_error(frappe.get_traceback()) - - if custom_redirect_to: - redirect_to = custom_redirect_to - - redirect_url = 'payment-success' - - if self.redirect_url: - redirect_url = self.redirect_url - redirect_to = None - else: - redirect_url = 'payment-failed' - - if redirect_to: - redirect_url += '?' + urlencode({'redirect_to': redirect_to}) - if redirect_message: - redirect_url += '&' + urlencode({'redirect_message': redirect_message}) - - return { - "redirect_to": redirect_url, - "status": status - } - -def get_gateway_controller(doc): - payment_request = frappe.get_doc("Payment Request", doc) - gateway_controller = frappe.db.get_value("Payment Gateway", payment_request.payment_gateway, "gateway_controller") - return gateway_controller diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js deleted file mode 100644 index b491ba5737..0000000000 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Stripe Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Stripe Settings - () => frappe.tests.make('Stripe Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py b/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py deleted file mode 100644 index 0c5aa83c65..0000000000 --- a/erpnext/erpnext_integrations/doctype/stripe_settings/test_stripe_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestStripeSettings(unittest.TestCase): - pass diff --git a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py b/erpnext/erpnext_integrations/stripe_integration.py similarity index 82% rename from erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py rename to erpnext/erpnext_integrations/stripe_integration.py index 0e71fbca5f..c33adf90d8 100644 --- a/erpnext/erpnext_integrations/doctype/payment_plan/payment_plan.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -9,10 +9,6 @@ from frappe import _ from frappe.integrations.utils import create_request_log import stripe -class PaymentPlan(Document): - pass - - def create_stripe_subscription(gateway_controller, data): stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) stripe_settings.data = frappe._dict(data) @@ -22,7 +18,7 @@ def create_stripe_subscription(gateway_controller, data): try: stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") - stripe_settings.payment_plan = frappe.db.get_value("Payment Request", stripe_settings.data.reference_docname, 'payment_plan') + stripe_settings.payment_plans = frappe.get_doc("Payment Request", stripe_settings.data.reference_docname).subscription_plans return create_subscription_on_stripe(stripe_settings) except Exception: @@ -34,11 +30,13 @@ def create_stripe_subscription(gateway_controller, data): def create_subscription_on_stripe(stripe_settings): - items = [ - { - "plan": stripe_settings.payment_plan - } - ] + items = [] + for payment_plan in stripe_settings.payment_plans: + plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "payment_plan_id") + items.append({"plan": plan, "quantity": payment_plan.qty}) + + frappe.log_error(items, 'Items') + try: customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id) @@ -56,4 +54,4 @@ def create_subscription_on_stripe(stripe_settings): stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) frappe.log_error(frappe.get_traceback()) - return stripe_settings.finalize_request() + return stripe_settings.finalize_request() \ No newline at end of file diff --git a/erpnext/templates/includes/integrations/stripe_checkout.js b/erpnext/templates/includes/integrations/stripe_checkout.js deleted file mode 100644 index 1ce3b12b7a..0000000000 --- a/erpnext/templates/includes/integrations/stripe_checkout.js +++ /dev/null @@ -1,85 +0,0 @@ -var stripe = Stripe("{{ publishable_key }}"); - -var elements = stripe.elements(); - -var style = { - base: { - color: '#32325d', - lineHeight: '18px', - fontFamily: '"Helvetica Neue", Helvetica, sans-serif', - fontSmoothing: 'antialiased', - fontSize: '16px', - '::placeholder': { - color: '#aab7c4' - } - }, - invalid: { - color: '#fa755a', - iconColor: '#fa755a' - } -}; - -var card = elements.create('card', { - hidePostalCode: true, - style: style -}); - -card.mount('#card-element'); - -function setOutcome(result) { - - if (result.token) { - $('#submit').prop('disabled', true) - $('#submit').html(__('Processing...')) - frappe.call({ - method:"erpnext.templates.pages.integrations.stripe_checkout.make_payment", - freeze:true, - headers: {"X-Requested-With": "XMLHttpRequest"}, - args: { - "stripe_token_id": result.token.id, - "data": JSON.stringify({{ frappe.form_dict|json }}), - "reference_doctype": "{{ reference_doctype }}", - "reference_docname": "{{ reference_docname }}" - }, - callback: function(r) { - if (r.message.status == "Completed") { - $('#submit').hide() - $('.success').show() - setTimeout(function() { - window.location.href = r.message.redirect_to - }, 2000); - } else { - $('#submit').hide() - $('.error').show() - setTimeout(function() { - window.location.href = r.message.redirect_to - }, 2000); - } - } - }); - - } else if (result.error) { - $('.error').html() = result.error.message; - $('.error').show() - } -} - -card.on('change', function(event) { - var displayError = document.getElementById('card-errors'); - if (event.error) { - displayError.textContent = event.error.message; - } else { - displayError.textContent = ''; - } -}); - -frappe.ready(function() { - $('#submit').off("click").on("click", function(e) { - e.preventDefault(); - var extraDetails = { - name: $('input[name=cardholder-name]').val(), - email: $('input[name=cardholder-email]').val() - } - stripe.createToken(card, extraDetails).then(setOutcome); - }) -}); diff --git a/erpnext/templates/pages/integrations/__init__.py b/erpnext/templates/pages/integrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.html b/erpnext/templates/pages/integrations/gocardless_checkout.html deleted file mode 100644 index eb124cafb6..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_checkout.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Payment {% endblock %} - -{%- block header -%}{% endblock %} - -{% block script %} - -{% endblock %} - -{%- block page_content -%} -

- {{ _("Loading Payment System") }} -

- -{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py deleted file mode 100644 index 3c2466ea2f..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_checkout.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import flt -import json -from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller -from frappe.utils import get_url - -no_cache = 1 -no_sitemap = 1 - -expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', - 'payer_name', 'payer_email', 'order_id', 'currency') - -def get_context(context): - context.no_cache = 1 - - # all these keys exist in form_dict - if not (set(expected_keys) - set(frappe.form_dict.keys())): - for key in expected_keys: - context[key] = frappe.form_dict[key] - - context['amount'] = flt(context['amount']) - - gateway_controller = get_gateway_controller(context.reference_docname) - context['header_img'] = frappe.db.get_value("GoCardless Settings", gateway_controller, "header_img") - - else: - frappe.redirect_to_message(_('Some information is missing'), - _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) - frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - -@frappe.whitelist(allow_guest=True) -def check_mandate(data, reference_doctype, reference_docname): - data = json.loads(data) - - client = gocardless_initialization(reference_docname) - - payer = frappe.get_doc("Customer", data["payer_name"]) - - if payer.customer_type == "Individual" and payer.customer_primary_contact is not None: - primary_contact = frappe.get_doc("Contact", payer.customer_primary_contact) - prefilled_customer = { - "company_name": payer.name, - "given_name": primary_contact.first_name, - "family_name": primary_contact.last_name, - } - if primary_contact.email_id is not None: - prefilled_customer.update({"email": primary_contact.email_id}) - else: - prefilled_customer.update({"email": frappe.session.user}) - - else: - prefilled_customer = { - "company_name": payer.name, - "email": frappe.session.user - } - - success_url = get_url("./integrations/gocardless_confirmation?reference_doctype=" + reference_doctype + "&reference_docname=" + reference_docname) - - try: - redirect_flow = client.redirect_flows.create(params={ - "description": _("Pay {0} {1}".format(data['amount'], data['currency'])), - "session_token": frappe.session.user, - "success_redirect_url": success_url, - "prefilled_customer": prefilled_customer - }) - - return {"redirect_to": redirect_flow.redirect_url} - - except Exception as e: - frappe.log_error(e, "GoCardless Payment Error") - return {"redirect_to": '/integrations/payment-failed'} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html deleted file mode 100644 index 1baf23be26..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Payment {% endblock %} - -{%- block header -%}{% endblock %} - -{% block script %} - -{% endblock %} - -{%- block page_content -%} -

- {{ _("Payment Confirmation") }} -

- -{% endblock %} diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py deleted file mode 100644 index fc564c3df9..0000000000 --- a/erpnext/templates/pages/integrations/gocardless_confirmation.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller - -no_cache = 1 -no_sitemap = 1 - -expected_keys = ('redirect_flow_id', 'reference_doctype', 'reference_docname') - -def get_context(context): - context.no_cache = 1 - - # all these keys exist in form_dict - if not (set(expected_keys) - set(frappe.form_dict.keys())): - for key in expected_keys: - context[key] = frappe.form_dict[key] - - else: - frappe.redirect_to_message(_('Some information is missing'), - _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) - frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - -@frappe.whitelist(allow_guest=True) -def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): - - client = gocardless_initialization(reference_docname) - - try: - redirect_flow = client.redirect_flows.complete( - redirect_flow_id, - params={ - "session_token": frappe.session.user - }) - - data = { - "mandate": redirect_flow.links.mandate, - "customer": redirect_flow.links.customer, - "redirect_to": redirect_flow.confirmation_url, - "redirect_message": "Mandate successfully created", - "reference_doctype": reference_doctype, - "reference_docname": reference_docname - } - - try: - create_mandate(data) - except Exception as e: - frappe.log_error(e, "GoCardless Mandate Registration Error") - - gateway_controller = get_gateway_controller(reference_docname) - frappe.get_doc("GoCardless Settings", gateway_controller).create_payment_request(data) - - return {"redirect_to": redirect_flow.confirmation_url} - - except Exception as e: - frappe.log_error(e, "GoCardless Payment Error") - return {"redirect_to": '/integrations/payment-failed'} - - -def create_mandate(data): - data = frappe._dict(data) - frappe.logger().debug(data) - - mandate = data.get('mandate') - - if frappe.db.exists("GoCardless Mandate", mandate): - return - - else: - reference_doc = frappe.db.get_value(data.get('reference_doctype'), data.get('reference_docname'), ["reference_doctype", "reference_name"], as_dict=1) - erpnext_customer = frappe.db.get_value(reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1) - - try: - frappe.get_doc({ - "doctype": "GoCardless Mandate", - "mandate": mandate, - "customer": erpnext_customer.customer_name, - "gocardless_customer": data.get('customer') - }).insert(ignore_permissions=True) - - except Exception: - frappe.log_error(frappe.get_traceback()) diff --git a/erpnext/templates/pages/integrations/stripe_checkout.css b/erpnext/templates/pages/integrations/stripe_checkout.css deleted file mode 100644 index a42808aa7f..0000000000 --- a/erpnext/templates/pages/integrations/stripe_checkout.css +++ /dev/null @@ -1,113 +0,0 @@ -.StripeElement { - background-color: white; - height: 40px; - padding: 10px 12px; - border-radius: 4px; - border: 1px solid transparent; - box-shadow: 0 1px 3px 0 #e6ebf1; - -webkit-transition: box-shadow 150ms ease; - transition: box-shadow 150ms ease; -} - -.StripeElement--focus { - box-shadow: 0 1px 3px 0 #cfd7df; -} - -.StripeElement--invalid { - border-color: #fa755a; -} - -.StripeElement--webkit-autofill { - background-color: #fefde5; -} - -.stripe #payment-form { - margin-top: 80px; -} - -.stripe button { - float: right; - display: block; - background: #5e64ff; - color: white; - box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); - border-radius: 4px; - border: 0; - margin-top: 20px; - font-size: 15px; - font-weight: 400; - max-width: 40%; - height: 40px; - line-height: 38px; - outline: none; -} - -.stripe button:hover, .stripe button:focus { - background: #2b33ff; - border-color: #0711ff; -} - -.stripe button:active { - background: #5e64ff; -} - -.stripe button:disabled { - background: #515e80; -} - -.stripe .group { - background: white; - box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08); - border-radius: 4px; - margin-bottom: 20px; -} - -.stripe label { - position: relative; - color: #8898AA; - font-weight: 300; - height: 40px; - line-height: 40px; - margin-left: 20px; - display: block; -} - -.stripe .group label:not(:last-child) { - border-bottom: 1px solid #F0F5FA; -} - -.stripe label>span { - width: 20%; - text-align: right; - float: left; -} - -.current-card { - margin-left: 20px; -} - -.field { - background: transparent; - font-weight: 300; - border: 0; - color: #31325F; - outline: none; - padding-right: 10px; - padding-left: 10px; - cursor: text; - width: 70%; - height: 40px; - float: right; -} - -.field::-webkit-input-placeholder { - color: #CFD7E0; -} - -.field::-moz-placeholder { - color: #CFD7E0; -} - -.field:-ms-input-placeholder { - color: #CFD7E0; -} diff --git a/erpnext/templates/pages/integrations/stripe_checkout.html b/erpnext/templates/pages/integrations/stripe_checkout.html deleted file mode 100644 index b52428667b..0000000000 --- a/erpnext/templates/pages/integrations/stripe_checkout.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Payment {% endblock %} - -{%- block header -%} -{% endblock %} - -{% block script %} - - -{% endblock %} - -{%- block page_content -%} - -
-
- -

{{description}}

-
-
-
-
- -
-
-
-
- -
-
-
- -
- -
- -
- - -
-
-
-
- - -{% endblock %} diff --git a/erpnext/templates/pages/integrations/stripe_checkout.py b/erpnext/templates/pages/integrations/stripe_checkout.py deleted file mode 100644 index 6faf8f1cc9..0000000000 --- a/erpnext/templates/pages/integrations/stripe_checkout.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import cint, fmt_money -import json -from erpnext.erpnext_integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller -from erpnext.erpnext_integrations.doctype.payment_plan.payment_plan import create_stripe_subscription - -no_cache = 1 -no_sitemap = 1 - -expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', - 'payer_name', 'payer_email', 'order_id', 'currency') - -def get_context(context): - context.no_cache = 1 - - # all these keys exist in form_dict - if not (set(expected_keys) - set(list(frappe.form_dict))): - for key in expected_keys: - context[key] = frappe.form_dict[key] - - gateway_controller = get_gateway_controller(context.reference_docname) - context.publishable_key = get_api_key(context.reference_docname, gateway_controller) - context.image = get_header_image(context.reference_docname, gateway_controller) - - context['amount'] = fmt_money(amount=context['amount'], currency=context['currency']) - - if frappe.db.get_value(context.reference_doctype, context.reference_docname, "is_a_subscription"): - payment_plan = frappe.db.get_value(context.reference_doctype, context.reference_docname, "payment_plan") - recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence") - - context['amount'] = context['amount'] + " " + _(recurrence) - - else: - frappe.redirect_to_message(_('Some information is missing'), - _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) - frappe.local.flags.redirect_location = frappe.local.response.location - raise frappe.Redirect - -def get_api_key(doc, gateway_controller): - publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key") - if cint(frappe.form_dict.get("use_sandbox")): - publishable_key = frappe.conf.sandbox_publishable_key - - return publishable_key - -def get_header_image(doc, gateway_controller): - header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img") - return header_image - -@frappe.whitelist(allow_guest=True) -def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None): - data = json.loads(data) - - data.update({ - "stripe_token_id": stripe_token_id - }) - - gateway_controller = get_gateway_controller(reference_docname) - - if frappe.db.get_value("Payment Request", reference_docname, 'is_a_subscription'): - data = create_stripe_subscription(gateway_controller, data) - else: - data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data) - - frappe.db.commit() - return data diff --git a/requirements.txt b/requirements.txt index 2da865c277..13055ac2ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,3 @@ python-stdnum braintree gocardless_pro woocommerce -stripe From a8d766f5f757c7fbdf7bdc7c7db4e07a69edc78a Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 17:40:00 +0000 Subject: [PATCH 11/24] Remove payment plan reference --- .../accounts/doctype/payment_request/payment_request.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index a80042937b..90042cbbb3 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -13,14 +13,6 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){ } }) } - - frm.set_query("payment_plan", function() { - return { - "filters": { - "payment_gateway": ["=", frm.doc.payment_gateway_account] - } - }; - }); }) frappe.ui.form.on("Payment Request", "refresh", function(frm) { From 244b638fe319bdd156c0e0dbf7887c058b418a33 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 17:51:52 +0000 Subject: [PATCH 12/24] Dev cleanup --- .../doctype/subscription/subscription.py | 12 ++- .../templates/pages/integrations/__init__.py | 0 .../integrations/gocardless_checkout.html | 16 ++++ .../pages/integrations/gocardless_checkout.py | 76 +++++++++++++++++ .../integrations/gocardless_confirmation.html | 16 ++++ .../integrations/gocardless_confirmation.py | 85 +++++++++++++++++++ 6 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 erpnext/templates/pages/integrations/__init__.py create mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.html create mode 100644 erpnext/templates/pages/integrations/gocardless_checkout.py create mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.html create mode 100644 erpnext/templates/pages/integrations/gocardless_confirmation.py diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 00dc61876d..2b18a62e1b 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -13,9 +13,9 @@ from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_l class Subscription(Document): def before_insert(self): # update start just before the subscription doc is created - self.update_subscription_period(self.start, init=True) + self.update_subscription_period(self.start) - def update_subscription_period(self, date=None, init=False): + def update_subscription_period(self, date=None): """ Subscription period is the period to be billed. This method updates the beginning of the billing period and end of the billing period. @@ -25,7 +25,7 @@ class Subscription(Document): as `current_invoice_end`. """ self.set_current_invoice_start(date) - self.set_current_invoice_end(init=False) + self.set_current_invoice_end() def set_current_invoice_start(self, date=None): """ @@ -40,7 +40,7 @@ class Subscription(Document): else: self.current_invoice_start = nowdate() - def set_current_invoice_end(self, init=False): + def set_current_invoice_end(self): """ This sets the date of the end of the current billing period. @@ -51,9 +51,7 @@ class Subscription(Document): current billing period where `x` is the billing interval from the `Subscription Plan` in the `Subscription`. """ - if init==True: - return - elif self.is_trialling(): + if self.is_trialling(): self.current_invoice_end = self.trial_period_end else: billing_cycle_info = self.get_billing_cycle_data() diff --git a/erpnext/templates/pages/integrations/__init__.py b/erpnext/templates/pages/integrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.html b/erpnext/templates/pages/integrations/gocardless_checkout.html new file mode 100644 index 0000000000..bbe5640d5e --- /dev/null +++ b/erpnext/templates/pages/integrations/gocardless_checkout.html @@ -0,0 +1,16 @@ +{% extends "templates/web.html" %} + +{% block title %} Payment {% endblock %} + +{%- block header -%}{% endblock %} + +{% block script %} + +{% endblock %} + +{%- block page_content -%} +

+ {{ _("Loading Payment System") }} +

+ +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py new file mode 100644 index 0000000000..2ba7001a61 --- /dev/null +++ b/erpnext/templates/pages/integrations/gocardless_checkout.py @@ -0,0 +1,76 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt +import json +from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller +from frappe.utils import get_url + +no_cache = 1 +no_sitemap = 1 + +expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname', + 'payer_name', 'payer_email', 'order_id', 'currency') + +def get_context(context): + context.no_cache = 1 + + # all these keys exist in form_dict + if not (set(expected_keys) - set(frappe.form_dict.keys())): + for key in expected_keys: + context[key] = frappe.form_dict[key] + + context['amount'] = flt(context['amount']) + + gateway_controller = get_gateway_controller(context.reference_docname) + context['header_img'] = frappe.db.get_value("GoCardless Settings", gateway_controller, "header_img") + + else: + frappe.redirect_to_message(_('Some information is missing'), + _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + +@frappe.whitelist(allow_guest=True) +def check_mandate(data, reference_doctype, reference_docname): + data = json.loads(data) + + client = gocardless_initialization(reference_docname) + + payer = frappe.get_doc("Customer", data["payer_name"]) + + if payer.customer_type == "Individual" and payer.customer_primary_contact is not None: + primary_contact = frappe.get_doc("Contact", payer.customer_primary_contact) + prefilled_customer = { + "company_name": payer.name, + "given_name": primary_contact.first_name, + "family_name": primary_contact.last_name, + } + if primary_contact.email_id is not None: + prefilled_customer.update({"email": primary_contact.email_id}) + else: + prefilled_customer.update({"email": frappe.session.user}) + + else: + prefilled_customer = { + "company_name": payer.name, + "email": frappe.session.user + } + + success_url = get_url("./integrations/gocardless_confirmation?reference_doctype=" + reference_doctype + "&reference_docname=" + reference_docname) + + try: + redirect_flow = client.redirect_flows.create(params={ + "description": _("Pay {0} {1}".format(data['amount'], data['currency'])), + "session_token": frappe.session.user, + "success_redirect_url": success_url, + "prefilled_customer": prefilled_customer + }) + + return {"redirect_to": redirect_flow.redirect_url} + + except Exception as e: + frappe.log_error(e, "GoCardless Payment Error") + return {"redirect_to": '/integrations/payment-failed'} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.html b/erpnext/templates/pages/integrations/gocardless_confirmation.html new file mode 100644 index 0000000000..1567487657 --- /dev/null +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.html @@ -0,0 +1,16 @@ +{% extends "templates/web.html" %} + +{% block title %} Payment {% endblock %} + +{%- block header -%}{% endblock %} + +{% block script %} + +{% endblock %} + +{%- block page_content -%} +

+ {{ _("Payment Confirmation") }} +

+ +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py new file mode 100644 index 0000000000..069d90050e --- /dev/null +++ b/erpnext/templates/pages/integrations/gocardless_confirmation.py @@ -0,0 +1,85 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt +from __future__ import unicode_literals +import frappe +from frappe import _ +from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_settings import gocardless_initialization, get_gateway_controller + +no_cache = 1 +no_sitemap = 1 + +expected_keys = ('redirect_flow_id', 'reference_doctype', 'reference_docname') + +def get_context(context): + context.no_cache = 1 + + # all these keys exist in form_dict + if not (set(expected_keys) - set(frappe.form_dict.keys())): + for key in expected_keys: + context[key] = frappe.form_dict[key] + + else: + frappe.redirect_to_message(_('Some information is missing'), + _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.')) + frappe.local.flags.redirect_location = frappe.local.response.location + raise frappe.Redirect + +@frappe.whitelist(allow_guest=True) +def confirm_payment(redirect_flow_id, reference_doctype, reference_docname): + + client = gocardless_initialization(reference_docname) + + try: + redirect_flow = client.redirect_flows.complete( + redirect_flow_id, + params={ + "session_token": frappe.session.user + }) + + data = { + "mandate": redirect_flow.links.mandate, + "customer": redirect_flow.links.customer, + "redirect_to": redirect_flow.confirmation_url, + "redirect_message": "Mandate successfully created", + "reference_doctype": reference_doctype, + "reference_docname": reference_docname + } + + try: + create_mandate(data) + except Exception as e: + frappe.log_error(e, "GoCardless Mandate Registration Error") + + gateway_controller = get_gateway_controller(reference_docname) + frappe.get_doc("GoCardless Settings", gateway_controller).create_payment_request(data) + + return {"redirect_to": redirect_flow.confirmation_url} + + except Exception as e: + frappe.log_error(e, "GoCardless Payment Error") + return {"redirect_to": '/integrations/payment-failed'} + + +def create_mandate(data): + data = frappe._dict(data) + frappe.logger().debug(data) + + mandate = data.get('mandate') + + if frappe.db.exists("GoCardless Mandate", mandate): + return + + else: + reference_doc = frappe.db.get_value(data.get('reference_doctype'), data.get('reference_docname'), ["reference_doctype", "reference_name"], as_dict=1) + erpnext_customer = frappe.db.get_value(reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1) + + try: + frappe.get_doc({ + "doctype": "GoCardless Mandate", + "mandate": mandate, + "customer": erpnext_customer.customer_name, + "gocardless_customer": data.get('customer') + }).insert(ignore_permissions=True) + + except Exception: + frappe.log_error(frappe.get_traceback()) \ No newline at end of file From 63e1dd59935ad774318e5f196b37b933a768b33a Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 18:01:51 +0000 Subject: [PATCH 13/24] Remove doc --- .../docs/user/manual/en/setting-up/index.txt | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 erpnext/docs/user/manual/en/setting-up/index.txt diff --git a/erpnext/docs/user/manual/en/setting-up/index.txt b/erpnext/docs/user/manual/en/setting-up/index.txt deleted file mode 100644 index 2733e91ae2..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/index.txt +++ /dev/null @@ -1,17 +0,0 @@ -setup-wizard -users-and-permissions -settings -data -email -print -setting-up-taxes -pos-setting -price-lists -authorization-rule -sms-setting -stock-reconciliation-for-non-serialized-item -territory -third-party-backups -workflows -company-setup -articles From bbed5d41ca00238e6f4822e115cee7c7523ace23 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 18:04:14 +0000 Subject: [PATCH 14/24] Remove remaining images --- .../img/setup/integrations/payment_plan.png | Bin 27145 -> 0 bytes .../subscription_payment_request.png | Bin 20124 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/docs/assets/img/setup/integrations/payment_plan.png delete mode 100644 erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png diff --git a/erpnext/docs/assets/img/setup/integrations/payment_plan.png b/erpnext/docs/assets/img/setup/integrations/payment_plan.png deleted file mode 100644 index 6187fc5c2b2fe4e8d6e5b65eaab3048f39661be2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27145 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 PCpcM2C5cio{m=gen%=Kn diff --git a/erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png b/erpnext/docs/assets/img/setup/integrations/subscription_payment_request.png deleted file mode 100644 index 528159efc06c7ca2970fd2eb58db3a5dae448023..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20124 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 From 19a47c3e0a4c88c095ed796bd8a3e0fc024a2786 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 20 Jun 2018 18:52:31 +0000 Subject: [PATCH 15/24] Codacy corrections --- .../payment_request/payment_request.js | 26 +++++++++---------- .../payment_request/payment_request.py | 6 ++--- .../subscription_plan/subscription_plan.js | 2 +- .../stripe_integration.py | 1 - 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index 90042cbbb3..9ecce18e7e 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -50,19 +50,19 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) { }); frappe.ui.form.on("Payment Request", "is_a_subscription", function(frm) { - frm.toggle_reqd("payment_gateway_account", frm.doc.is_a_subscription); - frm.toggle_reqd("subscription_plans", frm.doc.is_a_subscription); + frm.toggle_reqd("payment_gateway_account", frm.doc.is_a_subscription); + frm.toggle_reqd("subscription_plans", frm.doc.is_a_subscription); - if (frm.doc.is_a_subscription) { - frappe.call({ - method: "get_subscription_details", - doc: frm.doc, - freeze: true, - callback: function(r){ - if(!r.exc) { - frm.refresh_field("subscription_plans"); - } + if (frm.doc.is_a_subscription) { + frappe.call({ + method: "get_subscription_details", + doc: frm.doc, + freeze: true, + callback: function(r){ + if(!r.exc) { + frm.refresh_field("subscription_plans"); } - }); - } + } + }); + } }); diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 8af0bcc728..a1fdb85d97 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -14,7 +14,7 @@ from frappe.integrations.utils import get_payment_gateway_controller from frappe.utils.background_jobs import enqueue from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription -class PaymentRequest(Document): +class PaymentRequest(Document): def validate(self): self.validate_reference_document() self.validate_payment_request() @@ -47,9 +47,9 @@ class PaymentRequest(Document): rate = plan.get_plan_rate() frappe.log_error(rate) - + amount += rate - + if amount != self.grand_total: frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.".format(self.grand_total, amount))) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js index 5a45c2caa2..aaa32cfe7e 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.js +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.js @@ -6,4 +6,4 @@ frappe.ui.form.on('Subscription Plan', { frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate'); frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list'); } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py index c33adf90d8..a3a88bb1ba 100644 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -from frappe.model.document import Document import frappe from frappe import _ from frappe.integrations.utils import create_request_log From 5a2ee41af3821d11b9f822259deffccb4e9cecd3 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 21 Jun 2018 07:03:15 +0000 Subject: [PATCH 16/24] Test correction --- erpnext/accounts/doctype/subscription/test_subscription.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 47efa45429..ef0fed5889 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -15,6 +15,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 900 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -24,6 +25,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 2' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 1999 plan.billing_interval = 'Month' plan.billing_interval_count = 1 @@ -33,6 +35,7 @@ def create_plan(): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name 3' plan.item = '_Test Non Stock Item' + plan.price_determination = "Fixed rate" plan.cost = 1999 plan.billing_interval = 'Day' plan.billing_interval_count = 14 From cde1f08af6c96c44e83345e35c002bef02f4b683 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 27 Jun 2018 08:27:21 +0000 Subject: [PATCH 17/24] Review corrections --- .../accounts/doctype/payment_request/payment_request.py | 3 +-- erpnext/erpnext_integrations/stripe_integration.py | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index a1fdb85d97..d40a870174 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -46,7 +46,6 @@ class PaymentRequest(Document): frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(plan.name))) rate = plan.get_plan_rate() - frappe.log_error(rate) amount += rate @@ -261,7 +260,7 @@ class PaymentRequest(Document): def get_subscription_details(self): if self.reference_doctype == "Sales Invoice": - subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice='{0}'""".format(self.reference_name), as_dict=1) + subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""", self.reference_name, as_dict=1) self.subscription_plans = [] for subscription in subscriptions: plans = frappe.get_doc("Subscription", subscription.sub_name).plans diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py index a3a88bb1ba..a35ca28e0a 100644 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -16,9 +16,9 @@ def create_stripe_subscription(gateway_controller, data): stripe.default_http_client = stripe.http_client.RequestsClient() try: - stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") - stripe_settings.payment_plans = frappe.get_doc("Payment Request", stripe_settings.data.reference_docname).subscription_plans - return create_subscription_on_stripe(stripe_settings) + stripe_settings.integration_request = create_request_log(stripe_settings.data, "Host", "Stripe") + stripe_settings.payment_plans = frappe.get_doc("Payment Request", stripe_settings.data.reference_docname).subscription_plans + return create_subscription_on_stripe(stripe_settings) except Exception: frappe.log_error(frappe.get_traceback()) @@ -34,9 +34,6 @@ def create_subscription_on_stripe(stripe_settings): plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "payment_plan_id") items.append({"plan": plan, "quantity": payment_plan.qty}) - frappe.log_error(items, 'Items') - - try: customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id) subscription = stripe.Subscription.create(customer=customer, items=items) From 8b3e841fbfe93d1371ca9d02b47fea54dc09417f Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 27 Jun 2018 17:10:24 +0000 Subject: [PATCH 18/24] Travis correction --- .../doctype/subscription/test_subscription.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index ef0fed5889..d5c9fe8fac 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -61,7 +61,7 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_start = nowdate() subscription.trial_period_end = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.trial_period_start, nowdate()) @@ -76,7 +76,7 @@ class TestSubscription(unittest.TestCase): def test_create_subscription_without_trial_with_correct_period(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.trial_period_start, None) @@ -94,7 +94,7 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) self.assertRaises(frappe.ValidationError, subscription.save) subscription.delete() @@ -104,8 +104,8 @@ class TestSubscription(unittest.TestCase): subscription.subscriber = '_Test Customer' subscription.trial_period_end = nowdate() subscription.trial_period_start = add_days(nowdate(), 30) - subscription.append('plans', {'plan': '_Test Plan Name'}) - subscription.append('plans', {'plan': '_Test Plan Name 3'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) + subscription.append('plans', {'plan': '_Test Plan Name 3', 'qty': 1}) self.assertRaises(frappe.ValidationError, subscription.save) subscription.delete() @@ -114,7 +114,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.start = '2018-01-01' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.insert() self.assertEqual(subscription.status, 'Active') @@ -130,7 +130,7 @@ class TestSubscription(unittest.TestCase): def test_status_goes_back_to_active_after_invoice_is_paid(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -160,7 +160,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -183,7 +183,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -201,7 +201,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_invoice_days_until_due(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.days_until_due = 10 subscription.start = add_months(nowdate(), -1) subscription.insert() @@ -219,7 +219,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -243,7 +243,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_remains_active_during_invoice_period(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.process() # no changes expected @@ -269,7 +269,7 @@ class TestSubscription(unittest.TestCase): def test_subscription_cancelation(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -285,7 +285,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertEqual(subscription.status, 'Active') @@ -320,7 +320,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() invoice = subscription.get_current_invoice() @@ -340,7 +340,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -364,7 +364,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -398,7 +398,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -435,7 +435,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.start = '2018-01-01' subscription.insert() subscription.process() # generate first invoice @@ -463,7 +463,7 @@ class TestSubscription(unittest.TestCase): def test_restart_active_subscription(self): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() self.assertRaises(frappe.ValidationError, subscription.restart_subscription) @@ -474,7 +474,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.additional_discount_percentage = 10 - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() @@ -489,7 +489,7 @@ class TestSubscription(unittest.TestCase): subscription = frappe.new_doc('Subscription') subscription.subscriber = '_Test Customer' subscription.additional_discount_amount = 11 - subscription.append('plans', {'plan': '_Test Plan Name'}) + subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.save() subscription.cancel_subscription() From da8b11fd12f8686a88ea2ae23e482ba638473bc9 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 27 Jun 2018 18:05:35 +0000 Subject: [PATCH 19/24] Travis correction --- erpnext/accounts/doctype/subscription/subscription.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 2b18a62e1b..b75be0c73c 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -244,7 +244,6 @@ class Subscription(Document): # for that reason items_list = self.get_items_from_plans(self.plans, prorate) for item in items_list: - item['qty'] = self.quantity invoice.append('items', item) # Taxes From 90669a03b38d677843b4e5aa13ea1cfffdbd86dc Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Wed, 27 Jun 2018 18:59:03 +0000 Subject: [PATCH 20/24] Test correction --- erpnext/accounts/doctype/subscription/test_subscription.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index d5c9fe8fac..c42b8e824b 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -147,7 +147,7 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(subscription.status, 'Active') - self.assertEqual(subscription.current_invoice_start, nowdate()) + self.assertEqual(subscription.current_invoice_start, add_months(subscription.start, 1)) self.assertEqual(len(subscription.invoices), 1) subscription.delete() @@ -453,8 +453,9 @@ class TestSubscription(unittest.TestCase): subscription.process() self.assertEqual(subscription.status, 'Active') + # A new invoice is generated subscription.process() - self.assertEqual(subscription.status, 'Active') + self.assertEqual(subscription.status, 'Past Due Date') settings.cancel_after_grace = default_grace_period_action settings.save() From a0b7236f661680ad8baadbc3669e2c9a92a90f85 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 5 Jul 2018 20:50:19 +0000 Subject: [PATCH 21/24] Corrections following Saurabh's comments --- .../payment_request/payment_request.js | 13 +++++--- .../payment_request/payment_request.py | 31 ++++++++++--------- .../doctype/subscription/subscription.py | 7 +++-- .../subscription_plan/subscription_plan.py | 26 ++++++++++------ 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index 9ecce18e7e..8820161a36 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -55,11 +55,16 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function(frm) { if (frm.doc.is_a_subscription) { frappe.call({ - method: "get_subscription_details", - doc: frm.doc, + method: "erpnext.accounts.doctype.payment_request.payment_request.get_subscription_details", + args: {"reference_doctype": frm.doc.reference_doctype, "reference_name": frm.doc.reference_name}, freeze: true, - callback: function(r){ - if(!r.exc) { + callback: function(data){ + if(!data.exc) { + $.each(data.message || [], function(i, v){ + var d = frappe.model.add_child(frm.doc, "Subscription Plan Detail", "subscription_plans"); + d.qty = v.qty; + d.plan = v.plan; + }); frm.refresh_field("subscription_plans"); } } diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index d40a870174..7f6df77129 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -13,12 +13,14 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from frappe.integrations.utils import get_payment_gateway_controller from frappe.utils.background_jobs import enqueue from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription +from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate class PaymentRequest(Document): def validate(self): self.validate_reference_document() self.validate_payment_request() self.validate_currency() + self.validate_subscription_details() def validate_reference_document(self): if not self.reference_doctype or not self.reference_name: @@ -34,18 +36,15 @@ class PaymentRequest(Document): if self.payment_account and ref_doc.currency != frappe.db.get_value("Account", self.payment_account, "account_currency"): frappe.throw(_("Transaction currency must be same as Payment Gateway currency")) - def on_update(self): - self.validate_subscription_details() - def validate_subscription_details(self): if self.is_a_subscription: amount = 0 for subscription_plan in self.subscription_plans: - plan = frappe.get_doc("Subscription Plan", subscription_plan.plan) - if plan.payment_gateway != self.payment_gateway_account: + payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway") + if payment_gateway != self.payment_gateway_account: frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(plan.name))) - rate = plan.get_plan_rate() + rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty) amount += rate @@ -258,15 +257,6 @@ class PaymentRequest(Document): if payment_provider == "stripe": return create_stripe_subscription(gateway_controller, data) - def get_subscription_details(self): - if self.reference_doctype == "Sales Invoice": - subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""", self.reference_name, as_dict=1) - self.subscription_plans = [] - for subscription in subscriptions: - plans = frappe.get_doc("Subscription", subscription.sub_name).plans - for plan in plans: - self.append('subscription_plans', plan) - @frappe.whitelist(allow_guest=True) def make_payment_request(**args): """Make payment request""" @@ -407,3 +397,14 @@ def get_dummy_message(doc):

{{ _("Thank you for your business!") }}

""", dict(doc=doc, payment_url = '{{ payment_url }}')) + +@frappe.whitelist() +def get_subscription_details(reference_doctype, reference_name): + if reference_doctype == "Sales Invoice": + subscriptions = frappe.db.sql("""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""",reference_name, as_dict=1) + subscription_plans = [] + for subscription in subscriptions: + plans = frappe.get_doc("Subscription", subscription.sub_name).plans + for plan in plans: + subscription_plans.append(plan) + return subscription_plans \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index b75be0c73c..fe39161960 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -8,6 +8,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt +from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate class Subscription(Document): @@ -298,11 +299,11 @@ class Subscription(Document): items = [] customer = self.get_customer(self.subscriber) for plan in plans: - subscription_plan = frappe.get_doc("Subscription Plan", plan.plan) + item_code = frappe.db.get_value("Subscription Plan", plan.plan, "item") if not prorate: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': subscription_plan.get_plan_rate(customer)}) + items.append({'item_code': item_code, 'qty': plan.qty, 'rate': get_plan_rate(plan.plan, plan.qty, customer)}) else: - items.append({'item_code': subscription_plan.item, 'qty': plan.qty, 'rate': (subscription_plan.get_plan_rate(customer) * prorate_factor)}) + items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)}) return items diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index da16ee0e8a..d3fef6023b 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -15,14 +15,20 @@ class SubscriptionPlan(Document): if self.billing_interval_count < 1: frappe.throw('Billing Interval Count cannot be less than 1') - def get_plan_rate(self, quantity=1, customer=None): - if self.price_determination == "Fixed rate": - return self.cost +@frappe.whitelist() +def get_plan_rate(plan, quantity=1, customer=None): + plan = frappe.get_doc("Subscription Plan", plan) + if plan.price_determination == "Fixed rate": + return plan.cost - elif self.price_determination == "Based on price list": - if customer: - customer_group = frappe.db.get_value("Customer", customer, "customer_group") - else: - customer_group = None - - return get_price(item_code=self.item, price_list=self.price_list, customer_group=customer_group, company=None, qty=quantity).price_list_rate + elif plan.price_determination == "Based on price list": + if customer: + customer_group = frappe.db.get_value("Customer", customer, "customer_group") + else: + customer_group = None + + price = get_price(item_code=plan.item, price_list=plan.price_list, customer_group=customer_group, company=None, qty=quantity) + if not price: + return 0 + else: + return price.price_list_rate From 7722b81c8e92af726f8f827c4c69452b732da0a1 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Thu, 5 Jul 2018 21:05:52 +0000 Subject: [PATCH 22/24] Variable correction --- erpnext/accounts/doctype/payment_request/payment_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 7f6df77129..c58b185a08 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -42,7 +42,7 @@ class PaymentRequest(Document): for subscription_plan in self.subscription_plans: payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway") if payment_gateway != self.payment_gateway_account: - frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(plan.name))) + frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(subscription_plan.name))) rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty) From c047bf88db737bf7ec8f2fa70a72090a1c0aee43 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 13 Jul 2018 13:25:53 +0000 Subject: [PATCH 23/24] Rebase following Subscription corrections --- .../doctype/subscription/subscription.json | 1815 ++++++++--------- .../doctype/subscription/subscription.py | 2 + 2 files changed, 893 insertions(+), 924 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 70fa2c7bb1..a58ac3e68a 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -1,925 +1,892 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "SUBC.####", - "beta": 0, - "creation": "2017-07-18 17:50:43.967266", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscriber", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Subscriber", - "length": 0, - "no_copy": 0, - "options": "Subscriber", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "quantity", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscription_period", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subscription Period", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subscription Start Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cancelation_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cancelation Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "trial_period_start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Trial Period Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.trial_period_start", - "fieldname": "trial_period_end", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Trial Period End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_invoice_start", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Current Invoice Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_invoice_end", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Current Invoice End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "description": "Number of days that the subscriber has to pay invoices generated by this subscription", - "fieldname": "days_until_due", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Days Until Due", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cancel_at_period_end", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Cancel At End Of Period", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "sb_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Plans", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "plans", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Plans", - "length": 0, - "no_copy": 0, - "options": "Subscription Plan Detail", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "sb_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discounts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "apply_additional_discount", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Additional Discount On", - "length": 0, - "no_copy": 0, - "options": "\nGrand Total\nNet total", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "cb_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "additional_discount_percentage", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional DIscount Percentage", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "", - "fieldname": "additional_discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional DIscount Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.invoices", - "fieldname": "sb_3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Invoices", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "", - "fieldname": "invoices", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Invoices", - "length": 0, - "no_copy": 0, - "options": "Subscription Invoice", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-11 19:34:44.582203", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Subscription", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 0 - } \ No newline at end of file + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "SUBC.####", + "beta": 0, + "creation": "2017-07-18 17:50:43.967266", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscriber", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Subscriber", + "length": 0, + "no_copy": 0, + "options": "Subscriber", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_period", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Period", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Start Date", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cancelation_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Cancelation Date", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "trial_period_start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Trial Period Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.trial_period_start", + "fieldname": "trial_period_end", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Trial Period End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_invoice_start", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Invoice Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_invoice_end", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Current Invoice End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "Number of days that the subscriber has to pay invoices generated by this subscription", + "fieldname": "days_until_due", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Days Until Due", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cancel_at_period_end", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Cancel At End Of Period", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "sb_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Plans", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "plans", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Plans", + "length": 0, + "no_copy": 0, + "options": "Subscription Plan Detail", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sb_1", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Taxes", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tax_template", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sales Taxes and Charges Template", + "length": 0, + "no_copy": 0, + "options": "Sales Taxes and Charges Template", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "", + "fieldname": "sb_2", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Discounts", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "apply_additional_discount", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Apply Additional Discount On", + "length": 0, + "no_copy": 0, + "options": "\nGrand Total\nNet total", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "cb_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "additional_discount_percentage", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Additional DIscount Percentage", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "", + "fieldname": "additional_discount_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Additional DIscount Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:doc.invoices", + "fieldname": "sb_3", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "", + "fieldname": "invoices", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices", + "length": 0, + "no_copy": 0, + "options": "Subscription Invoice", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-07-13 15:18:49.016010", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Subscription", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index fe39161960..785a386d25 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -305,6 +305,8 @@ class Subscription(Document): else: items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)}) + frappe.log_error(items) + return items def process(self): From 3fa3104dccdeb3e1dc39b6e50b2fe69105daca9b Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Fri, 13 Jul 2018 14:00:42 +0000 Subject: [PATCH 24/24] Debug cleanup --- erpnext/accounts/doctype/subscription/subscription.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 785a386d25..fe39161960 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -305,8 +305,6 @@ class Subscription(Document): else: items.append({'item_code': item_code, 'qty': plan.qty, 'rate': (get_plan_rate(plan.plan, plan.qty, customer) * prorate_factor)}) - frappe.log_error(items) - return items def process(self):