From c583ad72d161438f16ce9a579fc00a7756a300b0 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 10 Mar 2026 18:15:36 +0530 Subject: [PATCH 1/5] fix: send payment reminders for incomplete batch payments only --- frontend/yarn.lock | 66 ++-------------------- lms/lms/doctype/lms_payment/lms_payment.py | 6 +- 2 files changed, 10 insertions(+), 62 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8cf99798..c190bd0c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1275,11 +1275,6 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== -"@kurkle/color@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf" - integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== - "@lezer/common@^1.0.0", "@lezer/common@^1.0.2", "@lezer/common@^1.1.0", "@lezer/common@^1.2.0", "@lezer/common@^1.2.1", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0": version "1.5.1" resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.1.tgz#6e8c114ff5d36a41148e146a253734d3bb8807d3" @@ -1574,33 +1569,6 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" -"@svgdotjs/svg.draggable.js@^3.0.4": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz#bca1065ec27b1dbae5a92a0558777ed964a395cb" - integrity sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA== - -"@svgdotjs/svg.filter.js@^3.0.8": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz#758e336b79e73a6797358d655b60842131a9a52b" - integrity sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw== - dependencies: - "@svgdotjs/svg.js" "^3.2.4" - -"@svgdotjs/svg.js@^3.2.4": - version "3.2.5" - resolved "https://registry.yarnpkg.com/@svgdotjs/svg.js/-/svg.js-3.2.5.tgz#fbbc56728b2b565f3ae3e3713301ff82abbb45bd" - integrity sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ== - -"@svgdotjs/svg.resize.js@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz#732e4cae15d09ad3021adeac63bc9fad0dc7255a" - integrity sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA== - -"@svgdotjs/svg.select.js@^4.0.1": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz#6af12755fd71caf703825d4f490fdf02a001cbfc" - integrity sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg== - "@swc/helpers@^0.5.0": version "0.5.18" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.18.tgz#feeeabea0d10106ee25aaf900165df911ab6d3b1" @@ -2105,11 +2073,6 @@ resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-14.2.1.tgz#829a271147937f6b105bb1422d3171e6142f47ba" integrity sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw== -"@yr/monotone-cubic-spline@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9" - integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA== - ace-builds@1.36.2: version "1.36.2" resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.36.2.tgz#9499bd59e839a335ac4850e74549ca8d849dc554" @@ -2155,18 +2118,6 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -apexcharts@4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-4.3.0.tgz#eccf28e830ce1b5e018cfc0e99d1c6af0076c9c7" - integrity sha512-PfvZQpv91T68hzry9l5zP3Gip7sQvF0nFK91uCBrswIKX7rbIdbVNS4fOks9m9yP3Ppgs6LHgU2M/mjoG4NM0A== - dependencies: - "@svgdotjs/svg.draggable.js" "^3.0.4" - "@svgdotjs/svg.filter.js" "^3.0.8" - "@svgdotjs/svg.js" "^3.2.4" - "@svgdotjs/svg.resize.js" "^2.0.2" - "@svgdotjs/svg.select.js" "^4.0.1" - "@yr/monotone-cubic-spline" "^1.0.3" - arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" @@ -2374,13 +2325,6 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chart.js@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.1.tgz#ac5dc0e69a7758909158a96fe80ce43b3bb96a9f" - integrity sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg== - dependencies: - "@kurkle/color" "^0.3.0" - chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -2962,10 +2906,10 @@ fraction.js@^4.1.2: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -frappe-ui@^0.1.261: - version "0.1.262" - resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.262.tgz#b88416aac76fdce183f95f2ac935ce65ffb2b2eb" - integrity sha512-KKH7LLLa3yvfM3QeLaiaSqeSS+BUdCu3+8BrvXAG09ejGU1Z6k0wXEG314J0yXxK4rLifd83zpKbtUCG9zIKFA== +frappe-ui@^0.1.264: + version "0.1.265" + resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.265.tgz#3aee2dd5593fb36f9f2d7eeb3c92246abaa2a974" + integrity sha512-N/4LXMcFAzoTfMWqap9e2rxxMKShVWnX63+Q+48CBbt2sfjeqR/4P7HRjXi7DUCEMfRt4xkv/1eIuBdNAuIT1A== dependencies: "@floating-ui/dom" "^1.7.4" "@floating-ui/vue" "^1.1.6" @@ -4324,7 +4268,7 @@ regjsparser@^0.13.0: dependencies: jsesc "~3.1.0" -reka-ui@^2.5.0, reka-ui@^2.8.0: +reka-ui@^2.5.0: version "2.8.0" resolved "https://registry.yarnpkg.com/reka-ui/-/reka-ui-2.8.0.tgz#612023ad40c5c10999aef304f2b828cdd08da6a8" integrity sha512-N4JOyIrmDE7w2i06WytqcV2QICubtS2PsK5Uo8FIMAgmO13KhUAgAByP26cXjjm2oF/w7rTyRs8YaqtvaBT+SA== diff --git a/lms/lms/doctype/lms_payment/lms_payment.py b/lms/lms/doctype/lms_payment/lms_payment.py index e38cccf4..db764ef3 100644 --- a/lms/lms/doctype/lms_payment/lms_payment.py +++ b/lms/lms/doctype/lms_payment/lms_payment.py @@ -24,7 +24,11 @@ def send_payment_reminder(): incomplete_payments = frappe.get_all( "LMS Payment", - {"payment_received": 0, "creation": [">", add_days(nowdate(), -1)]}, + { + "payment_received": 0, + "creation": [">", add_days(nowdate(), -1)], + "payment_for_document_type": "LMS Batch", + }, [ "name", "member", From d68a362115bb7b4615342db25c882de311566475 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 10 Mar 2026 18:44:28 +0530 Subject: [PATCH 2/5] feat: settings for payment reminders --- frontend/src/components/Settings/Settings.vue | 49 ++++++++++++++++--- lms/lms/doctype/lms_payment/lms_payment.py | 16 +++++- .../doctype/lms_settings/lms_settings.json | 18 ++++++- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Settings/Settings.vue b/frontend/src/components/Settings/Settings.vue index 4275642d..af89b212 100644 --- a/frontend/src/components/Settings/Settings.vue +++ b/frontend/src/components/Settings/Settings.vue @@ -331,29 +331,62 @@ const tabsStructure = computed(() => { doctype: 'Currency', }, { - label: 'Payment Gateway', - name: 'payment_gateway', - type: 'Link', - doctype: 'Payment Gateway', + label: 'Show USD equivalent amount', + name: 'show_usd_equivalent', + type: 'checkbox', + description: + 'If enabled, it shows the USD equivalent amount for all transactions based on the current exchange rate.', + }, + { + label: 'Apply rounding on equivalent', + name: 'apply_rounding', + type: 'checkbox', + description: + 'If enabled, it applies rounding on the USD equivalent amount.', }, ], }, { fields: [ + { + label: 'Payment Gateway', + name: 'payment_gateway', + type: 'Link', + doctype: 'Payment Gateway', + }, { label: 'Apply GST for India', name: 'apply_gst', type: 'checkbox', + description: + 'If enabled, GST will be applied to the price for students from India.', }, + ], + }, + ], + }, + { + label: 'Payment Reminders', + columns: [ + { + fields: [ { - label: 'Show USD equivalent amount', - name: 'show_usd_equivalent', + label: 'Send payment reminders for batch', + name: 'send_payment_reminders_for_batch', type: 'checkbox', + description: + 'If enabled, it sends payment reminders to students who left the payment incomplete for a batch.', }, + ], + }, + { + fields: [ { - label: 'Apply rounding on equivalent', - name: 'apply_rounding', + label: 'Send payment reminders for course', + name: 'send_payment_reminders_for_course', type: 'checkbox', + description: + 'If enabled, it sends payment reminders to students who left the payment incomplete for a course.', }, ], }, diff --git a/lms/lms/doctype/lms_payment/lms_payment.py b/lms/lms/doctype/lms_payment/lms_payment.py index db764ef3..5af94295 100644 --- a/lms/lms/doctype/lms_payment/lms_payment.py +++ b/lms/lms/doctype/lms_payment/lms_payment.py @@ -27,7 +27,7 @@ def send_payment_reminder(): { "payment_received": 0, "creation": [">", add_days(nowdate(), -1)], - "payment_for_document_type": "LMS Batch", + "payment_for_document_type": ["in", allowed_payment_types()], }, [ "name", @@ -48,6 +48,20 @@ def send_payment_reminder(): send_mail(payment) +def allowed_payment_types(): + send_batch_reminders = frappe.db.get_single_value("LMS Settings", "send_payment_reminders_for_batch") + send_course_reminders = frappe.db.get_single_value("LMS Settings", "send_payment_reminders_for_course") + + allowed_types = [] + if send_batch_reminders: + allowed_types.append("LMS Batch") + + if send_course_reminders: + allowed_types.append("LMS Course") + + return allowed_types + + def has_paid_later(payment): return frappe.db.exists( "LMS Payment", diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index f77405f6..3bdce8ee 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -62,6 +62,8 @@ "apply_gst", "show_usd_equivalent", "apply_rounding", + "send_payment_reminders_for_batch", + "send_payment_reminders_for_course", "no_payments_app", "payments_app_is_not_installed", "email_templates_tab", @@ -492,14 +494,26 @@ "fieldtype": "Check", "label": "Demo Data Present", "read_only": 1 + }, + { + "default": "0", + "fieldname": "send_payment_reminders_for_batch", + "fieldtype": "Check", + "label": "Send Payment Reminders for Batch" + }, + { + "default": "0", + "fieldname": "send_payment_reminders_for_course", + "fieldtype": "Check", + "label": "Send Payment Reminders for Course" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-03-05 13:57:56.303744", - "modified_by": "Administrator", + "modified": "2026-03-10 18:18:51.733955", + "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Settings", "owner": "Administrator", From 9820db329e1a6e7be06ec7247cdd1f57ab900f5d Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 11 Mar 2026 11:50:05 +0530 Subject: [PATCH 3/5] fix: course deletion issues --- lms/lms/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lms/lms/api.py b/lms/lms/api.py index 314b77bb..527914e0 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -885,8 +885,10 @@ def delete_course(course: str): frappe.db.delete("LMS Enrollment", {"course": course}) frappe.db.delete("LMS Course Progress", {"course": course}) + frappe.db.delete("LMS Certificate", {"course": course}) + frappe.db.delete("Batch Course", {"course": course}) frappe.db.delete("LMS Course Review", {"course": course}) - frappe.db.set_value("LMS Quiz", {"course": course}, "course", None) + frappe.db.set_value("LMS Quiz", {"course": course}, {"course": None, "lesson": None}) frappe.db.set_value("LMS Quiz Submission", {"course": course}, "course", None) chapters = frappe.get_all("Course Chapter", {"course": course}, pluck="name") From e90a730a29dce38f86d597cd0ee9622991287891 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 11 Mar 2026 12:34:05 +0530 Subject: [PATCH 4/5] test: delete course from cypress test after all operations are complete --- cypress/e2e/course_creation.cy.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index b7a67bc9..0ed474b3 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -22,6 +22,7 @@ describe("Course Creation", () => { ); cy.fixture("profile.png", "base64").then((fileContent) => { + expect(fileContent).to.exist; cy.get("div") .contains("Course Image") .siblings("div") @@ -74,10 +75,10 @@ describe("Course Creation", () => { cy.button("Save").click(); // Add Chapter - cy.wait(1000); + cy.wait(500); cy.button("Add").click(); - cy.wait(1000); + cy.wait(500); cy.get("[data-dismissable-layer]") .should("be.visible") .within(() => { @@ -86,12 +87,10 @@ describe("Course Creation", () => { }); // Add Lesson - cy.wait(1000); + cy.wait(500); cy.button("Add Lesson").click(); - cy.wait(1000); + cy.wait(500); cy.url().should("include", "/learn/1-1/edit"); - cy.wait(1000); - cy.get("label").contains("Title").type("Test Lesson"); cy.get("#content .ce-block").type( "{enter}This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now." @@ -99,7 +98,7 @@ describe("Course Creation", () => { cy.button("Save").click(); // View Course - cy.wait(1000); + cy.wait(500); cy.visit("/lms/courses"); cy.closeOnboardingModal(); @@ -133,7 +132,7 @@ describe("Course Creation", () => { cy.get("[id^=headlessui-disclosure-panel-").within(() => { cy.get("div").contains("Test Lesson").click(); }); - cy.wait(3000); + cy.wait(500); // View Lesson cy.url().should("include", "/learn/1-1"); @@ -166,5 +165,16 @@ describe("Course Creation", () => { cy.get("div").contains( "This is a test comment. This will check if the UI is working properly." ); + + // Delete Course + cy.get("div").contains("Test Course").click(); + cy.get("button").contains("Settings").click(); + cy.get("header").within(() => { + cy.get("svg.lucide.lucide-trash2-icon").click(); + }); + cy.get("span").contains("Delete").click(); + cy.wait(500); + cy.url().should("include", "/lms/courses"); + cy.get("div").contains("Test Course").should("not.exist"); }); }); From 1661389b07ec91cfef7f43f9f32e9ac60d435fd2 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 11 Mar 2026 12:53:01 +0530 Subject: [PATCH 5/5] test: fixed course image issue --- cypress/e2e/course_creation.cy.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 0ed474b3..8a8e0f2d 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -21,11 +21,17 @@ describe("Course Creation", () => { "Test Course Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now." ); - cy.fixture("profile.png", "base64").then((fileContent) => { + cy.contains("Course Image") + .should("exist") + .parent() + .find('input[type="file"]') + .attachFile("profile.png", { force: true }); + + /* cy.fixture("profile.png", "base64").then((fileContent) => { expect(fileContent).to.exist; cy.get("div") .contains("Course Image") - .siblings("div") + .parent() .children('input[type="file"]') .attachFile({ fileContent, @@ -33,7 +39,7 @@ describe("Course Creation", () => { mimeType: "image/png", encoding: "base64", }); - }); + }); */ /* Instructor */ cy.get("label")