<?xml version="1.0"?>
<rss version="2.0">
   <channel>
      <title>File manager by Trương Khánh</title>
      <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h</link>
      <description>A place to save, organize, and share your files. </description>
      <language>en-us</language>
      <pubDate>2025-04-21 07:09:29 UTC</pubDate>
      <lastBuildDate>2025-06-06 04:57:35 UTC</lastBuildDate>
      <webMaster>hello@padlet.com</webMaster>
      <image>
         <url>https://padlet.net/icons/png/1f5c3.png</url>
      </image>
      <item>
         <title>def get_students(academic_year, group_based_on, academic_term=None, program=None, batch=None, student_category=None, course=None):</title>
         <author></author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3417380113</link>
         <description><![CDATA[<p>	filters = {</p><p>		"enabled": 1,</p><p>		"academic_year": academic_year</p><p>	}</p><p>	if student_category:</p><p>		filters["student_category"] = student_category</p><p><br></p><p>	students = frappe.get_all("Student", filters=filters, fields=["name", "student_name"])</p><p>	student_list = []</p><p>	for s in students:</p><p>		s.update({</p><p>			"student": s.pop("name"),  # Đổi key name thành student</p><p>			"active": 1</p><p>		})</p><p>		student_list.append(s)</p><p>	return student_list</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-21 07:28:54 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3417380113</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3417419660</link>
         <description><![CDATA[]]></description>
         <enclosure url="https://docs.google.com/spreadsheets/d/1YXjkPSMO7Qw3nLqKgaD_cKR--aYAy87ut9jaLZagtLY/edit?usp=sharing" />
         <pubDate>2025-04-21 08:08:02 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3417419660</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3419252560</link>
         <description><![CDATA[<p># Server Script: Auto apply Fee Discount</p><p>student = frappe.get_doc("Student", doc.student)</p><p>entry_date = student.enrollment_date</p><p>education_level = <a rel="noopener noreferrer nofollow" href="http://student.education">student.education</a>_level</p><p>payment_type = doc.payment_type</p><p>entry_time = "After 3/2022" if entry_date &gt; frappe.utils.getdate("2022-03-31") else "Before 3/2022"</p><p>policy = frappe.get_all("Fee Discount Policy", filters={</p><p>    "entry_time": entry_time,</p><p>    "payment_type": payment_type,</p><p>    "education_level": education_level,</p><p>    "valid_from": ["&lt;=", frappe.utils.nowdate()],</p><p>    "valid_to": ["&gt;=", frappe.utils.nowdate()]</p><p>}, fields=["name", "discount_percent"], limit=1)</p><p>if policy:</p><p>    discount = policy[0]["discount_percent"]</p><p>    total = 0</p><p>    for component in doc.components:</p><p>        component.amount = component.amount * (1 - discount / 100)</p><p>        total += component.amount</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.final">doc.final</a>_fee_amount = total</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy = policy[0]["name"]</p><p><br></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-22 08:05:55 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3419252560</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3422617658</link>
         <description><![CDATA[<p># Auto approve nếu chưa</p><p>if doc.application_status != "Approved":</p><p>    doc.application_status = "Approved"</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p># Kiểm tra đã có Student chưa</p><p>if not doc.student:</p><p>    # Tạo Student</p><p>    student = <a rel="noopener noreferrer nofollow" href="http://frappe.new">frappe.new</a>_doc("Student")</p><p>    student.first_name = doc.first_name</p><p>    student.last_name = doc.last_name</p><p>    <a rel="noopener noreferrer nofollow" href="http://student.date">student.date</a>_of_birth = <a rel="noopener noreferrer nofollow" href="http://doc.date">doc.date</a>_of_birth</p><p>    student.gender = doc.gender</p><p>    <a rel="noopener noreferrer nofollow" href="http://student.email">student.email</a> = <a rel="noopener noreferrer nofollow" href="http://doc.email">doc.email</a>_id</p><p>    student.student_batch = doc.student_batch</p><p>    student.append("guardians", {</p><p>        "guardian": doc.guardian_name</p><p>    })</p><p>    <a rel="noopener noreferrer nofollow" href="http://student.save">student.save</a>()</p><p>    # Gán lại student_id vào applicant</p><p>    doc.student = <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a></p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p>    # Tự động enroll vào các lớp theo batch</p><p>    if doc.student_batch:</p><p>        student_groups = frappe.get_all(</p><p>            "Student Group",</p><p>            filters={"student_batch": doc.student_batch},</p><p>            fields=["name"]</p><p>        )</p><p>        for group in student_groups:</p><p>            group_doc = frappe.get_doc("Student Group", <a rel="noopener noreferrer nofollow" href="http://group.name">group.name</a>)</p><p>            if not any(s.student == <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a> for s in group_doc.students):</p><p>                group_doc.append("students", {"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>})</p><p>                group_<a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p><br></p><p><br></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-24 03:25:22 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3422617658</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3422969383</link>
         <description><![CDATA[<p>base_url = frappe.utils.get_url()</p><p>accept_url = f"{base_url}/api/method/<a rel="noopener noreferrer nofollow" href="http://your.custom.app">your.custom.app</a>.accept_applicant?token={doc.parent_response_token}"</p><p>reject_url = f"{base_url}/api/method/<a rel="noopener noreferrer nofollow" href="http://your.custom.app">your.custom.app</a>.reject_applicant?token={doc.parent_response_token}"</p><p># Gửi email</p><p>frappe.sendmail(</p><p>    recipients=[<a rel="noopener noreferrer nofollow" href="http://doc.email">doc.email</a>_id],</p><p>    subject="Xác nhận nhập học",</p><p>    message=f"""</p><p>        Kính gửi quý phụ huynh,&lt;br&gt;&lt;br&gt;</p><p>        Học sinh &lt;b&gt;{doc.student_name}&lt;/b&gt; đã hoàn thành bài kiểm tra đầu vào.&lt;br&gt;&lt;br&gt;</p><p>        Quý phụ huynh vui lòng xác nhận:&lt;br&gt;&lt;br&gt;</p><p>        ✅ &lt;a href="{accept_url}"&gt;Đồng ý nhập học&lt;/a&gt;&lt;br&gt;</p><p>        ❌ &lt;a href="{reject_url}"&gt;Không đồng ý&lt;/a&gt;&lt;br&gt;&lt;br&gt;</p><p>        Xin cảm ơn!</p><p>    """</p><p>)</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-24 07:13:31 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3422969383</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3423032417</link>
         <description><![CDATA[<p>Kính gửi quý phụ huynh,&lt;br&gt;&lt;br&gt;</p><p>Học sinh &lt;b&gt;{{ doc.student_applicant.student_name }}&lt;/b&gt; đã vượt qua bài kiểm tra đầu vào.&lt;br&gt;&lt;br&gt;</p><p>Vui lòng xác nhận nhập học:&lt;br&gt;&lt;br&gt;</p><p>✅ &lt;a href="{{ frappe.utils.get_url() }}/api/method/your_app.api.accept_applicant?token={{ doc.student_applicant.parent_response_token }}"&gt;Đồng ý nhập học&lt;/a&gt;&lt;br&gt;</p><p>❌ &lt;a href="{{ frappe.utils.get_url() }}/api/method/your_app.api.reject_applicant?token={{ doc.student_applicant.parent_response_token }}"&gt;Không đồng ý nhập học&lt;/a&gt;&lt;br&gt;&lt;br&gt;</p><p>Xin cảm ơn!</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-24 08:01:46 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3423032417</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3423113451</link>
         <description><![CDATA[<p>import frappe</p><p>@frappe.whitelist(allow_guest=True)</p><p>def accept_applicant(token):</p><p>    applicant = frappe.get_all("Student Applicant", filters={"parent_response_token": token}, limit=1)</p><p>    if not applicant:</p><p>        return "Invalid or expired token."</p><p>    doc = frappe.get_doc("Student Applicant", applicant[0].name)</p><p>    doc.parent_response_status = "Accepted"</p><p>    doc.application_status = "Approved"</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p>    return "✅ Phụ huynh đã đồng ý nhập học. Trạng thái đã được cập nhật."</p><p>@frappe.whitelist(allow_guest=True)</p><p>def reject_applicant(token):</p><p>    applicant = frappe.get_all("Student Applicant", filters={"parent_response_token": token}, limit=1)</p><p>    if not applicant:</p><p>        return "Invalid or expired token."</p><p>    doc = frappe.get_doc("Student Applicant", applicant[0].name)</p><p>    doc.parent_response_status = "Rejected"</p><p>    doc.application_status = "Rejected"</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p>    return "❌ Phụ huynh đã từ chối nhập học. Trạng thái đã được cập nhật."</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-24 09:12:17 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3423113451</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424216155</link>
         <description><![CDATA[<p># HTML cảm ơn</p><p>    html = f"""</p><p>    &lt;html&gt;</p><p>    &lt;head&gt;&lt;title&gt;Đồng ý nhập học&lt;/title&gt;&lt;/head&gt;</p><p>    &lt;body style="font-family: sans-serif; text-align: center; padding-top: 100px;"&gt;</p><p>        &lt;h1&gt;🎉 Cảm ơn bạn!&lt;/h1&gt;</p><p>        &lt;p&gt;Học sinh &lt;strong&gt;{doc.first_name} {doc.last_name or ""}&lt;/strong&gt; đã được đồng ý nhập học.&lt;/p&gt;</p><p>        &lt;p&gt;Chúng tôi sẽ sớm liên hệ lại với bạn.&lt;/p&gt;</p><p>    &lt;/body&gt;</p><p>    &lt;/html&gt;</p><p>    """</p><p>    return html</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-25 01:54:33 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424216155</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424280979</link>
         <description><![CDATA[<p>&lt;a href="{{ frappe.utils.get_url() }}/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.api.accept_applicant?token={{ doc.custom_response_token }}"&gt;Đồng ý&lt;/a&gt;</p><p>&lt;a href="{{ frappe.utils.get_url() }}/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.api.reject_applicant?token={{ doc.custom_response_token }}"&gt;Từ chối&lt;/a&gt;</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-25 02:29:08 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424280979</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424463611</link>
         <description><![CDATA[<pre><code>.exceptions.UpdateAfterSubmitError:  Not allowed to change Parent Response status after submission from Pending to Accepted</code></pre>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-25 04:32:25 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424463611</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424490145</link>
         <description><![CDATA[<p><br/></p><p>@frappe.whitelist(allow_guest=True)</p><p>def accept_applicant(token):</p><p>    applicant_list = frappe.get_all(</p><p>        "Student Applicant",</p><p>        filters={"parent_response_token": token},</p><p>        fields=["name"],</p><p>        limit=1</p><p>    )</p><p>    if not applicant_list:</p><p>        return frappe.respond_as_web_page(</p><p>            title="Lỗi xác nhận",</p><p>            html="&lt;h2&gt;🚫 Token không hợp lệ hoặc đã hết hạn.&lt;/h2&gt;",</p><p>            http_status_code=400</p><p>        )</p><p>    doc = frappe.get_doc("Student Applicant", applicant_list[0]["name"])</p><p>    if doc.docstatus != 0:</p><p>        return frappe.respond_as_web_page(</p><p>            title="Không thể cập nhật",</p><p>            html="&lt;h2&gt;⚠️ Hồ sơ đã được xử lý trước đó.&lt;/h2&gt;"</p><p>        )</p><p>    doc.parent_response_status = "Accepted"</p><p>    doc.application_status = "Approved"</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p>    doc.submit()</p><p>    return frappe.respond_as_web_page(</p><p>        title="Xác nhận thành công",</p><p>        html="&lt;h1&gt;✅ Cảm ơn bạn đã xác nhận!&lt;/h1&gt;&lt;p&gt;Hồ sơ của bạn đã được duyệt và chuyển sang bước tiếp theo.&lt;/p&gt;"</p><p>    )</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-25 04:52:45 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424490145</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424722744</link>
         <description><![CDATA[<p>doc.cancel()         # Hủy submit</p><p>    doc.some_field = "New value"</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>()</p><p>    doc.submit() </p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-25 08:07:40 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3424722744</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3425607104</link>
         <description><![CDATA[<p>if not selected_class:</p><p>        return frappe.respond_as_web_page(</p><p>            title="Không còn lớp trống",</p><p>            html="&lt;h3&gt;Rất tiếc, tất cả các lớp đã đầy. Nhà trường sẽ liên hệ với quý phụ huynh sau.&lt;/h3&gt;"</p><p>        )</p><p>    # Ghi danh vào lớp</p><p>    enrollment = frappe.get_doc({</p><p>        "doctype": "Student Group Student",</p><p>        "student_group": selected_class,</p><p>        "student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a></p><p>    })</p><p>    enrollment.insert(ignore_permissions=True)</p><p>    # Gửi email xác nhận</p><p>    frappe.sendmail(</p><p>        recipients=[<a rel="noopener noreferrer nofollow" href="http://doc.email">doc.email</a>_id],</p><p>        subject="✅ Xác nhận ghi danh thành công",</p><p>        message=f"""</p><p>            &lt;h2&gt;🎉 Xin chúc mừng!&lt;/h2&gt;</p><p>            &lt;p&gt;Học sinh &lt;strong&gt;{student.student_name}&lt;/strong&gt; đã được ghi danh thành công.&lt;/p&gt;</p><p>            &lt;ul&gt;</p><p>                &lt;li&gt;&lt;b&gt;Mã học sinh:&lt;/b&gt; {<a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>}&lt;/li&gt;</p><p>                &lt;li&gt;&lt;b&gt;Khối:&lt;/b&gt; {student.student_batch}&lt;/li&gt;</p><p>                &lt;li&gt;&lt;b&gt;Lớp:&lt;/b&gt; {selected_class}&lt;/li&gt;</p><p>            &lt;/ul&gt;</p><p>            &lt;p&gt;Nhà trường sẽ sớm liên hệ để hướng dẫn các bước tiếp theo.&lt;/p&gt;</p><p>        """</p><p>    )</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-26 02:46:48 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3425607104</guid>
      </item>
      <item>
         <title># 📌 Cách đúng: Mở Student Group và append vào child table students</title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3425654622</link>
         <description><![CDATA[<p>    student_group = frappe.get_doc("Student Group", selected_class)</p><p>    student_group.append("students", {</p><p>        "student": student.name,</p><p>        "student_name": student.student_name</p><p>    })</p><p>    student_group.save(ignore_permissions=True)</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-26 04:56:10 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3425654622</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3426211379</link>
         <description><![CDATA[# 5. Enroll Student vào Student Batch
    student_batch_student = frappe.get_doc({
        "doctype": "Student Batch Student",
        "student_batch": doc.student_batch,
        "student": student.name,
        "student_name": student.student_name
    })
    student_batch_student.insert(ignore_permissions=True)

    # 6. Tìm lớp phù hợp (Student Group)
    class_list = frappe.get_all(
        "Student Group",
        filters={"student_batch": doc.student_batch},
        fields=["name"],
        order_by="creation asc"
    )]]></description>
         <enclosure url="" />
         <pubDate>2025-04-27 02:24:52 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3426211379</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3427752157</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p><br></p><p>def accept_applicant(token):</p><p><br></p><p>applicant_list = frappe.get_all("Student Applicant",</p><p>filters={"response_token": token},</p><p>fields=["name"],</p><p>limit=1</p><p>    )</p><p><br></p><p>if not applicant_list:</p><p><br></p><p>return frappe.respond_as_web_page(</p><p><br></p><p>title="Lỗi xác nhận",</p><p><br></p><p>html="&lt;h2&gt;🚫 Token không hợp lệ hoặc đã hết hạn.&lt;/h2&gt;",</p><p><br></p><p>http_status_code=400</p><p><br></p><p>        )</p><p><br></p><p>doc = frappe.get_doc("Student Applicant", applicant_list[0].name)</p><p>if doc.docstatus == 1:   </p><p>doc.flags.ignore_submit = True</p><p>doc.db_set("parent_response_status", "Accepted", update_modified=False)</p><p><br></p><p>doc.db_set("application_status","Approved",update_modified=False)</p><p><a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>(ignore_permissions = True)</p><p>frappe.db.commit()</p><p>#doc.submit()</p><p><br></p><p>student = frappe.get_doc({</p><p>"doctype" : "Student" ,</p><p>"first_name" : doc.first_name,</p><p>"student_applicant" : <a rel="noopener noreferrer nofollow" href="http://doc.name">doc.name</a> ,</p><p>"batch" : doc.student_batch,</p><p>"enabled" : 1</p><p>       })   </p><p>student.insert(ignore_permissions= True)</p><p><a rel="noopener noreferrer nofollow" href="http://student.save">student.save</a>()</p><p>program_enrollment = frappe.get_all(</p><p>"Program Enrollment",</p><p>filters={"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>},</p><p>limit= 1</p><p>    )</p><p>program_name = doc.program or "Default Program"</p><p>if not program_enrollment: </p><p>program_enrollment= frappe.get_doc({</p><p>"doctype": "Program Enrollment",</p><p>"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>,</p><p>"student_batch_name" : doc.student_batch,</p><p>"program" : program_name,</p><p>"academic_year" : doc.academic_year,</p><p>"status": "Active"</p><p>     })</p><p>program_enrollment.insert(ignore_permissions=True)</p><p>program_enrollment.submit()</p><p>class_list = frappe.get_all("Student Group",</p><p>filters ={"batch": doc.student_batch},</p><p>fields=["name"],</p><p>order_by ="creation asc")</p><p><br><br></p><p>selected_class = None</p><p>for group in class_list:</p><p>current_count = frappe.db.count("Student Group Student", </p><p>                                           {"parent": group["name"], </p><p>"parenttype": "Student Group" })</p><p>max_limit = frappe.db.get_value("Student Group", group["name"], "max_strength") or 30</p><p>if current_count &lt; max_limit:</p><p>selected_class = group["name"]</p><p>break</p><p>if not selected_class:</p><p>return frappe.respond_as_web_page(</p><p><br></p><p>title="Không còn lớp trống",</p><p><br></p><p>html="&lt;h3&gt;Rất tiếc, tất cả các lớp đã đầy. Nhà trường sẽ liên hệ với quý phụ huynh sau.&lt;/h3&gt;"</p><p><br></p><p>        )</p><p><br></p><p>student_group = frappe.get_doc("Student Group", selected_class)</p><p>student_group.append("students", {</p><p>"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>,</p><p>"student_name": student.student_name,</p><p>    })</p><p># student_group.add_student(<a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>)</p><p>student_<a rel="noopener noreferrer nofollow" href="http://group.save">group.save</a>(ignore_permissions=True)</p><p># # Gửi email xác nhận</p><p><br></p><p>frappe.sendmail(</p><p><br></p><p>recipients=[doc.student_email_id],</p><p><br></p><p>subject="✅ Xác nhận ghi danh thành công",</p><p><br></p><p>message=f"""</p><p><br></p><p>            &lt;h2&gt;🎉 Xin chúc mừng!&lt;/h2&gt;</p><p><br></p><p>            &lt;p&gt;Học sinh &lt;strong&gt;{student.first_name}&lt;/strong&gt; đã được ghi danh thành công.&lt;/p&gt;</p><p><br></p><p>            &lt;ul&gt;</p><p><br></p><p>                &lt;li&gt;&lt;b&gt;Mã học sinh:&lt;/b&gt; {<a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>}&lt;/li&gt;</p><p><br></p><p>                &lt;li&gt;&lt;b&gt;Khối:&lt;/b&gt; {student.batch}&lt;/li&gt;</p><p><br></p><p>                &lt;li&gt;&lt;b&gt;Lớp:&lt;/b&gt; {selected_class}&lt;/li&gt;</p><p><br></p><p>            &lt;/ul&gt;</p><p><br></p><p>            &lt;p&gt;Nhà trường sẽ sớm liên hệ để hướng dẫn các bước tiếp theo.&lt;/p&gt;</p><p><br></p><p>        """</p><p>    )  </p><p>return frappe.respond_as_web_page(</p><p><br></p><p>title="Không thể cập nhật",</p><p><br></p><p>html="&lt;h2&gt;⚠️ Hồ sơ đã được xử lý trước đó.&lt;/h2&gt;"</p><p>    )</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-04-28 09:19:16 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3427752157</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3444128556</link>
         <description><![CDATA[<p>def generate_password():</p><p>    chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"</p><p>    password = ""</p><p>    from random import randint</p><p>    for i in range(8):</p><p>        password += chars[randint(0, len(chars) - 1)]</p><p>    return password</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-10 01:49:00 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3444128556</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3446044019</link>
         <description><![CDATA[<p>for s in students:</p><p>    if not s.student_email_id:</p><p>        continue</p><p>    if not frappe.db.exists("User Permission", {</p><p>        "user": s.student_email_id,</p><p>        "allow": "Student",</p><p>        "for_value": <a rel="noopener noreferrer nofollow" href="http://s.name">s.name</a></p><p>    }):</p><p>        frappe.get_doc({</p><p>            "doctype": "User Permission",</p><p>            "user": s.student_email_id,</p><p>            "allow": "Student",</p><p>            "for_value": <a rel="noopener noreferrer nofollow" href="http://s.name">s.name</a></p><p>        }).insert(ignore_permissions=True)</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-12 07:06:38 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3446044019</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3448052323</link>
         <description><![CDATA[<p># Kiểm tra nếu Fee được tạo từ Fee Schedule</p><p>if not doc.flags.created_via_fee_schedule:</p><p>    return</p><p># Lấy thông tin học sinh</p><p>student = frappe.get_doc("Student", doc.student)</p><p>admission_date = student.admission_date</p><p>education_level = <a rel="noopener noreferrer nofollow" href="http://student.education">student.education</a>_level  # VD: "Mầm non", "Tiểu học"</p><p>payment_mode = student.payment_mode  # Custom field: "thang", "ky", "nam"</p><p># Ngày mốc so sánh</p><p>cutoff_date = frappe.utils.getdate("2022-03-01")</p><p># Khởi tạo mức giảm</p><p>discount = 0</p><p># Xác định chính sách</p><p>if admission_date &gt; cutoff_date:</p><p>    # Nhập sau 3/2022 → bắt buộc đóng theo kỳ</p><p>    if payment_mode == "nam":</p><p>        if education_level == "Tiểu học":</p><p>            discount = 0.03</p><p>        elif education_level == "Mầm non":</p><p>            discount = 0.04</p><p>else:</p><p>    # Nhập trước 3/2022 → được đóng tháng, kỳ, năm</p><p>    if payment_mode == "ky":</p><p>        if education_level == "Mầm non":</p><p>            discount = 0.03</p><p>        elif education_level == "Tiểu học":</p><p>            discount = 0.04</p><p>    elif payment_mode == "nam":</p><p>        discount = 0.08</p><p># Áp dụng giảm vào từng component</p><p>for comp in doc.components:</p><p>    original = comp.amount</p><p>    comp.amount = original * (1 - discount)</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-13 07:11:21 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3448052323</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3450270497</link>
         <description><![CDATA[<p>if doc.flags.created_via_fee_schedule and not <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy:</p><p>    fee_schedule = frappe.get_doc("Fee Schedule", doc.fee_schedule)</p><p>    if fee_<a rel="noopener noreferrer nofollow" href="http://schedule.discount">schedule.discount</a>_policy:</p><p>        <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy = fee_<a rel="noopener noreferrer nofollow" href="http://schedule.discount">schedule.discount</a>_policy</p><p><br/></p><p># Nếu có chính sách, áp dụng giảm học phí</p><p>if <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy:</p><p>    policy = frappe.get_doc("Tuition Discount Policy", <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy)</p><p>    discount = 0</p><p>    if <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_type == "percent":</p><p>        discount = <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value / 100</p><p>    elif <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_type == "fixed":</p><p>        discount = -<a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value</p><p>    # Áp dụng giảm vào components</p><p>    for comp in doc.components:</p><p>        if comp.amount:</p><p>            if discount &gt;= 0 and discount &lt; 1:  # percent</p><p>                comp.amount = round(comp.amount * (1 - discount), 0)</p><p>            elif discount &lt; 0:  # fixed giảm trực tiếp dòng đầu tiên</p><p>                if comp.idx == 1:</p><p>                    comp.amount = round(comp.amount + discount, 0)  # discount &lt; 0 nên dùng +</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-14 09:19:20 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3450270497</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3451783929</link>
         <description><![CDATA[<p>def apply_discount_policy_from_schedule(doc):</p><p>    if not doc.fee_schedule:</p><p>        return</p><p>    fee_schedule = frappe.get_doc("Fee Schedule", doc.fee_schedule)</p><p>    if not fee_<a rel="noopener noreferrer nofollow" href="http://schedule.discount">schedule.discount</a>_policy:</p><p>        return</p><p>    policy = frappe.get_doc("Tuition Fee Discount Policy", fee_<a rel="noopener noreferrer nofollow" href="http://schedule.discount">schedule.discount</a>_policy)</p><p>    if not <a rel="noopener noreferrer nofollow" href="http://policy.is">policy.is</a>_active:</p><p>        return</p><p>    # Thông tin học sinh</p><p>    student = frappe.get_doc("Student", doc.student)</p><p>    admission_date = student.get("admission_date")</p><p>    student_level = student.get("student_group")  # hoặc ánh xạ theo cấp học bạn định nghĩa</p><p>    payment_method = doc.get("payment_method")</p><p>    # Kiểm tra cấp học</p><p>    applicable_levels = [<a rel="noopener noreferrer nofollow" href="http://lvl.education">lvl.education</a>_level for lvl in policy.levels]</p><p>    if student_level not in applicable_levels:</p><p>        return</p><p>    # Kiểm tra ngày nhập học (nếu có điều kiện)</p><p>    if policy.admission_before:</p><p>        is_after = admission_date &gt;= policy.admission_before</p><p>        if "sau" in policy.policy_name.lower() and not is_after:</p><p>            return</p><p>        if "trước" in policy.policy_name.lower() and is_after:</p><p>            return</p><p>    # Kiểm tra phương thức thanh toán (nếu có)</p><p>    if policy.payment_method and policy.payment_method != payment_method:</p><p>        return</p><p>    # Áp dụng giảm giá</p><p>    total = 0</p><p>    for row in doc.fee_structure_details:</p><p>        if not <a rel="noopener noreferrer nofollow" href="http://row.is">row.is</a>_tuition_fee:</p><p>            <a rel="noopener noreferrer nofollow" href="http://row.discount">row.discount</a>_applied = 0</p><p>            <a rel="noopener noreferrer nofollow" href="http://row.final">row.final</a>_amount = row.amount</p><p>            total += row.amount</p><p>            continue</p><p>        # Giảm học phí</p><p>        if <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_type == "Percent":</p><p>            discount = row.amount * <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value / 100</p><p>        else:</p><p>            discount = min(row.amount, <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value)</p><p>        <a rel="noopener noreferrer nofollow" href="http://row.discount">row.discount</a>_applied = discount</p><p>        <a rel="noopener noreferrer nofollow" href="http://row.final">row.final</a>_amount = row.amount - discount</p><p>        total += <a rel="noopener noreferrer nofollow" href="http://row.final">row.final</a>_amount</p><p>    doc.grand_total = total</p><p>apply_discount_policy_from_schedule(doc)</p><p><br/></p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-15 04:24:23 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3451783929</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3452244713</link>
         <description><![CDATA[<p>def calculate_discounted_fee(doc, method):</p><p>    if not doc.fee_structure or not <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy or not doc.student_group:</p><p>        return</p><p>    fee_structure = frappe.get_doc("Fee Structure", doc.fee_structure)</p><p>    policy = frappe.get_doc("Tuition Fee Discount Policy", <a rel="noopener noreferrer nofollow" href="http://doc.discount">doc.discount</a>_policy)</p><p>    tuition_fee = 0</p><p>    for row in fee_structure.components:</p><p>        if <a rel="noopener noreferrer nofollow" href="http://row.is">row.is</a>_tuition_fee:</p><p>            tuition_fee += row.amount or 0</p><p>    if <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_type == "Percent":</p><p>        discount = tuition_fee * (<a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value or 0) / 100</p><p>    else:</p><p>        discount = min(tuition_fee, <a rel="noopener noreferrer nofollow" href="http://policy.discount">policy.discount</a>_value or 0)</p><p>    final_per_student = tuition_fee - discount</p><p>    student_count = frappe.db.count("Student", {"student_group": doc.student_group})</p><p>    grand_total = final_per_student * student_count</p><p>    # Tách riêng gán ra</p><p>    <a rel="noopener noreferrer nofollow" href="http://doc.total">doc.total</a>_amount_per_student = final_per_student</p><p>    doc.grand_total = grand_total</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-15 09:09:39 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3452244713</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3453344848</link>
         <description><![CDATA[<pre><code class="language-javascript">frappe.ui.form.on('Fee Component', {
  amount: function (frm, cdt, cdn) {
    let d = locals[cdt][cdn]
    d.total = d.amount
    refresh_field('components')
    if (d.discount) {
      d.total = d.amount - d.amount * (d.discount / 100)
      refresh_field('components')
    }
  },
  discount: function (frm, cdt, cdn) {
    let d = locals[cdt][cdn]
    if (d.discount &lt;= 100) {
      d.total = d.amount - d.amount * (d.discount / 100)
    }
    refresh_field('components')
  },
})</code></pre>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-16 01:37:13 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3453344848</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3453370109</link>
         <description><![CDATA[<pre><code class="language-python">def validate_fee_components(self):
		fee_schedule_components = [d.fees_category for d in self.components]

		fee_structure_components = frappe.get_all(
			"Fee Component",
			pluck="fees_category",
			filters={"parent": self.fee_structure},
		)

		for component in fee_schedule_components:
			if component not in fee_structure_components:
				frappe.msgprint(
					_("Fee Component {0} is not part of Fee Structure {1}").format(
						component, frappe.bold(getlink("Fee Structure", self.fee_structure))
					),
					alert=True,
				)

	def validate_total_against_fee_strucuture(self):
		fee_schedules_total = (
			frappe.db.get_all(
				"Fee Schedule",
				filters={"fee_structure": self.fee_structure},
				fields=["sum(total_amount) as total"],
			)[0]["total"]
			or 0
		)
		fee_structure_total = (
			frappe.db.get_value("Fee Structure", self.fee_structure, "total_amount") or 0
		)

		if fee_schedules_total &gt; fee_structure_total:
			frappe.msgprint(
				_("Total amount of Fee Schedules exceeds the Total Amount of Fee Structure"),
				alert=True,
			)</code></pre>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-16 01:51:37 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3453370109</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3458549556</link>
         <description><![CDATA[<p>frappe.ready(function () {</p><p>  toggle_grade_fields();</p><p>  $('[data-fieldname="program"]').on('change', function () {</p><p>    toggle_grade_fields();</p><p>  });</p><p>  function toggle_grade_fields() {</p><p>    const program = $('[data-fieldname="program"] select').val();</p><p>    const show = program === "THPT";</p><p>    const fields_to_toggle = [</p><p>      "grade_6_score",</p><p>      "grade_7_score",</p><p>      "grade_8_score",</p><p>      "grade_9_score"</p><p>    ];</p><p>    fields_to_toggle.forEach(fieldname =&gt; {</p><p>      const field = $(`[data-fieldname="${fieldname}"]`);</p><p>      if (show) {</p><p>        <a rel="noopener noreferrer nofollow" href="http://field.show">field.show</a>();</p><p>      } else {</p><p>        field.hide();</p><p>      }</p><p>    });</p><p>  }</p><p>});</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-20 04:41:47 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3458549556</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3458904052</link>
         <description><![CDATA[<p># Lấy instructor cũ trước khi lưu</p><p>old_doc = frappe.get_doc("Student Group", <a rel="noopener noreferrer nofollow" href="http://doc.name">doc.name</a>)</p><p>old_instructors = {i.instructor for i in old_doc.instructors}</p><p># Lấy instructor hiện tại sau khi lưu</p><p>new_instructors = {i.instructor for i in doc.instructors}</p><p># Xác định instructor mới được thêm</p><p>added_instructors = new_instructors - old_instructors</p><p># Gửi email cho instructor mới</p><p>for instructor_id in added_instructors:</p><p>    instructor = frappe.get_doc("Instructor", instructor_id)</p><p>    if <a rel="noopener noreferrer nofollow" href="http://instructor.email">instructor.email</a>:</p><p>        subject = "Thông báo: Bạn đã được thêm vào nhóm học sinh"</p><p>        message = f"""</p><p>            Kính gửi {instructor.instructor_name or <a rel="noopener noreferrer nofollow" href="http://instructor.name">instructor.name</a>},&lt;br&gt;&lt;br&gt;</p><p>            Bạn đã được thêm vào nhóm học sinh &lt;b&gt;{<a rel="noopener noreferrer nofollow" href="http://doc.name">doc.name</a>}&lt;/b&gt; trong hệ thống ERP.&lt;br&gt;&lt;br&gt;</p><p>            Trân trọng,&lt;br&gt;</p><p>            Phòng đào tạo</p><p>        """</p><p>        frappe.sendmail(</p><p>            recipients=<a rel="noopener noreferrer nofollow" href="http://instructor.email">instructor.email</a>,</p><p>            subject=subject,</p><p>            message=message</p><p>        )</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-20 07:43:39 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3458904052</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3462135555</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p><br></p><p>def accept_applicant(token):</p><p>placement_doctypes = [</p><p>"Kindergarten Entrance Test",</p><p>"Primary Entrance Test",</p><p>"Highschool Admission Evaluation"</p><p>        ]</p><p>doc = None</p><p>for doctype in placement_doctypes:</p><p>result = frappe.db.get_value(doctype,</p><p>                                     {"response_token": token , "result" : "Pass"},</p><p>                                     ["name", "student_fullname"],</p><p>as_dict=True)</p><p>if result:</p><p>doc = frappe.get_doc("Student Applicant" , <a rel="noopener noreferrer nofollow" href="http://result.name">result.name</a>)</p><p>break</p><p>if not doc:</p><p>return frappe.respond_as_web_page(</p><p>title="Lỗi xác nhận",</p><p>html="&lt;h2&gt;🚫 Not found student applicant.&lt;/h2&gt;",</p><p>http_status_code=400</p><p>            )</p><p># doc = frappe.get_doc("Student Applicant", applicant_list[0].name)</p><p>if doc.docstatus == 1:   </p><p>doc.flags.ignore_submit = True</p><p>doc.db_set("parent_response_status", "Accepted", update_modified=False)</p><p><br></p><p>doc.db_set("application_status","Approved",update_modified=False)</p><p><a rel="noopener noreferrer nofollow" href="http://doc.save">doc.save</a>(ignore_permissions = True)</p><p>frappe.db.commit()</p><p>#doc.submit()</p><p>student = frappe.get_doc({</p><p>"doctype" : "Student" ,</p><p>"first_name" : doc.first_name,</p><p>"student_email_id" : doc.student_email_id,</p><p>"student_applicant" : <a rel="noopener noreferrer nofollow" href="http://doc.name">doc.name</a> ,</p><p>"program" : doc.program,</p><p>"batch" : doc.student_batch,</p><p>"payment_method" : doc.payment_mode,</p><p>"enabled" : 1</p><p>       })   </p><p>student.insert(ignore_permissions= True)</p><p><a rel="noopener noreferrer nofollow" href="http://student.save">student.save</a>()</p><p>student.submit()</p><p>frappe.db.commit()   </p><p>program_enrollment = frappe.get_all(</p><p>"Program Enrollment",</p><p>filters={"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>},</p><p>limit= 1</p><p>    )</p><p>program_name = doc.program or "Default Program"</p><p>if not program_enrollment: </p><p>program_enrollment= frappe.get_doc({</p><p>"doctype": "Program Enrollment",</p><p>"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>,</p><p>"student_batch_name" : doc.student_batch,</p><p>"program" : program_name,</p><p>"academic_year" : doc.academic_year,</p><p>"status": "Active"</p><p>     })</p><p>program_enrollment.insert(ignore_permissions=True)</p><p>program_enrollment.submit()</p><p>frappe.db.commit()</p><p>class_list = frappe.get_all("Student Group",</p><p>filters ={"batch": doc.student_batch},</p><p>fields=["name"],</p><p>order_by ="creation asc")</p><p><br></p><p>selected_class = None</p><p>for group in class_list:</p><p>current_count = frappe.db.count("Student Group Student", </p><p>                                           {"parent": group["name"], </p><p>"parenttype": "Student Group" })</p><p>max_limit = frappe.db.get_value("Student Group", group["name"], "max_strength") </p><p>if current_count &lt; max_limit:</p><p>selected_class = group["name"]</p><p>break</p><p>if not selected_class:</p><p>return frappe.respond_as_web_page(</p><p>title="Không còn lớp trống",</p><p>html="&lt;h3&gt;Rất tiếc, tất cả các lớp đã đầy. Nhà trường sẽ liên hệ với quý phụ huynh sau.&lt;/h3&gt;"</p><p>        )</p><p><br></p><p>student_group = frappe.get_doc("Student Group", selected_class)</p><p>student_group.append("students", {</p><p>"student": <a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>,</p><p>"student_name": student.first_name,</p><p>    })</p><p># student_group.add_student(<a rel="noopener noreferrer nofollow" href="http://student.name">student.name</a>)   </p><p>student_<a rel="noopener noreferrer nofollow" href="http://group.save">group.save</a>(ignore_permissions=True)</p><p>frappe.db.commit()</p><p><br></p><p>return frappe.respond_as_web_page(</p><p><br></p><p>title="Cập nhật",</p><p><br></p><p>html="&lt;h2&gt;✅ Xác nhận ghi danh thành công&lt;/h2&gt;"</p><p>    )</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-22 01:28:25 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3462135555</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3470652005</link>
         <description><![CDATA[<p>vnp_Url = "<a rel="noopener noreferrer nofollow" href="https://sandbox.vnpayment.vn/paymentv2/vpcpay.html">https://sandbox.vnpayment.vn/paymentv2/vpcpay.html</a>"</p><p>    vnp_TmnCode = frappe.db.get_single_value("Education Settings", "vnp_tmn_code")  # mã Terminal</p><p>    vnp_HashSecret = frappe.db.get_single_value("Education Settings", "vnp_hash_secret")  # secret để ký</p><p>    vnp_ReturnUrl = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_vnpay_callback")</p><p>    vnp_Params = {</p><p>        "vnp_Version": "2.1.0",</p><p>        "vnp_Command": "pay",</p><p>        "vnp_TmnCode": vnp_TmnCode,</p><p>        "vnp_Amount": int(amount * 100),  # x100 theo yêu cầu VNPAY</p><p>        "vnp_CurrCode": "VND",</p><p>        "vnp_TxnRef": order_id,</p><p>        "vnp_OrderInfo": f"Payment for invoice {docname}",</p><p>        "vnp_OrderType": "other",</p><p>        "vnp_Locale": "vn",</p><p>        "vnp_ReturnUrl": vnp_ReturnUrl,</p><p>        "vnp_IpAddr": frappe.local.request_ip or "127.0.0.1",</p><p>        "vnp_CreateDate": <a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>().strftime("%Y%m%d%H%M%S"),</p><p>    }</p><p>    sorted_params = sorted(vnp_Params.items())</p><p>    query_string = '&amp;'.join([f"{k}={urllib.parse.quote_plus(str(v))}" for k, v in sorted_params])</p><p>    # Ký SHA256</p><p>    hash_data = '&amp;'.join([f"{k}={v}" for k, v in sorted_params])</p><p>    secure_hash = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(vnp_HashSecret.encode(), hash_data.encode(), hashlib.sha256).hexdigest()</p><p>    payment_url = f"{vnp_Url}?{query_string}&amp;vnp_SecureHashType=SHA256&amp;vnp_SecureHash={secure_hash}"</p><p>    return {</p><p>        "payment_url": payment_url</p><p>    }</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-28 07:25:16 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3470652005</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472326076</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p>def get_payment_options(doctype, docname, phone, currency=None):</p><p>if not frappe.db.exists(doctype, docname):</p><p>frappe.throw(_("Invalid document provided."))</p><p>validate_phone_number(phone_number=phone, throw=True)</p><p>details = get_details(docname)</p><p>vnp_Url = "<a rel="noopener noreferrer nofollow" href="https://sandbox.vnpayment.vn/paymentv2/vpcpay.html">https://sandbox.vnpayment.vn/paymentv2/vpcpay.html</a>"</p><p># vnp_TmnCode = frappe.db.get_single_value("Education Settings", "vnp_tmn_code")  # mã Terminal</p><p># vnp_HashSecret = frappe.db.get_single_value("Education Settings", "vnp_hash_secret")  # secret để ký</p><p>vnp_TmnCode = "NJJ0R8FS"</p><p>vnp_HashSecret = "BYKJBHPPZKQMKBIBGGXIYKWYFAYSJXCM"</p><p>vnp_ReturnUrl = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_vnpay_callback")</p><p><br></p><p>vnp_Params = {</p><p>"vnp_Version": "2.1.0",</p><p>"vnp_Command": "pay",</p><p>"vnp_TmnCode": vnp_TmnCode,</p><p>"vnp_Amount": int(details.outstanding_amount * 100),  # x100 theo yêu cầu VNPAY</p><p>"vnp_CurrCode": "VND",</p><p>"vnp_TxnRef": details["name"],  # Mã đơn hàng, có thể là ID của invoice</p><p>"vnp_OrderInfo": f"INV-{docname}",</p><p>"vnp_OrderType": "other",</p><p>"vnp_Locale": "vn",</p><p>"vnp_CreateDate": <a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>().strftime("%Y%m%d%H%M%S"),</p><p>"vnp_IpAddr": frappe.local.request_ip or "127.0.0.1",</p><p>"vnp_ReturnUrl": vnp_ReturnUrl,</p><p>    }</p><p># if not vnp_Params.get("vnp_Bankcode"):</p><p>#     vnp_Params["vnp_BankCode"].set("VNPAYQR")  # Mã ngân hàng, nếu không có thì dùng mã QR</p><p>sorted_params = sorted(vnp_Params.items())</p><p><br></p><p>query_string = '&amp;'.join([f"{k}={urllib.parse.quote_plus(str(v))}" for k, v in sorted_params])</p><p>print("Query String:", query_string)</p><p># Ký SHA256</p><p><br></p><p>hash_data = '&amp;'.join([f"{k}={str(v)}" for k, v in sorted_params])</p><p>print("Hash Data:", hash_data)</p><p>secure_hash = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(vnp_HashSecret.encode(), hash_data.encode(), hashlib.sha512).hexdigest()</p><p>print("Secure Hash:", secure_hash)</p><p>payment_url = f"{vnp_Url}?{query_string}&amp;vnp_SecureHashType=SHA512&amp;vnp_SecureHash={secure_hash}"</p><p><br></p><p>return {</p><p>"payment_url": payment_url</p><p>    }</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-29 07:57:59 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472326076</guid>
      </item>
      <item>
         <title>Terminal ID / Mã Website (vnp_TmnCode): NCVDVNZ5</title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472364776</link>
         <description><![CDATA[<p>Secret Key / Chuỗi bí mật tạo checksum (vnp_HashSecret): V1VFO5UWJ97BIFH9IFX683WJYU5WB51W</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-29 08:47:30 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472364776</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472397460</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p>def handle_vnpay_callback(**kwargs):</p><p>    import hmac, hashlib</p><p>    vnp_HashSecret = frappe.db.get_single_value("Education Settings", "vnp_hash_secret")</p><p>    </p><p>    vnp_data = {k: v for k, v in frappe.local.form_dict.items() if k.startswith("vnp_")}</p><p>    vnp_secure_hash = vnp_data.pop("vnp_SecureHash", None)</p><p>    vnp_secure_hash_type = vnp_data.pop("vnp_SecureHashType", None)</p><p>    sorted_data = sorted(vnp_data.items())</p><p>    hash_data = '&amp;'.join(f"{k}={v}" for k, v in sorted_data)</p><p>    </p><p>    computed_hash = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(</p><p>        vnp_HashSecret.encode(), </p><p>        hash_data.encode(), </p><p>        hashlib.sha512</p><p>    ).hexdigest()</p><p>    if computed_hash == vnp_secure_hash:</p><p>        # Success → xử lý update trạng thái, ghi log, v.v.</p><p>        frappe.logger().info("✅ VNPAY callback verified successfully")</p><p>        return "Confirm Success"</p><p>    else:</p><p>        frappe.logger().error("❌ Invalid VNPAY secure hash")</p><p>        return "Invalid Signature"</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-29 09:34:35 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3472397460</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473225637</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p>def get_payment_options(doctype, docname, phone, currency=None):</p><p>if not frappe.db.exists(doctype, docname):</p><p>frappe.throw(_("Invalid document provided."))</p><p>validate_phone_number(phone_number=phone, throw=True)</p><p>details = get_details(docname)</p><p>vnp_Url = "<a rel="noopener noreferrer nofollow" href="https://sandbox.vnpayment.vn/paymentv2/vpcpay.html">https://sandbox.vnpayment.vn/paymentv2/vpcpay.html</a>"</p><p># vnp_TmnCode = frappe.db.get_single_value("Education Settings", "vnp_tmn_code")  # mã Terminal</p><p># vnp_HashSecret = frappe.db.get_single_value("Education Settings", "vnp_hash_secret")  # secret để ký</p><p>vnp_TmnCode = "NCVDVNZ5"</p><p>vnp_HashSecret = "V1VFO5UWJ97BIFH9IFX683WJYU5WB51W"</p><p>vnp_ReturnUrl = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_vnpay_callback")</p><p>vnp_Params = {</p><p>"vnp_Version": "2.1.0",</p><p>"vnp_Command": "pay",</p><p>"vnp_TmnCode": vnp_TmnCode,</p><p>"vnp_Amount": int(details.outstanding_amount * 100),  # x100 theo yêu cầu VNPAY</p><p>"vnp_CurrCode": "VND",</p><p>"vnp_TxnRef": <a rel="noopener noreferrer nofollow" href="http://details.name">details.name</a>,  # Mã đơn hàng, có thể là ID của invoice</p><p>"vnp_OrderInfo": f"Test-{docname}",</p><p>"vnp_OrderType": "other",</p><p>"vnp_Locale": "vn",</p><p>"vnp_CreateDate": <a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>().strftime("%Y%m%d%H%M%S"),</p><p>"vnp_ExpireDate": (<a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>() + timedelta(minutes=3600)).strftime("%Y%m%d%H%M%S"),</p><p>"vnp_IpAddr":  frappe.get_request_header("X-Forwarded-For") or frappe.local.request_ip ,</p><p>"vnp_ReturnUrl": vnp_ReturnUrl,</p><p>    }</p><p>sorted_params = sorted(vnp_Params.items())</p><p># Ký SHA512</p><p>print("Sorted Params:", sorted_params)</p><p>hash_data = '&amp;'.join([f"{k}={str(v)}" for k, v in sorted_params])</p><p>print("Hash Data:", hash_data)</p><p>secure_hash = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(vnp_HashSecret.encode(), hash_data.encode(), hashlib.sha512).hexdigest()</p><p>print("Secure Hash:", secure_hash)</p><p>query_string = '&amp;'.join([f"{k}={urllib.parse.quote_plus(str(v))}" for k, v in sorted_params])</p><p>print("Query String:", query_string)</p><p>payment_url = f"{vnp_Url}?{query_string}&amp;vnp_SecureHash={secure_hash}"</p><p>print ("Payment URL:", payment_url)</p><p>return {</p><p>"payment_url": payment_url</p><p>    }</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-30 01:47:03 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473225637</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473449816</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p>def get_payment_options(doctype, docname, phone, currency=None):</p><p>if not frappe.db.exists(doctype, docname):</p><p>frappe.throw(_("Invalid document provided."))</p><p>validate_phone_number(phone_number=phone, throw=True)</p><p>details = get_details(docname)</p><p>vnp_Url = "<a rel="noopener noreferrer nofollow" href="https://sandbox.vnpayment.vn/paymentv2/vpcpay.html">https://sandbox.vnpayment.vn/paymentv2/vpcpay.html</a>"</p><p># vnp_TmnCode = frappe.db.get_single_value("Education Settings", "vnp_tmn_code")  # mã Terminal</p><p># vnp_HashSecret = frappe.db.get_single_value("Education Settings", "vnp_hash_secret")  # secret để ký</p><p>vnp_TmnCode = "NCVDVNZ5"</p><p>vnp_HashSecret = "V1VFO5UWJ97BIFH9IFX683WJYU5WB51W"</p><p>vnp_ReturnUrl = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_vnpay_callback")</p><p>vnp_Params = {</p><p>"vnp_Version": "2.1.0",</p><p>"vnp_Command": "pay",</p><p>"vnp_TmnCode": vnp_TmnCode,</p><p>"vnp_Amount": int(details.outstanding_amount * 100),  # x100 theo yêu cầu VNPAY</p><p>"vnp_CurrCode": "VND",</p><p>"vnp_TxnRef": <a rel="noopener noreferrer nofollow" href="http://details.name">details.name</a>,  # Mã đơn hàng, có thể là ID của invoice</p><p>"vnp_OrderInfo": f"Test-{docname}",</p><p>"vnp_OrderType": "other",</p><p>"vnp_Locale": "vn",</p><p>"vnp_CreateDate": <a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>().strftime("%Y%m%d%H%M%S"),</p><p>"vnp_ExpireDate": (<a rel="noopener noreferrer nofollow" href="http://datetime.now">datetime.now</a>() + timedelta(minutes=3600)).strftime("%Y%m%d%H%M%S"),</p><p>"vnp_IpAddr":  frappe.get_request_header("X-Forwarded-For") or frappe.local.request_ip ,</p><p>"vnp_ReturnUrl": vnp_ReturnUrl,</p><p>    }</p><p>sorted_params = sorted(vnp_Params.items())</p><p># Ký SHA512</p><p>print("Sorted Params:", sorted_params)</p><p>hash_data = '&amp;'.join([f"{k}={str(v)}" for k, v in sorted_params])</p><p>print("Hash Data:", hash_data)</p><p>secure_hash = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(vnp_HashSecret.encode(), hash_data.encode(), hashlib.sha512).hexdigest()</p><p>print("Secure Hash:", secure_hash)</p><p>query_string = '&amp;'.join([f"{k}={urllib.parse.quote_plus(str(v))}" for k, v in sorted_params])</p><p>print("Query String:", query_string)</p><p>payment_url = f"{vnp_Url}?{query_string}&amp;vnp_SecureHash={secure_hash}"</p><p>print ("Payment URL:", payment_url)</p><p>return {</p><p>"payment_url": payment_url</p><p>    }</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-30 04:07:52 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473449816</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473450620</link>
         <description><![CDATA[<p>// Ví dụ với Axios</p><p>import axios from 'axios'</p><p>async function handleMomoPayment(doctype, docname, phone) {</p><p>  try {</p><p>    const response = await <a rel="noopener noreferrer nofollow" href="http://axios.post">axios.post</a>('/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.get_payment_options', {</p><p>      doctype: doctype,</p><p>      docname: docname,</p><p>      phone: phone</p><p>    });</p><p>    const paymentUrl = <a rel="noopener noreferrer nofollow" href="http://response.data">response.data</a>.message.payment_url;</p><p>    if (paymentUrl) {</p><p>      // Redirect người dùng đến link thanh toán MOMO</p><p>      window.location.href = paymentUrl;</p><p>    } else {</p><p>      alert("Không thể tạo link thanh toán.");</p><p>    }</p><p>  } catch (error) {</p><p>    console.error("Payment error:", error);</p><p>    alert("Có lỗi xảy ra khi tạo thanh toán MOMO.");</p><p>  }</p><p>}</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-30 04:08:34 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473450620</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473450818</link>
         <description><![CDATA[<p>import requests</p><p>import hmac</p><p>import hashlib</p><p>import json</p><p>import time</p><p>from datetime import datetime</p><p>import uuid</p><p>import frappe</p><p>from frappe import _</p><p>@frappe.whitelist(allow_guest=True)</p><p>def get_payment_options(doctype, docname, phone, currency=None):</p><p>    if not frappe.db.exists(doctype, docname):</p><p>        frappe.throw(_("Invalid document provided."))</p><p>    validate_phone_number(phone_number=phone, throw=True)</p><p>    details = get_details(docname)</p><p>    # MOMO CONFIG</p><p>    partner_code = "MOMOXNXX20210701"</p><p>    access_key = "F8BBA842ECF85"</p><p>    secret_key = "K951B6PE1waDMi640xX08PD3vg6EkVlz"</p><p>    endpoint = "<a rel="noopener noreferrer nofollow" href="https://test-payment.momo.vn/v2/gateway/api/create">https://test-payment.momo.vn/v2/gateway/api/create</a>"</p><p>    redirect_url = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_momo_callback")</p><p>    ipn_url = frappe.utils.get_url("/api/method/<a rel="noopener noreferrer nofollow" href="http://education.education">education.education</a>.billing.handle_momo_ipn")</p><p>    order_id = str(uuid.uuid4())</p><p>    request_id = str(uuid.uuid4())</p><p>    amount = str(int(details.outstanding_amount))</p><p>    order_info = f"Thanh toán hóa đơn {docname}"</p><p>    extra_data = ""</p><p>    raw_signature = f"accessKey={access_key}&amp;amount={amount}&amp;extraData={extra_data}&amp;ipnUrl={ipn_url}" \</p><p>                    f"&amp;orderId={order_id}&amp;orderInfo={order_info}&amp;partnerCode={partner_code}" \</p><p>                    f"&amp;redirectUrl={redirect_url}&amp;requestId={request_id}&amp;requestType=captureWallet"</p><p>    # Tạo chữ ký SHA256</p><p>    signature = <a rel="noopener noreferrer nofollow" href="http://hmac.new">hmac.new</a>(secret_key.encode(), raw_signature.encode(), hashlib.sha256).hexdigest()</p><p>    payload = {</p><p>        "partnerCode": partner_code,</p><p>        "accessKey": access_key,</p><p>        "requestId": request_id,</p><p>        "amount": amount,</p><p>        "orderId": order_id,</p><p>        "orderInfo": order_info,</p><p>        "redirectUrl": redirect_url,</p><p>        "ipnUrl": ipn_url,</p><p>        "extraData": extra_data,</p><p>        "requestType": "captureWallet",</p><p>        "signature": signature,</p><p>        "lang": "vi"</p><p>    }</p><p>    # Gửi request đến MOMO</p><p>    response = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(endpoint, json=payload, headers={"Content-Type": "application/json"})</p><p>    if response.status_code != 200:</p><p>        frappe.throw(_("Momo Payment Gateway Error"))</p><p>    res_json = response.json()</p><p>    if res_json.get("payUrl"):</p><p>        return {"payment_url": res_json["payUrl"]}</p><p>    else:</p><p>        frappe.throw(_("Unable to create payment link. Please try again."))</p><p><br/></p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-30 04:08:47 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473450818</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473455255</link>
         <description><![CDATA[<p>function openPaymentGateway(close) {</p><p>  if (!<a rel="noopener noreferrer nofollow" href="http://billingDetails.mobile">billingDetails.mobile</a>_number || !<a rel="noopener noreferrer nofollow" href="http://billingDetails.email">billingDetails.email</a>) {</p><p>validateFields()</p><p>return</p><p>  }</p><p>paymentOptions.submit(</p><p>    {},</p><p>    {</p><p>onSuccess(data) {</p><p>if(data.payment_url){</p><p>window.location.href = data.payment_url</p><p>        }else{</p><p>alert('Payment URL not found')</p><p>        }</p><p>      },</p><p>onError(err) {</p><p>showError(err)</p><p>      },</p><p>    }</p><p>  )</p><p>}</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-05-30 04:12:31 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3473455255</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478115764</link>
         <description><![CDATA[<p>import requests</p><p>import base64</p><p>import json</p><p>PAYPAL_CLIENT_ID = frappe.conf.paypal_client_id</p><p>PAYPAL_SECRET = frappe.conf.paypal_secret</p><p>BASE_URL = "<a rel="noopener noreferrer nofollow" href="https://api-m.sandbox.paypal.com">https://api-m.sandbox.paypal.com</a>"  # Chuyển thành <a rel="noopener noreferrer nofollow" href="http://api-m.paypal.com">api-m.paypal.com</a> khi live</p><p>def get_access_token():</p><p>    auth = base64.b64encode(f"{PAYPAL_CLIENT_ID}:{PAYPAL_SECRET}".encode()).decode()</p><p>    res = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(f"{BASE_URL}/v1/oauth2/token",</p><p>        headers={"Authorization": f"Basic {auth}"},</p><p>        data={"grant_type": "client_credentials"}</p><p>    )</p><p>    return res.json()["access_token"]</p><p>@frappe.whitelist(allow_guest=True)</p><p>def create_order(amount, currency="USD"):</p><p>    token = get_access_token()</p><p>    res = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(f"{BASE_URL}/v2/checkout/orders",</p><p>        headers={</p><p>            "Authorization": f"Bearer {token}",</p><p>            "Content-Type": "application/json",</p><p>        },</p><p>        json={</p><p>            "intent": "CAPTURE",</p><p>            "purchase_units": [{</p><p>                "amount": {</p><p>                    "currency_code": currency,</p><p>                    "value": f"{int(amount)/100:.2f}"</p><p>                }</p><p>            }],</p><p>            "application_context": {</p><p>                "return_url": "<a rel="noopener noreferrer nofollow" href="https://your-site.com/paypal-success">https://your-site.com/paypal-success</a>",</p><p>                "cancel_url": "<a rel="noopener noreferrer nofollow" href="https://your-site.com/paypal-cancel">https://your-site.com/paypal-cancel</a>"</p><p>            }</p><p>        }</p><p>    )</p><p>    return res.json()</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 01:57:43 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478115764</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478131784</link>
         <description><![CDATA[<p>AaQ_mVsRRWtq0EEHuvWDU9TB88SjQQ2bM3QUS29eRUIPvnc_F6ipz3MeEe6ZhjZk_A-sl-sm7nYPGUZe</p><p><br/></p><p>EOh27KYnQgW4hSqD41SgO2jqpnL8d_rwoOLg0jprpQT8YJZ71SUxr1gGgRcUfH3M20aTqYWBs-mm1-er</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 02:07:21 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478131784</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478166565</link>
         <description><![CDATA[<p>@frappe.whitelist(allow_guest=True)</p><p>def get_payment_options(doctype, docname, phone, currency="USD"):</p><p>    if not frappe.db.exists(doctype, docname):</p><p>        frappe.throw(_("Invalid document provided."))</p><p>    validate_phone_number(phone_number=phone, throw=True)</p><p>    details = get_details(docname)</p><p>    # Convert amount to USD or use your logic</p><p>    amount = round(float(details.outstanding_amount), 2)</p><p>    # Tạo access token</p><p>    access_token = get_paypal_access_token()</p><p>    # Tạo order</p><p>    payload = {</p><p>        "intent": "CAPTURE",</p><p>        "purchase_units": [{</p><p>            "reference_id": docname,</p><p>            "amount": {</p><p>                "currency_code": currency,</p><p>                "value": f"{amount:.2f}"</p><p>            },</p><p>            "description": f"Payment for {docname}"</p><p>        }],</p><p>        "application_context": {</p><p>            "brand_name": "Your Brand",</p><p>            "landing_page": "LOGIN",</p><p>            "user_action": "PAY_NOW",</p><p>            "return_url": frappe.utils.get_url("/paypal/success"),</p><p>            "cancel_url": frappe.utils.get_url("/paypal/cancel")</p><p>        }</p><p>    }</p><p>    headers = {</p><p>        "Content-Type": "application/json",</p><p>        "Authorization": f"Bearer {access_token}"</p><p>    }</p><p>    res = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(f"{PAYPAL_BASE_URL}/v2/checkout/orders", headers=headers, json=payload)</p><p>    res.raise_for_status()</p><p>    paypal_data = res.json()</p><p>    # Lấy approve URL</p><p>    approval_url = next(</p><p>        (link["href"] for link in paypal_data.get("links", []) if link["rel"] == "approve"),</p><p>        None</p><p>    )</p><p>    if not approval_url:</p><p>        frappe.throw(_("Could not get approval link from PayPal."))</p><p>    return {</p><p>        "payment_url": approval_url</p><p>    }</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 02:24:46 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478166565</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478178758</link>
         <description><![CDATA[<p><a rel="noopener noreferrer nofollow" href="mailto:sb-ud9kd30222426@personal.example.com">sb-ud9kd30222426@personal.example.com</a></p><p>lsE]3T&gt;_</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 02:31:14 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478178758</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478491821</link>
         <description><![CDATA[<p>import frappe</p><p>import requests</p><p>import base64</p><p>import json</p><p>PAYPAL_CLIENT_ID = frappe.conf.paypal_client_id</p><p>PAYPAL_SECRET    = frappe.conf.paypal_secret</p><p>PAYPAL_BASE_URL  = "<a rel="noopener noreferrer nofollow" href="https://api-m.sandbox.paypal.com">https://api-m.sandbox.paypal.com</a>"  # Sandbox; đổi thành live URL khi lên production.</p><p>def get_paypal_access_token():</p><p>    auth = base64.b64encode(f"{PAYPAL_CLIENT_ID}:{PAYPAL_SECRET}".encode()).decode()</p><p>    r = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(</p><p>        f"{PAYPAL_BASE_URL}/v1/oauth2/token",</p><p>        headers={ "Authorization": f"Basic {auth}" },</p><p>        data={ "grant_type": "client_credentials" }</p><p>    )</p><p>    r.raise_for_status()</p><p>    return r.json().get("access_token")</p><p>@frappe.whitelist(allow_guest=True)</p><p>def paypal_return():</p><p>    data = frappe.local.form_dict</p><p>    order_id = data.get("token")</p><p>    payer_id = data.get("PayerID")</p><p>    if not order_id or not payer_id:</p><p>        frappe.logger().error(f"Missing token or PayerID: {data}")</p><p>        frappe.respond_as_web_page(</p><p>            title="Payment Error",</p><p>            html="&lt;p&gt;Không tìm thấy token hoặc PayerID từ PayPal.&lt;/p&gt;",</p><p>            indicator_color="red",</p><p>            http_status_code=400,</p><p>        )</p><p>        return</p><p>    # 1. Lấy access token</p><p>    try:</p><p>        access_token = get_paypal_access_token()</p><p>    except Exception as e:</p><p>        frappe.logger().error(f"Could not retrieve PayPal access token: {e}")</p><p>        frappe.respond_as_web_page(</p><p>            title="Payment Error",</p><p>            html="&lt;p&gt;Lỗi khi kết nối PayPal. Vui lòng thử lại sau.&lt;/p&gt;",</p><p>            indicator_color="red",</p><p>            http_status_code=500,</p><p>        )</p><p>        return</p><p>    # 2. Gọi capture</p><p>    capture_url = f"{PAYPAL_BASE_URL}/v2/checkout/orders/{order_id}/capture"</p><p>    headers = {</p><p>        "Content-Type": "application/json",</p><p>        "Authorization": f"Bearer {access_token}"</p><p>    }</p><p>    try:</p><p>        resp = <a rel="noopener noreferrer nofollow" href="http://requests.post">requests.post</a>(capture_url, headers=headers)</p><p>        resp.raise_for_status()</p><p>    except requests.HTTPError as http_err:</p><p>        frappe.logger().error(f"PayPal capture HTTP error: {http_err} | Response: {resp.text}")</p><p>        frappe.respond_as_web_page(</p><p>            title="Payment Failed",</p><p>            html=f"&lt;p&gt;Thanh toán không thành công: {resp.text}&lt;/p&gt;",</p><p>            indicator_color="red",</p><p>            http_status_code=402,</p><p>        )</p><p>        return</p><p>    except Exception as ex:</p><p>        frappe.logger().error(f"PayPal capture exception: {ex}")</p><p>        frappe.respond_as_web_page(</p><p>            title="Payment Error",</p><p>            html="&lt;p&gt;Đã xảy ra lỗi khi xác nhận giao dịch.&lt;/p&gt;",</p><p>            indicator_color="red",</p><p>            http_status_code=500,</p><p>        )</p><p>        return</p><p>    capture_data = resp.json()</p><p>    frappe.logger().info(f"PayPal Capture Response: {json.dumps(capture_data)}")</p><p>    # 3. Kiểm tra kết quả capture</p><p>    status = capture_data.get("status")</p><p>    if status == "COMPLETED":</p><p>        # Ví dụ: Lấy transaction ID, amount, invoice (nếu có)</p><p>        purchase_units = capture_data.get("purchase_units", [])</p><p>        # Thông thường ta sẽ có purchase_units[0].payments.captures[0].id, .status, .amount</p><p>        capture_info = purchase_units[0].get("payments", {}).get("captures", [])[0]</p><p>        transaction_id = capture_info.get("id")</p><p>        payment_amount = capture_info.get("amount", {}).get("value")</p><p>        currency = capture_info.get("amount", {}).get("currency_code")</p><p>        # TODO: Ở đây bạn sẽ update trong ERPNext, ví dụ:</p><p>        # - Ghi nhận Payment Entry (API của ERPNext)</p><p>        # - Đánh dấu Invoice/Order là đã thanh toán</p><p>        # - Gửi email xác nhận cho phụ huynh</p><p>        frappe.logger().info(f"Payment completed. Transaction ID: {transaction_id}, Amount: {payment_amount} {currency}")</p><p>        return frappe.respond_as_web_page(</p><p>            title="Payment Successful",</p><p>            html="&lt;p&gt;Thanh toán thành công! Cảm ơn bạn đã sử dụng dịch vụ.&lt;/p&gt;",</p><p>            indicator_color="green"</p><p>        )</p><p>    else:</p><p>        # Nếu không hoàn thành (ví dụ: PAYER_ACTION_REQUIRED, etc)</p><p>        frappe.logger().error(f"PayPal capture not completed: {capture_data}")</p><p>        return frappe.respond_as_web_page(</p><p>            title="Payment Not Completed",</p><p>            html="&lt;p&gt;Giao dịch chưa hoàn thành. Vui lòng kiểm tra lại.&lt;/p&gt;",</p><p>            indicator_color="orange"</p><p>        )</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 06:27:30 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478491821</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478493362</link>
         <description><![CDATA[<p><a rel="noopener noreferrer nofollow" href="mailto:sb-ud9kd30222426@personal.example.com">sb-ud9kd30222426@personal.example.com</a></p><p>lsE]3T&gt;_</p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-04 06:28:44 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3478493362</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3479792551</link>
         <description><![CDATA[<p>frappe.ui.form.on('Sales Invoice', {</p><p>    currency(frm) {</p><p>        if (frm.doc.currency &amp;&amp; frm.doc.currency !== frappe.boot.sysdefaults.currency) {</p><p>            <a rel="noopener noreferrer nofollow" href="http://frappe.call">frappe.call</a>({</p><p>                method: "erpnext.setup.utils.get_exchange_rate",</p><p>                args: {</p><p>                    from_currency: frappe.boot.sysdefaults.currency,</p><p>                    to_currency: frm.doc.currency,</p><p>                    transaction_date: frm.doc.posting_date || frappe.datetime.get_today()</p><p>                },</p><p>                callback(r) {</p><p>                    if (!r.exc) {</p><p>                        let rate = r.message;</p><p>                        frm.doc.items.forEach(item =&gt; {</p><p>                            item.rate = flt(item.rate / rate, 2);</p><p>                            item.price_list_rate = item.rate;</p><p>                            item.amount = flt(item.rate * item.qty, 2);</p><p>                        });</p><p>                        frm.doc.conversion_rate = rate;</p><p>                        frm.refresh_field("items");</p><p>                        frm.refresh_field("conversion_rate");</p><p>                    }</p><p>                }</p><p>            });</p><p>        }</p><p>    }</p><p>});</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-05 03:50:13 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3479792551</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3481062884</link>
         <description><![CDATA[<p>&lt;table class="table table-bordered"&gt;</p><p>  &lt;thead&gt;</p><p>    &lt;tr&gt;</p><p>      &lt;th&gt;Item&lt;/th&gt;</p><p>      &lt;th&gt;Qty&lt;/th&gt;</p><p>      &lt;th&gt;Rate (VND)&lt;/th&gt;</p><p>      &lt;th&gt;Amount (VND)&lt;/th&gt;</p><p>      {% if doc.print_exchange_rate %}</p><p>        &lt;th&gt;Rate (USD)&lt;/th&gt;</p><p>        &lt;th&gt;Amount (USD)&lt;/th&gt;</p><p>      {% endif %}</p><p>    &lt;/tr&gt;</p><p>  &lt;/thead&gt;</p><p>  &lt;tbody&gt;</p><p>    {% for row in doc.items %}</p><p>    &lt;tr&gt;</p><p>      &lt;td&gt;{{ row.item_name }}&lt;/td&gt;</p><p>      &lt;td&gt;{{ row.qty }}&lt;/td&gt;</p><p>      &lt;td class="text-right"&gt;{{ row.rate | round(0) }}&lt;/td&gt;</p><p>      &lt;td class="text-right"&gt;{{ row.amount | round(0) }}&lt;/td&gt;</p><p>      {% if doc.print_exchange_rate %}</p><p>        &lt;td class="text-right"&gt;{{ (row.rate / doc.print_exchange_rate) | round(2) }}&lt;/td&gt;</p><p>        &lt;td class="text-right"&gt;{{ (row.amount / doc.print_exchange_rate) | round(2) }}&lt;/td&gt;</p><p>      {% endif %}</p><p>    &lt;/tr&gt;</p><p>    {% endfor %}</p><p>  &lt;/tbody&gt;</p><p>&lt;/table&gt;</p><p>{% if doc.print_exchange_rate %}</p><p>  &lt;hr&gt;</p><p>  &lt;p&gt;&lt;strong&gt;Tổng tiền bằng USD:&lt;/strong&gt; {{ (doc.grand_total / doc.print_exchange_rate) | round(2) }} USD&lt;/p&gt;</p><p>  &lt;p&gt;&lt;em&gt;(Tỷ giá quy đổi: 1 USD = {{ doc.print_exchange_rate | round(2) }} VND)&lt;/em&gt;&lt;/p&gt;</p><p>{% endif %}</p><p><br/></p>]]></description>
         <enclosure url="" />
         <pubDate>2025-06-06 04:00:41 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3481062884</guid>
      </item>
      <item>
         <title></title>
         <author>khanhtruong326</author>
         <link>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3481077540</link>
         <description><![CDATA[<p>url = "<a rel="noopener noreferrer nofollow" href="https://api.vnappmob.com/api/v2/exchange_rate/tcb">https://api.vnappmob.com/api/v2/exchange_rate/tcb</a>"</p><p>    response = requests.get(url)</p><p>&lt;api_key|scope=exchange_rate|permission=0&gt;</p><p> <a rel="noopener noreferrer nofollow" href="https://api.exchangerate.host/latest?base=USD&amp;symbols=VND">https://api.exchangerate.host/latest?base=USD&amp;symbols=VND</a></p><pre><code></code></pre><p><a rel="noopener noreferrer nofollow" href="https://api.exchangerate.host/convert?from=USD&amp;to=VND">https://api.exchangerate.host/convert?from=USD&amp;to=VND</a></p><p>dac5d4f1c4d2e5cd85d7400ae45d2849</p>]]></description>
         <enclosure url="https://api.exchangerate.host/convert?from=USD&amp;to=VND" />
         <pubDate>2025-06-06 04:27:43 UTC</pubDate>
         <guid>https://padlet.com/khanhtruong326/3iin7vzf6ewnd34h/wish/3481077540</guid>
      </item>
   </channel>
</rss>
