├── license.txt ├── thai_tax ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── modules.txt ├── public │ ├── .gitkeep │ ├── images │ │ └── wht │ │ │ └── WithholdingTaxCert.png │ └── js │ │ ├── expense_claim.js │ │ ├── sales_tax_invoice.js │ │ ├── purchase_tax_invoice.js │ │ ├── address.js │ │ ├── journal_entry.js │ │ ├── withholding_tax_cert.js │ │ └── payment_entry.js ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py ├── thai_tax │ ├── __init__.py │ ├── doctype │ │ ├── __init__.py │ │ ├── purchase_tax_invoice │ │ │ ├── __init__.py │ │ │ ├── purchase_tax_invoice.js │ │ │ ├── test_purchase_tax_invoice.py │ │ │ ├── purchase_tax_invoice.py │ │ │ └── purchase_tax_invoice.json │ │ ├── sales_tax_invoice │ │ │ ├── __init__.py │ │ │ ├── sales_tax_invoice.js │ │ │ ├── test_sales_tax_invoice.py │ │ │ ├── sales_tax_invoice.py │ │ │ └── sales_tax_invoice.json │ │ ├── tax_invoice_settings │ │ │ ├── __init__.py │ │ │ ├── test_tax_invoice_settings.py │ │ │ ├── tax_invoice_settings.py │ │ │ ├── tax_invoice_settings.js │ │ │ └── tax_invoice_settings.json │ │ ├── withholding_tax_cert │ │ │ ├── __init__.py │ │ │ ├── test_withholding_tax_cert.py │ │ │ ├── withholding_tax_cert.py │ │ │ ├── withholding_tax_cert.js │ │ │ └── withholding_tax_cert.json │ │ ├── withholding_tax_type │ │ │ ├── __init__.py │ │ │ ├── test_withholding_tax_type.py │ │ │ ├── withholding_tax_type.js │ │ │ ├── withholding_tax_type.py │ │ │ └── withholding_tax_type.json │ │ ├── withholding_tax_items │ │ │ ├── __init__.py │ │ │ ├── withholding_tax_items.py │ │ │ └── withholding_tax_items.json │ │ ├── withholding_tax_setting │ │ │ ├── __init__.py │ │ │ ├── withholding_tax_setting.js │ │ │ ├── test_withholding_tax_setting.py │ │ │ ├── withholding_tax_setting.py │ │ │ └── withholding_tax_setting.json │ │ └── journal_entry_tax_invoice_detail │ │ │ ├── __init__.py │ │ │ ├── journal_entry_tax_invoice_detail.py │ │ │ └── journal_entry_tax_invoice_detail.json │ ├── report │ │ ├── __init__.py │ │ ├── pnd3 │ │ │ ├── __init__.py │ │ │ ├── pnd3.json │ │ │ ├── pnd3.js │ │ │ └── pnd3.py │ │ ├── pnd53 │ │ │ ├── __init__.py │ │ │ ├── pnd53.json │ │ │ ├── pnd53.js │ │ │ └── pnd53.py │ │ ├── sales_tax_report │ │ │ ├── __init__.py │ │ │ ├── sales_tax_report.json │ │ │ ├── sales_tax_report.js │ │ │ └── sales_tax_report.py │ │ └── purchase_tax_report │ │ │ ├── __init__.py │ │ │ ├── purchase_tax_report.json │ │ │ ├── purchase_tax_report.js │ │ │ └── purchase_tax_report.py │ ├── print_format │ │ ├── __init__.py │ │ └── withholding_tax_cert │ │ │ ├── __init__.py │ │ │ └── withholding_tax_cert.json │ ├── onboarding_step │ │ ├── add_taxes_to_chart_of_account │ │ │ └── add_taxes_to_chart_of_account.json │ │ ├── add_withholding_tax_type │ │ │ └── add_withholding_tax_type.json │ │ ├── tax_invoice_setting │ │ │ └── tax_invoice_setting.json │ │ ├── tax_report │ │ │ └── tax_report.json │ │ ├── browse_withholding_tax_cert │ │ │ └── browse_withholding_tax_cert.json │ │ ├── asset_item │ │ │ └── asset_item.json │ │ ├── browse_sales_tax_invoices │ │ │ └── browse_sales_tax_invoices.json │ │ ├── browse_purchase_tax_invoices │ │ │ └── browse_purchase_tax_invoices.json │ │ ├── withholding_tax_report_(pnd) │ │ │ └── withholding_tax_report_(pnd).json │ │ ├── company_set_up │ │ │ └── company_set_up.json │ │ └── add_thai_tax_accounts │ │ │ └── add_thai_tax_accounts.json │ ├── module_onboarding │ │ └── thai_tax │ │ │ └── thai_tax.json │ ├── form_tour │ │ └── tax_invoice_settings │ │ │ └── tax_invoice_settings.json │ └── workspace │ │ └── thai_tax │ │ └── thai_tax.json ├── __init__.py ├── patches.txt ├── custom │ ├── gl_entry.py │ ├── unreconcile_payment.py │ ├── queries.py │ ├── journal_entry.py │ ├── dashboard_overrides.py │ ├── employee_advance.py │ ├── payment_entry.py │ └── custom_api.py ├── uninstall.py ├── install.py ├── utils.py ├── hooks.py └── constants.py ├── requirements.txt ├── .gitignore ├── .editorconfig ├── MANIFEST.in ├── setup.py ├── .flake8 ├── .pre-commit-config.yaml ├── README.md └── .eslintrc /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /thai_tax/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/modules.txt: -------------------------------------------------------------------------------- 1 | Thai Tax -------------------------------------------------------------------------------- /thai_tax/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zeep 2 | pandas -------------------------------------------------------------------------------- /thai_tax/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/print_format/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd3/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd53/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/sales_tax_report/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/purchase_tax_invoice/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/sales_tax_invoice/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/tax_invoice_settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_cert/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_type/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/purchase_tax_report/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_items/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_setting/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/print_format/withholding_tax_cert/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/journal_entry_tax_invoice_detail/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | thai_tax/docs/current 7 | node_modules/ -------------------------------------------------------------------------------- /thai_tax/patches.txt: -------------------------------------------------------------------------------- 1 | [pre_model_sync] 2 | 3 | [post_model_sync] 4 | execute:from thai_tax.install import after_install; after_install() 5 | -------------------------------------------------------------------------------- /thai_tax/public/images/wht/WithholdingTaxCert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittiu/thai_tax/HEAD/thai_tax/public/images/wht/WithholdingTaxCert.png -------------------------------------------------------------------------------- /thai_tax/config/desktop.py: -------------------------------------------------------------------------------- 1 | from frappe import _ 2 | 3 | 4 | def get_data(): 5 | return [{"module_name": "Thai Tax", "type": "module", "label": _("Thai Tax")}] 6 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/sales_tax_invoice/sales_tax_invoice.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Sales Tax Invoice", { 5 | // refresh: function(frm) { 6 | // } 7 | }); 8 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/purchase_tax_invoice/purchase_tax_invoice.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Purchase Tax Invoice", { 5 | // refresh: function(frm) { 6 | // } 7 | }); 8 | -------------------------------------------------------------------------------- /thai_tax/public/js/expense_claim.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Expense Claim", { 2 | refresh(frm) { 3 | frm.set_query("company_tax_address", function () { 4 | return { 5 | filters: { 6 | is_your_company_address: true, 7 | }, 8 | }; 9 | }); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_setting/withholding_tax_setting.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Withholding Tax Setting", { 5 | // refresh: function(frm) { 6 | // } 7 | }); 8 | -------------------------------------------------------------------------------- /thai_tax/public/js/sales_tax_invoice.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Sales Tax Invoice", { 2 | refresh(frm) { 3 | frm.set_query("company_tax_address", function () { 4 | return { 5 | filters: { 6 | is_your_company_address: true, 7 | }, 8 | }; 9 | }); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/sales_tax_invoice/test_sales_tax_invoice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestSalesTaxInvoice(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/public/js/purchase_tax_invoice.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Purchase Tax Invoice", { 2 | refresh(frm) { 3 | frm.set_query("company_tax_address", function () { 4 | return { 5 | filters: { 6 | is_your_company_address: true, 7 | }, 8 | }; 9 | }); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/purchase_tax_invoice/test_purchase_tax_invoice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestPurchaseTaxInvoice(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/tax_invoice_settings/test_tax_invoice_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestTaxInvoiceSettings(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_cert/test_withholding_tax_cert.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestWithholdingTaxCert(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_type/test_withholding_tax_type.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestWithholdingTaxType(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_setting/test_withholding_tax_setting.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestWithholdingTaxSetting(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/tax_invoice_settings/tax_invoice_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class TaxInvoiceSettings(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_cert/withholding_tax_cert.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class WithholdingTaxCert(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_items/withholding_tax_items.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class WithholdingTaxItems(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_setting/withholding_tax_setting.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class WithholdingTaxSetting(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /thai_tax/custom/gl_entry.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | def rename_gl_entry_in_tax_invoice(newname, oldname): 5 | for tax_invoice in ["Sales Tax Invoice", "Purchase Tax Invoice"]: 6 | frappe.db.sql( 7 | f"UPDATE `tab{tax_invoice}` SET gl_entry = %s where gl_entry = %s", 8 | (newname, oldname) 9 | ) 10 | -------------------------------------------------------------------------------- /thai_tax/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/thai_tax" 6 | # headline = "App that does everything" 7 | # sub_heading = "Yes, you got that right the first time, everything" 8 | 9 | 10 | def get_context(context): 11 | context.brand_html = "Thai Tax" 12 | -------------------------------------------------------------------------------- /thai_tax/custom/unreconcile_payment.py: -------------------------------------------------------------------------------- 1 | from .payment_entry import reconcile_undue_tax_gls 2 | 3 | 4 | def unreconcile_undue_tax(doc, method): 5 | """ If bs_reconcile is installed, unreconcile undue tax gls """ 6 | vouchers = [doc.voucher_no] + [r.reference_name for r in doc.allocations] 7 | reconcile_undue_tax_gls(vouchers, unreconcile=True) 8 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/journal_entry_tax_invoice_detail/journal_entry_tax_invoice_detail.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class JournalEntryTaxInvoiceDetail(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Root editor config file 2 | root = true 3 | 4 | # Common settings 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | # python, js indentation settings 12 | [{*.py,*.js,*.vue,*.css,*.scss,*.html}] 13 | indent_style = tab 14 | indent_size = 4 15 | max_line_length = 99 16 | -------------------------------------------------------------------------------- /thai_tax/custom/queries.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | 4 | @frappe.whitelist() 5 | @frappe.validate_and_sanitize_search_inputs 6 | def undue_tax_query(doctype, txt, searchfield, start, page_len, filters): 7 | setting = frappe.get_doc("Tax Invoice Settings") 8 | res = list({setting.purchase_tax_account_undue, setting.sales_tax_account_undue}) 9 | return [[x] for x in res if x] 10 | -------------------------------------------------------------------------------- /thai_tax/custom/journal_entry.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from .payment_entry import reconcile_undue_tax_gls 3 | 4 | 5 | def reconcile_undue_tax(jv, method): 6 | """ If bs_reconcile is installed, reconcile undue tax gls """ 7 | if jv.for_payment: 8 | pay = frappe.get_doc("Payment Entry", jv.for_payment) 9 | vouchers = [jv.name, pay.name] + [r.reference_name for r in pay.references] 10 | reconcile_undue_tax_gls(vouchers) 11 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_cert/withholding_tax_cert.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Withholding Tax Cert", { 5 | refresh(frm) { 6 | frm.set_query("supplier_address", function () { 7 | return { 8 | filters: { 9 | link_doctype: "Supplier", 10 | link_name: frm.doc.supplier, 11 | }, 12 | }; 13 | }); 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_type/withholding_tax_type.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Withholding Tax Type", { 5 | refresh: function (frm) { 6 | if (frm.doc.name === "Auto" && !frappe.boot.developer_mode) { 7 | // make the document read-only 8 | frm.disable_form(); 9 | } else { 10 | frm.enable_save(); 11 | } 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include thai_tax *.css 8 | recursive-include thai_tax *.csv 9 | recursive-include thai_tax *.html 10 | recursive-include thai_tax *.ico 11 | recursive-include thai_tax *.js 12 | recursive-include thai_tax *.json 13 | recursive-include thai_tax *.md 14 | recursive-include thai_tax *.png 15 | recursive-include thai_tax *.py 16 | recursive-include thai_tax *.svg 17 | recursive-include thai_tax *.txt 18 | recursive-exclude thai_tax *.pyc -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open("requirements.txt") as f: 4 | install_requires = f.read().strip().split("\n") 5 | 6 | # get version from __version__ variable in thai_tax/__init__.py 7 | from thai_tax import __version__ as version 8 | 9 | setup( 10 | name="thai_tax", 11 | version=version, 12 | description="Thailand Taxation - VAT, WHT", 13 | author="Kitti U.", 14 | author_email="kittiu@gmail.com", 15 | packages=find_packages(), 16 | zip_safe=False, 17 | include_package_data=True, 18 | install_requires=install_requires, 19 | ) 20 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_type/withholding_tax_type.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.model.document import Document 7 | from frappe.utils import cint, cstr 8 | 9 | 10 | class WithholdingTaxType(Document): 11 | def on_trash(self): 12 | if ( 13 | self.name == "Auto" 14 | and not cint(getattr(frappe.local.conf, "developer_mode", 0)) 15 | and not (frappe.flags.in_migrate or frappe.flags.in_patch) 16 | ): 17 | frappe.throw(_("You are not allowed to delete Auto")) 18 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/add_taxes_to_chart_of_account/add_taxes_to_chart_of_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Create Entry", 3 | "creation": "2023-06-22 12:02:29.107120", 4 | "description": "YYY", 5 | "docstatus": 0, 6 | "doctype": "Onboarding Step", 7 | "idx": 0, 8 | "is_complete": 0, 9 | "is_single": 0, 10 | "is_skipped": 0, 11 | "modified": "2023-06-22 12:02:29.107120", 12 | "modified_by": "Administrator", 13 | "name": "Add Taxes to Chart of Account", 14 | "owner": "Administrator", 15 | "reference_document": "Account", 16 | "show_form_tour": 0, 17 | "show_full_form": 0, 18 | "title": "XXX", 19 | "validate_action": 1 20 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/add_withholding_tax_type/add_withholding_tax_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Create Entry", 3 | "creation": "2023-06-23 10:43:23.606425", 4 | "description": "To withhold tax, you need to create Withholding Tyes, i.e., 1%, 2%, 3% and 5%", 5 | "docstatus": 0, 6 | "doctype": "Onboarding Step", 7 | "idx": 0, 8 | "is_complete": 0, 9 | "is_single": 0, 10 | "is_skipped": 0, 11 | "modified": "2023-06-23 11:59:02.378631", 12 | "modified_by": "Administrator", 13 | "name": "Add Withholding Tax Type", 14 | "owner": "Administrator", 15 | "reference_document": "Withholding Tax Type", 16 | "show_form_tour": 0, 17 | "show_full_form": 0, 18 | "title": "Add Withholding Tax Type", 19 | "validate_action": 1 20 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/sales_tax_invoice/sales_tax_invoice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | import frappe 4 | from frappe.model.document import Document 5 | from frappe.utils import add_months 6 | 7 | 8 | class SalesTaxInvoice(Document): 9 | def validate(self): 10 | self.compute_report_date() 11 | 12 | def on_update_after_submit(self): 13 | if self.get_doc_before_save(): # Some change is made 14 | self.compute_report_date() 15 | 16 | def compute_report_date(self): 17 | if int(self.months_delayed) == 0: 18 | self.db_set("report_date", self.date) 19 | else: 20 | self.db_set("report_date", add_months(self.date, int(self.months_delayed))) 21 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd3/pnd3.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_total_row": 1, 3 | "columns": [], 4 | "creation": "2023-03-14 14:33:48.576965", 5 | "disable_prepared_report": 0, 6 | "disabled": 0, 7 | "docstatus": 0, 8 | "doctype": "Report", 9 | "filters": [], 10 | "idx": 0, 11 | "is_standard": "Yes", 12 | "modified": "2023-03-14 14:33:48.576965", 13 | "modified_by": "Administrator", 14 | "module": "Thai Tax", 15 | "name": "PND3", 16 | "owner": "Administrator", 17 | "prepared_report": 0, 18 | "query": "", 19 | "ref_doctype": "Withholding Tax Cert", 20 | "report_name": "PND3", 21 | "report_type": "Script Report", 22 | "roles": [ 23 | { 24 | "role": "Accounts Manager" 25 | }, 26 | { 27 | "role": "Accounts User" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd53/pnd53.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_total_row": 1, 3 | "columns": [], 4 | "creation": "2023-03-07 10:38:13.525376", 5 | "disable_prepared_report": 0, 6 | "disabled": 0, 7 | "docstatus": 0, 8 | "doctype": "Report", 9 | "filters": [], 10 | "idx": 0, 11 | "is_standard": "Yes", 12 | "modified": "2023-03-14 13:49:42.818372", 13 | "modified_by": "Administrator", 14 | "module": "Thai Tax", 15 | "name": "PND53", 16 | "owner": "Administrator", 17 | "prepared_report": 0, 18 | "query": "", 19 | "ref_doctype": "Withholding Tax Cert", 20 | "report_name": "PND53", 21 | "report_type": "Script Report", 22 | "roles": [ 23 | { 24 | "role": "Accounts Manager" 25 | }, 26 | { 27 | "role": "Accounts User" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /thai_tax/uninstall.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | from thai_tax.constants import HRMS_CUSTOM_FIELDS 4 | 5 | 6 | def before_app_uninstall(app_name): 7 | if app_name == "hrms": 8 | delete_custom_fields(HRMS_CUSTOM_FIELDS) 9 | 10 | 11 | def delete_custom_fields(custom_fields): 12 | """Helper function to delete custom fields""" 13 | for doctypes, fields in custom_fields.items(): 14 | if isinstance(fields, dict): 15 | fields = [fields] 16 | if isinstance(doctypes, str): 17 | doctypes = (doctypes,) 18 | for doctype in doctypes: 19 | frappe.db.delete( 20 | "Custom Field", 21 | { 22 | "fieldname": ("in", [field["fieldname"] for field in fields]), 23 | "dt": doctype, 24 | }, 25 | ) 26 | frappe.clear_cache(doctype=doctype) 27 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/sales_tax_report/sales_tax_report.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_total_row": 1, 3 | "columns": [], 4 | "creation": "2023-03-22 16:47:28.702077", 5 | "disable_prepared_report": 0, 6 | "disabled": 0, 7 | "docstatus": 0, 8 | "doctype": "Report", 9 | "filters": [], 10 | "idx": 0, 11 | "is_standard": "Yes", 12 | "modified": "2023-03-22 21:44:23.331658", 13 | "modified_by": "Administrator", 14 | "module": "Thai Tax", 15 | "name": "Sales Tax Report", 16 | "owner": "Administrator", 17 | "prepared_report": 0, 18 | "query": "", 19 | "ref_doctype": "Sales Tax Invoice", 20 | "report_name": "Sales Tax Report", 21 | "report_type": "Script Report", 22 | "roles": [ 23 | { 24 | "role": "Accounts Manager" 25 | }, 26 | { 27 | "role": "Accounts User" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/purchase_tax_report/purchase_tax_report.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_total_row": 1, 3 | "columns": [], 4 | "creation": "2023-03-20 16:44:27.862127", 5 | "disable_prepared_report": 0, 6 | "disabled": 0, 7 | "docstatus": 0, 8 | "doctype": "Report", 9 | "filters": [], 10 | "idx": 0, 11 | "is_standard": "Yes", 12 | "modified": "2023-03-20 16:44:27.862127", 13 | "modified_by": "Administrator", 14 | "module": "Thai Tax", 15 | "name": "Purchase Tax Report", 16 | "owner": "Administrator", 17 | "prepared_report": 0, 18 | "query": "", 19 | "ref_doctype": "Purchase Tax Invoice", 20 | "report_name": "Purchase Tax Report", 21 | "report_type": "Script Report", 22 | "roles": [ 23 | { 24 | "role": "Accounts Manager" 25 | }, 26 | { 27 | "role": "Accounts User" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/tax_invoice_setting/tax_invoice_setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Show Form Tour", 3 | "creation": "2023-06-22 12:59:15.255686", 4 | "description": "#Tax Invoice Settings\n\nAfter you have created all required tax account, please map them in the settings.", 5 | "docstatus": 0, 6 | "doctype": "Onboarding Step", 7 | "field": "sales_tax_account", 8 | "idx": 0, 9 | "is_complete": 0, 10 | "is_single": 1, 11 | "is_skipped": 0, 12 | "modified": "2023-06-22 14:00:33.605362", 13 | "modified_by": "Administrator", 14 | "name": "Tax Invoice Setting", 15 | "owner": "Administrator", 16 | "reference_document": "Tax Invoice Settings", 17 | "show_form_tour": 0, 18 | "show_full_form": 0, 19 | "title": "Tax Invoice Setting", 20 | "validate_action": 1, 21 | "value_to_validate": "%" 22 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd3/pnd3.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | /* eslint-disable */ 4 | 5 | frappe.query_reports["PND3"] = { 6 | filters: [ 7 | { 8 | fieldname: "year", 9 | label: __("Year"), 10 | fieldtype: "Link", 11 | options: "Fiscal Year", 12 | }, 13 | { 14 | fieldname: "month", 15 | label: __("Month"), 16 | fieldtype: "Select", 17 | options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], 18 | }, 19 | { 20 | fieldname: "company_address", 21 | label: __("Company Address"), 22 | fieldtype: "Link", 23 | options: "Address", 24 | get_query: () => { 25 | return { 26 | filters: { 27 | is_your_company_address: 1, 28 | }, 29 | }; 30 | }, 31 | }, 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd53/pnd53.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | /* eslint-disable */ 4 | 5 | frappe.query_reports["PND53"] = { 6 | filters: [ 7 | { 8 | fieldname: "year", 9 | label: __("Year"), 10 | fieldtype: "Link", 11 | options: "Fiscal Year", 12 | }, 13 | { 14 | fieldname: "month", 15 | label: __("Month"), 16 | fieldtype: "Select", 17 | options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], 18 | }, 19 | { 20 | fieldname: "company_address", 21 | label: __("Company Address"), 22 | fieldtype: "Link", 23 | options: "Address", 24 | get_query: () => { 25 | return { 26 | filters: { 27 | is_your_company_address: 1, 28 | }, 29 | }; 30 | }, 31 | }, 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/tax_report/tax_report.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "View Report", 3 | "creation": "2023-06-23 11:18:50.186869", 4 | "description": "Sales Tax Report and Purchase Tax Repot can be found in separated menu.", 5 | "docstatus": 0, 6 | "doctype": "Onboarding Step", 7 | "idx": 0, 8 | "is_complete": 0, 9 | "is_single": 0, 10 | "is_skipped": 0, 11 | "modified": "2023-06-23 11:32:45.317640", 12 | "modified_by": "Administrator", 13 | "name": "Tax Report", 14 | "owner": "Administrator", 15 | "reference_report": "Sales Tax Report", 16 | "report_description": "You can also go to Purchase Tax Report for the same", 17 | "report_reference_doctype": "Sales Tax Invoice", 18 | "report_type": "Script Report", 19 | "show_form_tour": 0, 20 | "show_full_form": 0, 21 | "title": "Sales / Purchase Tax Report", 22 | "validate_action": 1 23 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/browse_withholding_tax_cert/browse_withholding_tax_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Go to Page", 3 | "action_label": "Browse Withholding Tax Certs", 4 | "creation": "2023-06-23 11:22:48.027326", 5 | "description": "On Payment Entry with withholding tax deduction, button Create Withholing Tax Cert will be visible.\nThese are all Withholding Tax Certs created during payments.", 6 | "docstatus": 0, 7 | "doctype": "Onboarding Step", 8 | "idx": 0, 9 | "is_complete": 0, 10 | "is_single": 0, 11 | "is_skipped": 0, 12 | "modified": "2023-06-23 11:26:09.219232", 13 | "modified_by": "Administrator", 14 | "name": "Browse Withholding Tax Cert", 15 | "owner": "Administrator", 16 | "path": "List/Withholding Tax Cert", 17 | "show_form_tour": 0, 18 | "show_full_form": 0, 19 | "title": "Browse Withholding Tax Cert", 20 | "validate_action": 1 21 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/asset_item/asset_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Create Entry", 3 | "action_label": "Let's create a new Asset item", 4 | "creation": "2021-08-13 14:27:07.277167", 5 | "description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ", 6 | "docstatus": 0, 7 | "doctype": "Onboarding Step", 8 | "form_tour": "Item", 9 | "idx": 0, 10 | "is_complete": 0, 11 | "is_single": 0, 12 | "is_skipped": 0, 13 | "modified": "2021-12-02 11:23:48.158504", 14 | "modified_by": "Administrator", 15 | "name": "Asset Item", 16 | "owner": "Administrator", 17 | "reference_document": "Item", 18 | "show_form_tour": 1, 19 | "show_full_form": 1, 20 | "title": "Create an Asset Item", 21 | "validate_action": 1 22 | } -------------------------------------------------------------------------------- /thai_tax/custom/dashboard_overrides.py: -------------------------------------------------------------------------------- 1 | from frappe import _ 2 | 3 | 4 | def get_dashboard_data_for_purchase_invoice(data): 5 | data["non_standard_fieldnames"].update({"Purchase Tax Invoice": "voucher_no"}) 6 | data["transactions"].append( 7 | {"label": _("Tax Invoice"), "items": ["Purchase Tax Invoice"]} 8 | ) 9 | return data 10 | 11 | 12 | def get_dashboard_data_for_sales_invoice(data): 13 | data["non_standard_fieldnames"].update({"Sales Tax Invoice": "voucher_no"}) 14 | data["transactions"].append( 15 | {"label": _("Tax Invoice"), "items": ["Sales Tax Invoice"]} 16 | ) 17 | return data 18 | 19 | 20 | def get_dashboard_data_for_expense_claim(data): 21 | data["non_standard_fieldnames"].update({"Purchase Tax Invoice": "voucher_no"}) 22 | data["transactions"].append( 23 | {"label": _("Tax Invoice"), "items": ["Purchase Tax Invoice"]} 24 | ) 25 | return data 26 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/tax_invoice_settings/tax_invoice_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on("Tax Invoice Settings", { 5 | onload: function (frm) { 6 | for (let field of [ 7 | "sales_tax_account", 8 | "sales_tax_account_undue", 9 | "purchase_tax_account", 10 | "purchase_tax_account_undue", 11 | ]) { 12 | frm.set_query(field, function (doc) { 13 | return { 14 | filters: { 15 | account_type: "Tax", 16 | company: doc.company, 17 | }, 18 | }; 19 | }); 20 | } 21 | }, 22 | 23 | company: function (frm) { 24 | frm.set_value("sales_tax_account", null); 25 | frm.set_value("sales_tax_account_undue", null); 26 | frm.set_value("purchase_tax_account", null); 27 | frm.set_value("purchase_tax_account_undue", null); 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/browse_sales_tax_invoices/browse_sales_tax_invoices.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Go to Page", 3 | "callback_message": "", 4 | "callback_title": "Sales Tax Invoices are created after you have record tax.", 5 | "creation": "2023-06-23 11:11:48.094053", 6 | "description": "Sales Tax Invoice is created when tax ledger is recorded, which could be,\n1. On Sales Invoice for case selling product\n2. On Payment for case selling service", 7 | "docstatus": 0, 8 | "doctype": "Onboarding Step", 9 | "idx": 0, 10 | "is_complete": 0, 11 | "is_single": 0, 12 | "is_skipped": 0, 13 | "modified": "2023-06-23 11:17:45.307520", 14 | "modified_by": "Administrator", 15 | "name": "Browse Sales Tax Invoices", 16 | "owner": "Administrator", 17 | "path": "List/Sales Tax Invoice", 18 | "show_form_tour": 0, 19 | "show_full_form": 0, 20 | "title": "Browse Sales Tax Invoices", 21 | "validate_action": 1 22 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/browse_purchase_tax_invoices/browse_purchase_tax_invoices.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Go to Page", 3 | "callback_message": "", 4 | "callback_title": "Purchase Tax Invoices are created after you have record tax.", 5 | "creation": "2023-06-23 11:16:19.993908", 6 | "description": "Purchase Tax Invoice is created when tax ledger is recorded, which could be,\n1. On Purchase Invoice for case buying product\n2. On Payment for case buying service and can record tax on payment\n3. On Journal Entry for case buying service and record tax afterward", 7 | "docstatus": 0, 8 | "doctype": "Onboarding Step", 9 | "idx": 0, 10 | "is_complete": 0, 11 | "is_single": 0, 12 | "is_skipped": 0, 13 | "modified": "2023-06-23 11:17:34.911128", 14 | "modified_by": "Administrator", 15 | "name": "Browse Purchase Tax Invoices", 16 | "owner": "Administrator", 17 | "path": "List/Purchase Tax Invoice", 18 | "show_form_tour": 0, 19 | "show_full_form": 0, 20 | "title": "Browse Purchase Tax Invoices", 21 | "validate_action": 1 22 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/withholding_tax_report_(pnd)/withholding_tax_report_(pnd).json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Go to Page", 3 | "action_label": "View PND Report", 4 | "callback_message": "PND53 can be found in separated menu.", 5 | "callback_title": "PND3 Report", 6 | "creation": "2023-06-23 11:27:09.437734", 7 | "description": "PND3 and PND53 reports are summay of Withholding Ta Certs", 8 | "docstatus": 0, 9 | "doctype": "Onboarding Step", 10 | "idx": 0, 11 | "is_complete": 0, 12 | "is_single": 0, 13 | "is_skipped": 0, 14 | "modified": "2023-06-23 11:32:29.566442", 15 | "modified_by": "Administrator", 16 | "name": "Withholding Tax Report (PND)", 17 | "owner": "Administrator", 18 | "path": "query-report/PND3", 19 | "reference_report": "PND3", 20 | "report_description": "PND53 can also be found in separated menu.", 21 | "report_reference_doctype": "Withholding Tax Cert", 22 | "report_type": "Script Report", 23 | "show_form_tour": 0, 24 | "show_full_form": 0, 25 | "title": "Withholding Tax Report (PND)", 26 | "validate_action": 1 27 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/company_set_up/company_set_up.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Create Entry", 3 | "action_label": "Let's review your Company", 4 | "creation": "2021-11-22 11:55:48.931427", 5 | "description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n", 6 | "docstatus": 0, 7 | "doctype": "Onboarding Step", 8 | "idx": 1, 9 | "is_complete": 0, 10 | "is_single": 0, 11 | "is_skipped": 0, 12 | "modified": "2023-05-15 09:18:42.895537", 13 | "modified_by": "Administrator", 14 | "name": "Company Set Up", 15 | "owner": "Administrator", 16 | "reference_document": "Company", 17 | "show_form_tour": 1, 18 | "show_full_form": 1, 19 | "title": "Set Up a Company", 20 | "validate_action": 1 21 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_setting/withholding_tax_setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-03-04 12:09:31.842524", 5 | "default_view": "List", 6 | "doctype": "DocType", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "wht_cert_image" 11 | ], 12 | "fields": [ 13 | { 14 | "fieldname": "wht_cert_image", 15 | "fieldtype": "Attach Image", 16 | "label": "WHT Cert Image" 17 | } 18 | ], 19 | "image_field": "wht_cert_image", 20 | "index_web_pages_for_search": 1, 21 | "issingle": 1, 22 | "links": [], 23 | "modified": "2023-03-04 12:14:13.207499", 24 | "modified_by": "Administrator", 25 | "module": "Thai Tax", 26 | "name": "Withholding Tax Setting", 27 | "owner": "Administrator", 28 | "permissions": [ 29 | { 30 | "create": 1, 31 | "delete": 1, 32 | "email": 1, 33 | "print": 1, 34 | "read": 1, 35 | "role": "System Manager", 36 | "share": 1, 37 | "write": 1 38 | } 39 | ], 40 | "sort_field": "modified", 41 | "sort_order": "DESC", 42 | "states": [] 43 | } -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | B001, 4 | B007, 5 | B009, 6 | B010, 7 | B950, 8 | E101, 9 | E111, 10 | E114, 11 | E116, 12 | E117, 13 | E121, 14 | E122, 15 | E123, 16 | E124, 17 | E125, 18 | E126, 19 | E127, 20 | E128, 21 | E131, 22 | E201, 23 | E202, 24 | E203, 25 | E211, 26 | E221, 27 | E222, 28 | E223, 29 | E224, 30 | E225, 31 | E226, 32 | E228, 33 | E231, 34 | E241, 35 | E242, 36 | E251, 37 | E261, 38 | E262, 39 | E265, 40 | E266, 41 | E271, 42 | E272, 43 | E273, 44 | E274, 45 | E301, 46 | E302, 47 | E303, 48 | E305, 49 | E306, 50 | E402, 51 | E501, 52 | E502, 53 | E701, 54 | E702, 55 | E703, 56 | E741, 57 | F401, 58 | F403, 59 | F405, 60 | W191, 61 | W291, 62 | W292, 63 | W293, 64 | W391, 65 | W503, 66 | W504, 67 | E711, 68 | E129, 69 | F841, 70 | E713, 71 | E712, 72 | B028, 73 | 74 | max-line-length = 200 75 | exclude=,test_*.py -------------------------------------------------------------------------------- /thai_tax/thai_tax/onboarding_step/add_thai_tax_accounts/add_thai_tax_accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "Go to Page", 3 | "action_label": "Let's create tax accounts", 4 | "callback_message": "", 5 | "callback_title": "", 6 | "creation": "2023-06-22 12:36:13.122910", 7 | "description": "# Add Thai Tax Accounts\n\nPlease make sure you have all required Tax Accounts, you can name it as you want.\n1. Purchase Tax -- Current Asset, Rate = 7\n2. Undue Purchase Tax -- Currency Asset, Rate = 7\n3. Sales Tax -- Current Liability, Rate = 7\n4. Undue Sales Tax -- Current Liability, Rate = 7\n5. Withholding Tax -- Current Liability\n\n", 8 | "docstatus": 0, 9 | "doctype": "Onboarding Step", 10 | "idx": 0, 11 | "is_complete": 0, 12 | "is_single": 0, 13 | "is_skipped": 0, 14 | "modified": "2023-06-23 11:20:25.502674", 15 | "modified_by": "Administrator", 16 | "name": "Add Thai Tax Accounts", 17 | "owner": "Administrator", 18 | "path": "account/view/tree", 19 | "reference_document": "Account", 20 | "show_form_tour": 0, 21 | "show_full_form": 1, 22 | "title": "Add Thai Tax Accounts", 23 | "validate_action": 1 24 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/purchase_tax_invoice/purchase_tax_invoice.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | import frappe 4 | from frappe.model.document import Document 5 | from frappe.utils import add_months 6 | 7 | 8 | class PurchaseTaxInvoice(Document): 9 | def validate(self): 10 | self.compute_report_date() 11 | 12 | def on_update_after_submit(self): 13 | if self.get_doc_before_save(): # Some change is made 14 | self.compute_report_date() 15 | if self.voucher_type and self.voucher_no: 16 | origin_doc = frappe.get_doc(self.voucher_type, self.voucher_no) 17 | if self.voucher_type == "Journal Entry": 18 | gl = frappe.get_doc("GL Entry", self.gl_entry) 19 | origin_doc = frappe.get_doc( 20 | "Journal Entry Tax Invoice Detail", gl.voucher_detail_no 21 | ) 22 | origin_doc.tax_invoice_number = self.number 23 | origin_doc.tax_invoice_date = self.date 24 | origin_doc.save(ignore_permissions=True) 25 | 26 | def compute_report_date(self): 27 | if int(self.months_delayed) == 0: 28 | self.db_set("report_date", self.date) 29 | else: 30 | self.db_set("report_date", add_months(self.date, int(self.months_delayed))) 31 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/module_onboarding/thai_tax/thai_tax.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_roles": [ 3 | { 4 | "role": "Accounts Manager" 5 | }, 6 | { 7 | "role": "Accounts User" 8 | } 9 | ], 10 | "creation": "2023-06-22 11:58:50.650153", 11 | "docstatus": 0, 12 | "doctype": "Module Onboarding", 13 | "documentation_url": "https://github.com/kittiu/thai_tax/blob/main/README.md", 14 | "idx": 0, 15 | "is_complete": 0, 16 | "modified": "2023-06-23 11:59:08.144024", 17 | "modified_by": "Administrator", 18 | "module": "Thai Tax", 19 | "name": "Thai Tax", 20 | "owner": "Administrator", 21 | "steps": [ 22 | { 23 | "step": "Add Thai Tax Accounts" 24 | }, 25 | { 26 | "step": "Tax Invoice Setting" 27 | }, 28 | { 29 | "step": "Add Withholding Tax Type" 30 | }, 31 | { 32 | "step": "Browse Sales Tax Invoices" 33 | }, 34 | { 35 | "step": "Browse Purchase Tax Invoices" 36 | }, 37 | { 38 | "step": "Tax Report" 39 | }, 40 | { 41 | "step": "Browse Withholding Tax Cert" 42 | }, 43 | { 44 | "step": "Withholding Tax Report (PND)" 45 | } 46 | ], 47 | "subtitle": "Tax Account Setup, Withholding Tax Type", 48 | "success_message": "The Thai Tax Module is all set up!", 49 | "title": "Setup Thai Tax" 50 | } -------------------------------------------------------------------------------- /thai_tax/public/js/address.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Address", { 2 | refresh(frm) { 3 | // Add button to use VAT Service 4 | frm.add_custom_button(__("Get Address by Tax ID"), function () { 5 | const fields = [ 6 | { 7 | fieldtype: "Data", 8 | label: __("Tax ID"), 9 | fieldname: "tax_id", 10 | reqd: 1, 11 | }, 12 | { 13 | fieldtype: "Data", 14 | label: __("Branch"), 15 | fieldname: "branch", 16 | default: "00000", 17 | reqd: 1, 18 | }, 19 | ]; 20 | frappe.prompt( 21 | fields, 22 | function (filters) { 23 | frm.events.get_address_by_tax_id(frm, filters); 24 | }, 25 | __("RD VAT Service"), 26 | __("Get Address") 27 | ); 28 | }); 29 | }, 30 | 31 | get_address_by_tax_id: function (frm, filters) { 32 | return frappe.call({ 33 | method: "thai_tax.utils.get_address_by_tax_id", 34 | args: { 35 | tax_id: filters.tax_id, 36 | branch: filters.branch, 37 | }, 38 | callback: function (r) { 39 | cur_frm.set_value("address_title", r.message["name"]); 40 | cur_frm.set_value("address_line1", r.message["address_line1"]); 41 | cur_frm.set_value("city", r.message["city"]); 42 | cur_frm.set_value("county", r.message["county"]); 43 | cur_frm.set_value("state", r.message["state"]); 44 | cur_frm.set_value("pincode", r.message["pincode"]); 45 | }, 46 | }); 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /thai_tax/public/js/journal_entry.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Journal Entry", { 2 | setup(frm) { 3 | frm.add_fetch("customer", "customer_name", "party_name"); 4 | frm.add_fetch("supplier", "supplier_name", "party_name"); 5 | }, 6 | refresh(frm) { 7 | frm.set_query("company_tax_address", function () { 8 | return { 9 | filters: { 10 | is_your_company_address: true, 11 | }, 12 | }; 13 | }); 14 | frm.set_query("company_tax_address", "tax_invoice_details", () => { 15 | return { 16 | filters: { 17 | is_your_company_address: true, 18 | }, 19 | }; 20 | }); 21 | }, 22 | }); 23 | 24 | frappe.ui.form.on("Journal Entry Tax Invoice Detail", { 25 | customer(frm, cdt, cdn) { 26 | let row = locals[cdt][cdn]; 27 | if (row.customer) { 28 | frappe.model.set_value(cdt, cdn, "supplier", ""); 29 | } 30 | }, 31 | supplier(frm, cdt, cdn) { 32 | let row = locals[cdt][cdn]; 33 | if (row.supplier) { 34 | frappe.model.set_value(cdt, cdn, "customer", ""); 35 | } 36 | }, 37 | }); 38 | 39 | frappe.ui.form.on("Journal Entry Account", { 40 | customer(frm, cdt, cdn) { 41 | let row = locals[cdt][cdn]; 42 | if (row.customer) { 43 | frappe.model.set_value(cdt, cdn, "supplier", ""); 44 | } 45 | }, 46 | supplier(frm, cdt, cdn) { 47 | let row = locals[cdt][cdn]; 48 | if (row.supplier) { 49 | frappe.model.set_value(cdt, cdn, "customer", ""); 50 | } 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_type/withholding_tax_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "field:title", 5 | "creation": "2023-03-04 12:08:38.271210", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "editable_grid": 1, 9 | "engine": "InnoDB", 10 | "field_order": [ 11 | "title", 12 | "percent", 13 | "account" 14 | ], 15 | "fields": [ 16 | { 17 | "fieldname": "title", 18 | "fieldtype": "Data", 19 | "in_list_view": 1, 20 | "label": "Title", 21 | "mandatory_depends_on": "eval:doc.title == 'Auto'", 22 | "reqd": 1, 23 | "unique": 1 24 | }, 25 | { 26 | "description": "% of withholding tax", 27 | "fieldname": "percent", 28 | "fieldtype": "Int", 29 | "in_list_view": 1, 30 | "label": "Percent", 31 | "reqd": 1 32 | }, 33 | { 34 | "fieldname": "account", 35 | "fieldtype": "Link", 36 | "in_list_view": 1, 37 | "label": "Account", 38 | "options": "Account", 39 | "reqd": 1 40 | } 41 | ], 42 | "index_web_pages_for_search": 1, 43 | "links": [], 44 | "modified": "2024-10-10 15:27:17.778852", 45 | "modified_by": "Administrator", 46 | "module": "Thai Tax", 47 | "name": "Withholding Tax Type", 48 | "naming_rule": "By fieldname", 49 | "owner": "Administrator", 50 | "permissions": [ 51 | { 52 | "create": 1, 53 | "delete": 1, 54 | "email": 1, 55 | "export": 1, 56 | "print": 1, 57 | "read": 1, 58 | "report": 1, 59 | "role": "System Manager", 60 | "share": 1, 61 | "write": 1 62 | }, 63 | { 64 | "email": 1, 65 | "export": 1, 66 | "print": 1, 67 | "read": 1, 68 | "report": 1, 69 | "role": "Accounts User", 70 | "share": 1 71 | } 72 | ], 73 | "sort_field": "title", 74 | "sort_order": "DESC", 75 | "states": [], 76 | "title_field": "title" 77 | } -------------------------------------------------------------------------------- /thai_tax/install.py: -------------------------------------------------------------------------------- 1 | import click 2 | import frappe 3 | from frappe.custom.doctype.custom_field.custom_field import \ 4 | create_custom_fields 5 | from frappe.custom.doctype.property_setter.property_setter import \ 6 | make_property_setter 7 | 8 | from thai_tax.constants import (ERP_CUSTOM_FIELDS, ERP_PROPERTY_SETTERS, 9 | HRMS_CUSTOM_FIELDS) 10 | 11 | 12 | def after_install(): 13 | try: 14 | print("Setting up Thai Tax...") 15 | make_custom_fields() 16 | make_property_setters() 17 | click.secho("Thank you for installing Thai Tax!", fg="green") 18 | except Exception as e: 19 | BUG_REPORT_URL = "https://github.com/kittiu/thai_tax/issues/new" 20 | click.secho( 21 | "Installation for Thai Tax app failed due to an error." 22 | " Please try re-installing the app or" 23 | f" report the issue on {BUG_REPORT_URL} if not resolved.", 24 | fg="bright_red", 25 | ) 26 | raise e 27 | 28 | 29 | def make_custom_fields(): 30 | print("Setup custom fields for erpnext...") 31 | create_custom_fields(ERP_CUSTOM_FIELDS, ignore_validate=True) 32 | if "hrms" in frappe.get_installed_apps(): 33 | print("Setup custom fields for hrms...") 34 | create_custom_fields(HRMS_CUSTOM_FIELDS, ignore_validate=True) 35 | 36 | 37 | def make_property_setters(): 38 | print("Setup property setters for erpnext...") 39 | for doctypes, property_setters in ERP_PROPERTY_SETTERS.items(): 40 | if isinstance(doctypes, str): 41 | doctypes = (doctypes,) 42 | for doctype in doctypes: 43 | for property_setter in property_setters: 44 | for_doctype = not property_setter[0] 45 | make_property_setter(doctype, *property_setter, for_doctype) 46 | 47 | 48 | def after_app_install(app_name): 49 | if app_name == "hrms": 50 | create_custom_fields(HRMS_CUSTOM_FIELDS, ignore_validate=True) 51 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: 'node_modules|.git' 2 | default_stages: [commit] 3 | fail_fast: false 4 | 5 | 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v4.3.0 9 | hooks: 10 | - id: trailing-whitespace 11 | files: "frappe.*" 12 | exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" 13 | - id: check-yaml 14 | - id: no-commit-to-branch 15 | args: ['--branch', 'develop'] 16 | - id: check-merge-conflict 17 | - id: check-ast 18 | - id: check-json 19 | - id: check-toml 20 | - id: check-yaml 21 | - id: debug-statements 22 | 23 | - repo: https://github.com/asottile/pyupgrade 24 | rev: v2.34.0 25 | hooks: 26 | - id: pyupgrade 27 | args: ['--py310-plus'] 28 | 29 | - repo: https://github.com/frappe/black 30 | rev: 951ccf4d5bb0d692b457a5ebc4215d755618eb68 31 | hooks: 32 | - id: black 33 | 34 | - repo: https://github.com/pre-commit/mirrors-prettier 35 | rev: v2.7.1 36 | hooks: 37 | - id: prettier 38 | types_or: [javascript] 39 | # Ignore any files that might contain jinja / bundles 40 | exclude: | 41 | (?x)^( 42 | frappe/public/dist/.*| 43 | .*node_modules.*| 44 | .*boilerplate.*| 45 | frappe/www/website_script.js| 46 | frappe/templates/includes/.*| 47 | frappe/public/js/lib/.* 48 | )$ 49 | 50 | 51 | - repo: https://github.com/PyCQA/isort 52 | rev: 5.12.0 53 | hooks: 54 | - id: isort 55 | 56 | - repo: https://github.com/PyCQA/flake8 57 | rev: 5.0.4 58 | hooks: 59 | - id: flake8 60 | additional_dependencies: ['flake8-bugbear',] 61 | 62 | ci: 63 | autoupdate_schedule: weekly 64 | skip: [] 65 | submodules: false 66 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/sales_tax_report/sales_tax_report.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | /* eslint-disable */ 4 | 5 | frappe.query_reports["Sales Tax Report"] = { 6 | filters: [ 7 | { 8 | fieldname: "filter_based_on", 9 | label: __("Filter Based On"), 10 | fieldtype: "Select", 11 | options: ["Fiscal Year", "Date Range"], 12 | default: ["Fiscal Year"], 13 | reqd: 1, 14 | on_change: function () { 15 | let filter_based_on = frappe.query_report.get_filter_value("filter_based_on"); 16 | frappe.query_report.toggle_filter_display( 17 | "year", 18 | filter_based_on === "Date Range" 19 | ); 20 | frappe.query_report.toggle_filter_display( 21 | "month", 22 | filter_based_on === "Date Range" 23 | ); 24 | frappe.query_report.toggle_filter_display( 25 | "start_date", 26 | filter_based_on === "Fiscal Year" 27 | ); 28 | frappe.query_report.toggle_filter_display( 29 | "end_date", 30 | filter_based_on === "Fiscal Year" 31 | ); 32 | 33 | frappe.query_report.refresh(); 34 | }, 35 | }, 36 | { 37 | fieldname: "year", 38 | label: __("Year"), 39 | fieldtype: "Link", 40 | options: "Fiscal Year", 41 | }, 42 | { 43 | fieldname: "month", 44 | label: __("Month"), 45 | fieldtype: "Select", 46 | options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], 47 | }, 48 | { 49 | fieldname: "start_date", 50 | label: __("Start Date"), 51 | fieldtype: "Date", 52 | }, 53 | { 54 | fieldname: "end_date", 55 | label: __("End Date"), 56 | fieldtype: "Date", 57 | }, 58 | { 59 | fieldname: "company_tax_address", 60 | label: __("Company Address"), 61 | fieldtype: "Link", 62 | options: "Address", 63 | get_query: () => { 64 | return { 65 | filters: { 66 | is_your_company_address: 1, 67 | }, 68 | }; 69 | }, 70 | }, 71 | ], 72 | }; 73 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/purchase_tax_report/purchase_tax_report.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Kitti U. and contributors 2 | // For license information, please see license.txt 3 | /* eslint-disable */ 4 | 5 | frappe.query_reports["Purchase Tax Report"] = { 6 | filters: [ 7 | { 8 | fieldname: "filter_based_on", 9 | label: __("Filter Based On"), 10 | fieldtype: "Select", 11 | options: ["Fiscal Year", "Date Range"], 12 | default: ["Fiscal Year"], 13 | reqd: 1, 14 | on_change: function () { 15 | let filter_based_on = frappe.query_report.get_filter_value("filter_based_on"); 16 | frappe.query_report.toggle_filter_display( 17 | "year", 18 | filter_based_on === "Date Range" 19 | ); 20 | frappe.query_report.toggle_filter_display( 21 | "month", 22 | filter_based_on === "Date Range" 23 | ); 24 | frappe.query_report.toggle_filter_display( 25 | "start_date", 26 | filter_based_on === "Fiscal Year" 27 | ); 28 | frappe.query_report.toggle_filter_display( 29 | "end_date", 30 | filter_based_on === "Fiscal Year" 31 | ); 32 | 33 | frappe.query_report.refresh(); 34 | }, 35 | }, 36 | { 37 | fieldname: "year", 38 | label: __("Year"), 39 | fieldtype: "Link", 40 | options: "Fiscal Year", 41 | }, 42 | { 43 | fieldname: "month", 44 | label: __("Month"), 45 | fieldtype: "Select", 46 | options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], 47 | }, 48 | { 49 | fieldname: "start_date", 50 | label: __("Start Date"), 51 | fieldtype: "Date", 52 | }, 53 | { 54 | fieldname: "end_date", 55 | label: __("End Date"), 56 | fieldtype: "Date", 57 | }, 58 | { 59 | fieldname: "company_tax_address", 60 | label: __("Company Address"), 61 | fieldtype: "Link", 62 | options: "Address", 63 | get_query: () => { 64 | return { 65 | filters: { 66 | is_your_company_address: 1, 67 | }, 68 | }; 69 | }, 70 | }, 71 | ], 72 | }; 73 | -------------------------------------------------------------------------------- /thai_tax/custom/employee_advance.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe import _ 3 | from frappe.query_builder.functions import Sum 4 | from frappe.utils import flt 5 | from hrms.hr.doctype.employee_advance.employee_advance import EmployeeAdvance 6 | 7 | 8 | class EmployeeAdvanceOverPayment(frappe.ValidationError): 9 | pass 10 | 11 | 12 | class ThaiTaxEmployeeAdvance(EmployeeAdvance): 13 | 14 | # For python bug fix only, to be removed if fixed in the core 15 | def set_total_advance_paid(self): 16 | gle = frappe.qb.DocType("GL Entry") 17 | 18 | paid_amount = ( 19 | frappe.qb.from_(gle) 20 | .select(Sum(gle.debit).as_("paid_amount")) 21 | .where( 22 | (gle.against_voucher_type == "Employee Advance") 23 | & (gle.against_voucher == self.name) 24 | & (gle.party_type == "Employee") 25 | & (gle.party == self.employee) 26 | & (gle.docstatus == 1) 27 | & (gle.is_cancelled == 0) 28 | ) 29 | ).run(as_dict=True)[0].paid_amount or 0 30 | 31 | return_amount = ( 32 | frappe.qb.from_(gle) 33 | .select(Sum(gle.credit).as_("return_amount")) 34 | .where( 35 | (gle.against_voucher_type == "Employee Advance") 36 | & (gle.voucher_type != "Expense Claim") 37 | & (gle.against_voucher == self.name) 38 | & (gle.party_type == "Employee") 39 | & (gle.party == self.employee) 40 | & (gle.docstatus == 1) 41 | & (gle.is_cancelled == 0) 42 | ) 43 | ).run(as_dict=True)[0].return_amount or 0 44 | 45 | if paid_amount != 0: 46 | paid_amount = flt(paid_amount) / flt(self.exchange_rate) 47 | if return_amount != 0: 48 | return_amount = flt(return_amount) / flt(self.exchange_rate) 49 | 50 | if flt(paid_amount) > self.advance_amount: 51 | frappe.throw( 52 | _("Row {0}# Paid Amount cannot be greater than requested advance amount"), 53 | EmployeeAdvanceOverPayment, 54 | ) 55 | 56 | # FIX: because python result in 2000.0-1780.2 = 219.79999999999995 57 | # if flt(return_amount) > self.paid_amount - self.claimed_amount: 58 | if flt(return_amount) > flt(self.paid_amount - self.claimed_amount, 2): 59 | frappe.throw(_("Return amount cannot be greater unclaimed amount")) 60 | 61 | self.db_set("paid_amount", paid_amount) 62 | self.db_set("return_amount", return_amount) 63 | self.set_status(update=True) 64 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/tax_invoice_settings/tax_invoice_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-03-04 12:11:10.337895", 5 | "default_view": "List", 6 | "doctype": "DocType", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "company", 11 | "section_break_2", 12 | "sales_tax_account", 13 | "sales_tax_account_undue", 14 | "column_break_5", 15 | "purchase_tax_account", 16 | "purchase_tax_account_undue" 17 | ], 18 | "fields": [ 19 | { 20 | "fieldname": "company", 21 | "fieldtype": "Link", 22 | "in_list_view": 1, 23 | "label": "Company", 24 | "options": "Company", 25 | "reqd": 1 26 | }, 27 | { 28 | "fieldname": "section_break_2", 29 | "fieldtype": "Section Break", 30 | "label": "Tax Invoice Account" 31 | }, 32 | { 33 | "fieldname": "sales_tax_account", 34 | "fieldtype": "Link", 35 | "in_list_view": 1, 36 | "label": "Sales Tax Account", 37 | "options": "Account", 38 | "reqd": 1 39 | }, 40 | { 41 | "fieldname": "column_break_5", 42 | "fieldtype": "Column Break" 43 | }, 44 | { 45 | "fieldname": "purchase_tax_account", 46 | "fieldtype": "Link", 47 | "in_list_view": 1, 48 | "label": "Purchase Tax Account", 49 | "options": "Account", 50 | "reqd": 1 51 | }, 52 | { 53 | "fieldname": "sales_tax_account_undue", 54 | "fieldtype": "Link", 55 | "label": "Sales Tax Account Undue", 56 | "options": "Account" 57 | }, 58 | { 59 | "fieldname": "purchase_tax_account_undue", 60 | "fieldtype": "Link", 61 | "label": "Purchase Tax Account Undue", 62 | "options": "Account" 63 | } 64 | ], 65 | "index_web_pages_for_search": 1, 66 | "issingle": 1, 67 | "links": [], 68 | "modified": "2023-06-22 09:27:29.366931", 69 | "modified_by": "Administrator", 70 | "module": "Thai Tax", 71 | "name": "Tax Invoice Settings", 72 | "owner": "Administrator", 73 | "permissions": [ 74 | { 75 | "create": 1, 76 | "read": 1, 77 | "role": "Accounts Manager", 78 | "write": 1 79 | }, 80 | { 81 | "read": 1, 82 | "role": "Sales User" 83 | }, 84 | { 85 | "read": 1, 86 | "role": "Purchase User" 87 | } 88 | ], 89 | "sort_field": "modified", 90 | "sort_order": "DESC", 91 | "states": [], 92 | "track_changes": 1 93 | } -------------------------------------------------------------------------------- /thai_tax/public/js/withholding_tax_cert.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Withholding Tax Cert", { 2 | refresh(frm) { 3 | frm.set_query("company_address", function (doc) { 4 | return { 5 | query: "frappe.contacts.doctype.address.address.address_query", 6 | filters: { 7 | link_doctype: "Company", 8 | link_name: doc.company, 9 | }, 10 | }; 11 | }); 12 | frm.set_query("voucher_type", function () { 13 | return { 14 | filters: { 15 | name: ["in", ["Payment Entry", "Journal Entry"]], 16 | }, 17 | }; 18 | }); 19 | }, 20 | }); 21 | 22 | frappe.ui.form.on("Withholding Tax Items", { 23 | // Helper to calculate tax amount from given rate 24 | tax_rate: function (frm, cdt, cdn) { 25 | var row = locals[cdt][cdn]; 26 | frappe.model.set_value(cdt, cdn, "tax_amount", (row.tax_base * row.tax_rate) / 100); 27 | }, 28 | // Auto assign income type description 29 | type_of_income: function (frm, cdt, cdn) { 30 | var row = locals[cdt][cdn]; 31 | var vals = { 32 | 1: "เงินเดือน ค่าจ้าง ฯลฯ 40(1)", 33 | 2: "ค่าธรรมเนียม ค่านายหน้า ฯลฯ 40(2)", 34 | 3: "ค่าแห่งลิขสิทธิ์ ฯลฯ 40(3)", 35 | 4: "ดอกเบี้ย ฯลฯ 40(4)ก", 36 | "4.1.1": 37 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (1.1) กิจการที่ต้องเสียภาษีเงินได้นิติบุคคลร้อยละ 30 ของกำไรสุทธิ", 38 | "4.1.2": 39 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (1.2) กิจการที่ต้องเสียภาษีเงินได้นิติบุคคลร้อยละ 25 ของกำไรสุทธิ", 40 | "4.1.3": 41 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (1.3) กิจการที่ต้องเสียภาษีเงินได้นิติบุคคลร้อยละ 20 ของกำไรสุทธิ", 42 | "4.1.4": 43 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (1.4) กิจการที่ต้องเสียภาษีเงินได้นิติบุคคลร้อยละ อื่นๆ (ระบุ) ของกำไรสุทธิ", 44 | "4.2.1": 45 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (2.1) กำไรสุทธิกิจการที่ได้รับยกเว้นภาษีเงินได้นิติบุคคล", 46 | "4.2.2": 47 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (2.2) ได้รับยกเว้นไม่ต้องนำมารวมคำนวณเป็นรายได้", 48 | "4.2.3": 49 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (2.3) กำไรสุทธิส่วนที่หักผลขาดทุนสุทธิยกมาไม่เกิน 5 ปี", 50 | "4.2.4": 51 | "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (2.4) กำไรที่รับรู้ทางบัญชีโดยวิธีส่วนได้เสีย", 52 | "4.2.5": "เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ 40(4)ข (2.5) อื่นๆ (ระบุ)", 53 | 5: "ค่าจ้างทำของ ค่าบริการ ค่าเช่า ค่าขนส่ง ฯลฯ 3 เตรส", 54 | 6: "อื่นๆ (ระบุ)", 55 | }; 56 | frappe.model.set_value(cdt, cdn, "description", vals[row.type_of_income]); 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/journal_entry_tax_invoice_detail/journal_entry_tax_invoice_detail.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-07-30 10:00:38.519032", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "company_tax_address", 10 | "column_break_gg3aa", 11 | "party_name", 12 | "section_break_gh6bu", 13 | "supplier", 14 | "column_break_b7ziy", 15 | "customer", 16 | "section_break_rpmxt", 17 | "tax_invoice_number", 18 | "tax_invoice_date", 19 | "column_break_bxvza", 20 | "tax_base_amount", 21 | "tax_amount" 22 | ], 23 | "fields": [ 24 | { 25 | "fieldname": "company_tax_address", 26 | "fieldtype": "Link", 27 | "label": "Company Tax Address", 28 | "options": "Address" 29 | }, 30 | { 31 | "fieldname": "supplier", 32 | "fieldtype": "Link", 33 | "label": "Supplier", 34 | "options": "Supplier" 35 | }, 36 | { 37 | "allow_on_submit": 1, 38 | "fieldname": "tax_invoice_number", 39 | "fieldtype": "Data", 40 | "in_list_view": 1, 41 | "in_standard_filter": 1, 42 | "label": "Tax Invoice Number" 43 | }, 44 | { 45 | "allow_on_submit": 1, 46 | "fieldname": "tax_invoice_date", 47 | "fieldtype": "Date", 48 | "in_list_view": 1, 49 | "label": "Tax Invoice Date" 50 | }, 51 | { 52 | "fieldname": "tax_base_amount", 53 | "fieldtype": "Float", 54 | "in_list_view": 1, 55 | "label": "Tax Base Amount", 56 | "precision": "2", 57 | "read_only": 1 58 | }, 59 | { 60 | "fieldname": "tax_amount", 61 | "fieldtype": "Float", 62 | "in_list_view": 1, 63 | "label": "Tax Amount", 64 | "precision": "2", 65 | "read_only": 1 66 | }, 67 | { 68 | "fieldname": "customer", 69 | "fieldtype": "Link", 70 | "label": "Customer", 71 | "options": "Customer" 72 | }, 73 | { 74 | "fieldname": "party_name", 75 | "fieldtype": "Data", 76 | "in_list_view": 1, 77 | "label": "Party Name", 78 | "read_only": 1 79 | }, 80 | { 81 | "fieldname": "column_break_gg3aa", 82 | "fieldtype": "Column Break" 83 | }, 84 | { 85 | "fieldname": "section_break_gh6bu", 86 | "fieldtype": "Section Break" 87 | }, 88 | { 89 | "fieldname": "column_break_b7ziy", 90 | "fieldtype": "Column Break" 91 | }, 92 | { 93 | "fieldname": "section_break_rpmxt", 94 | "fieldtype": "Section Break" 95 | }, 96 | { 97 | "fieldname": "column_break_bxvza", 98 | "fieldtype": "Column Break" 99 | } 100 | ], 101 | "index_web_pages_for_search": 1, 102 | "istable": 1, 103 | "links": [], 104 | "modified": "2023-07-30 15:33:54.967313", 105 | "modified_by": "Administrator", 106 | "module": "Thai Tax", 107 | "name": "Journal Entry Tax Invoice Detail", 108 | "owner": "Administrator", 109 | "permissions": [], 110 | "sort_field": "modified", 111 | "sort_order": "DESC", 112 | "states": [] 113 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/form_tour/tax_invoice_settings/tax_invoice_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "creation": "2023-06-22 13:05:27.078306", 3 | "docstatus": 0, 4 | "doctype": "Form Tour", 5 | "first_document": 0, 6 | "idx": 0, 7 | "include_name_field": 0, 8 | "is_standard": 1, 9 | "list_name": "List", 10 | "modified": "2023-06-22 13:35:18.914103", 11 | "modified_by": "Administrator", 12 | "module": "Thai Tax", 13 | "name": "Tax Invoice Settings", 14 | "new_document_form": 0, 15 | "owner": "Administrator", 16 | "reference_doctype": "Tax Invoice Settings", 17 | "report_name": "", 18 | "save_on_complete": 0, 19 | "steps": [ 20 | { 21 | "description": "Select your current company", 22 | "fieldname": "company", 23 | "fieldtype": "Link", 24 | "has_next_condition": 0, 25 | "hide_buttons": 0, 26 | "is_table_field": 0, 27 | "label": "Company", 28 | "modal_trigger": 0, 29 | "next_on_click": 0, 30 | "offset_x": 0, 31 | "offset_y": 0, 32 | "popover_element": 0, 33 | "position": "Bottom", 34 | "title": "Company", 35 | "ui_tour": 0 36 | }, 37 | { 38 | "description": "Select Sales Tax Account", 39 | "fieldname": "sales_tax_account", 40 | "fieldtype": "Link", 41 | "has_next_condition": 0, 42 | "hide_buttons": 0, 43 | "is_table_field": 0, 44 | "label": "Sales Tax Account", 45 | "modal_trigger": 0, 46 | "next_on_click": 0, 47 | "offset_x": 0, 48 | "offset_y": 0, 49 | "popover_element": 0, 50 | "position": "Bottom", 51 | "title": "Sales Tax Account", 52 | "ui_tour": 0 53 | }, 54 | { 55 | "description": "Select Undue Sales Tax Account", 56 | "fieldname": "sales_tax_account_undue", 57 | "fieldtype": "Link", 58 | "has_next_condition": 0, 59 | "hide_buttons": 0, 60 | "is_table_field": 0, 61 | "label": "Sales Tax Account Undue", 62 | "modal_trigger": 0, 63 | "next_on_click": 0, 64 | "offset_x": 0, 65 | "offset_y": 0, 66 | "popover_element": 0, 67 | "position": "Bottom", 68 | "title": "Select Undue Sales Tax Account", 69 | "ui_tour": 0 70 | }, 71 | { 72 | "description": "Select Purchase Tax Account", 73 | "fieldname": "purchase_tax_account", 74 | "fieldtype": "Link", 75 | "has_next_condition": 0, 76 | "hide_buttons": 0, 77 | "is_table_field": 0, 78 | "label": "Purchase Tax Account", 79 | "modal_trigger": 0, 80 | "next_on_click": 0, 81 | "offset_x": 0, 82 | "offset_y": 0, 83 | "popover_element": 0, 84 | "position": "Bottom", 85 | "title": "Select Purchase Tax Account", 86 | "ui_tour": 0 87 | }, 88 | { 89 | "description": "Select Undue Purchase Tax Account", 90 | "fieldname": "purchase_tax_account_undue", 91 | "fieldtype": "Link", 92 | "has_next_condition": 0, 93 | "hide_buttons": 0, 94 | "is_table_field": 0, 95 | "label": "Purchase Tax Account Undue", 96 | "modal_trigger": 0, 97 | "next_on_click": 0, 98 | "offset_x": 0, 99 | "offset_y": 0, 100 | "popover_element": 0, 101 | "position": "Bottom", 102 | "title": "Select Undue Purchase Tax Account", 103 | "ui_tour": 0 104 | } 105 | ], 106 | "title": "Tax Invoice Settings", 107 | "track_steps": 0, 108 | "ui_tour": 0, 109 | "view_name": "Workspaces" 110 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # !IMPORTANT! This repo is not longer improved (only bug fix), we moved to https://github.com/ecosoft-frappe/erpnext_thailand 2 | 3 | ## Thai Tax 4 | 5 | Additional tax functionality to comply with Thailand Tax regulation. 6 | 7 | ### 1. Tax Point on both Invoice and Payment 8 | 9 | Tax Point determine when tax is recorded in general ledger. And on tax point, with Sales Tax Invoice or Purchase Tax Invoice will be created. 10 | 11 | Trading of stockable product, tax point occur when deliver product and invoice is issued. The selling party will issue out document called "Delivery Note / Tax Invoice". And so Tax Invoice doctype got created when submit sales/purchase invoice. 12 | 13 | For service, tax point occur when service is done and payment is made. When submit sales/purchase invoice, account ledger will record Undue Tax. Until when the seller get paid, it will then create Tax Invoice doctype on payment submission, in which account ledger will clear Undue Tax into Tax. The document issued from seller is called "Receipt / Tax Invoice" 14 | 15 | ### 2. Withholding Tax and Certificate 16 | 17 | When a company purchase service from a supplier, when making payment, it is responsible to withhold (deduct) a tax amount (i.e., 3%) of invoice amount and issue out the Withholding Tax Certificate (pdf) to supplier. 18 | 19 | ### 3. Reports that require for submission to RD, 20 | 21 | - Purchase Tax Report, Sales Tax Report 22 | - Withholding Tax Report (PND or ภงด) 23 | 24 | ### TODO: 25 | 26 | - Thailand e-Tax Invoice, e-Withholding Tax 27 | 28 | ## Features 29 | 30 | - Sales Tax and Undue Sales Tax 31 | - Purchase Tax and Undue Purchase Tax 32 | - Sales and Purchase Tax Report 33 | - Withholding Tax on Payment (based on invoice amount before tax) and Withholding Tax Cert (pdf) 34 | - Withholding Tax Report (PND3, PND53) 35 | - Get Address by Tax ID 36 | 37 | ## Setup 38 | 39 | ### Installation 40 | 41 | ``` 42 | $ cd frappe-bench 43 | $ bench get-app https://github.com/kittiu/thai_tax 44 | $ bench install-app thai_tax 45 | ``` 46 | 47 | ### Configurations 48 | 49 | #### For Tax Invoice setup 50 | 51 | 1. In chart of account, make sure to have with Rate, i.e, 7% for Thailand Tax (Tax) 52 | - Sales Tax, Undue Sales Tax 53 | - Purchase Tax, Undue Purchase Tax 54 | 2. Open Tax Invoice Settings, and setup above taxes 55 | 3. Setup Sales / Purchase Taxes and Charges Template, we just want to make sure that, 56 | - When buy/sell product, Sales/Purchase Tax is record on invoice 57 | - When buy/sell service, Undue Sales/Purchase Tax is record on invoice, then on payment, clear Undue Tax and record Tax 58 | 4. Make sure you have setup Company's Billing Address, as it will be used for Tax Invoice 59 | 5. Make sure all Supplier/Customer have setup Billing Address, they will be used for Tax Invoice 60 | 61 | Whenever Tax is recorded (with Tax Invoice and Tax Date), Sales/Purchase Tax Invoice will be created. 62 | 63 | #### For Withholding Tax setup 64 | 65 | 1. In chart of account, make sure to have Withholding Tax Account 66 | 2. Create Withholding Tax Types (1%, 2%, 3% and 5%) 67 | 68 | During payment, user will manually choose to deduct with one of these Withholding Tax Type, and then click button Create Withholding Tax cert with the deducted amount plus some additional deduction information. 69 | 70 | ----------------------- 71 | #### License 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/sales_tax_report/sales_tax_report.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.query_builder import Case, CustomFunction 7 | 8 | 9 | def execute(filters=None): 10 | columns = get_columns() 11 | data = get_data(filters) 12 | return columns, data, None, None, None 13 | 14 | 15 | def get_columns(): 16 | return [ 17 | { 18 | "label": _("Tax Address"), 19 | "fieldname": "company_tax_address", 20 | "fieldtype": "Link", 21 | "options": "Address", 22 | "width": 0, 23 | }, 24 | { 25 | "label": _("Report Date"), 26 | "fieldname": "report_date", 27 | "fieldtype": "Date", 28 | "width": 0, 29 | }, 30 | { 31 | "label": _("Number"), 32 | "fieldname": "name", 33 | "fieldtype": "Data", 34 | "width": 150, 35 | }, 36 | { 37 | "label": _("Customer"), 38 | "fieldname": "party_name", 39 | "fieldtype": "Data", 40 | "width": 200, 41 | }, 42 | { 43 | "label": _("Tax ID"), 44 | "fieldname": "tax_id", 45 | "fieldtype": "Data", 46 | "width": 120, 47 | }, 48 | { 49 | "label": _("Tax Base"), 50 | "fieldname": "tax_base", 51 | "fieldtype": "Currency", 52 | "options": "Company:company:default_currency", 53 | "width": 0, 54 | }, 55 | { 56 | "label": _("Tax Amount"), 57 | "fieldname": "tax_amount", 58 | "fieldtype": "Currency", 59 | "options": "Company:company:default_currency", 60 | "width": 0, 61 | }, 62 | { 63 | "label": _("Ref Voucher Type"), 64 | "fieldname": "voucher_type", 65 | "fieldtype": "Data", 66 | "width": 0, 67 | }, 68 | { 69 | "label": _("Ref Voucher No"), 70 | "fieldname": "voucher_no", 71 | "fieldtype": "Dynamic Link", 72 | "options": "voucher_type", 73 | "width": 200, 74 | }, 75 | ] 76 | 77 | 78 | def get_data(filters): 79 | 80 | tinv = frappe.qb.DocType("Sales Tax Invoice") 81 | cust = frappe.qb.DocType("Customer") 82 | round = CustomFunction("round", ["value", "digit"]) 83 | month = CustomFunction("month", ["date"]) 84 | year = CustomFunction("year", ["date"]) 85 | concat = CustomFunction("concat", ["1", "2"]) 86 | 87 | query = ( 88 | frappe.qb.from_(tinv) 89 | .left_join(cust) 90 | .on(cust.name == tinv.party) 91 | .select( 92 | tinv.company_tax_address.as_("company_tax_address"), 93 | tinv.report_date.as_("report_date"), 94 | Case() 95 | .when(tinv.docstatus == 1, tinv.name) 96 | .else_(concat(tinv.name, " (CANCEL)")) 97 | .as_("name"), 98 | tinv.party_name.as_("party_name"), 99 | cust.tax_id.as_("tax_id"), 100 | Case().when(tinv.docstatus == 1, round(tinv.tax_base, 2)).else_(0).as_("tax_base"), 101 | Case() 102 | .when(tinv.docstatus == 1, round(tinv.tax_amount, 2)) 103 | .else_(0) 104 | .as_("tax_amount"), 105 | tinv.voucher_type.as_("voucher_type"), 106 | tinv.voucher_no.as_("voucher_no"), 107 | ) 108 | .where(tinv.docstatus.isin([1, 2])) 109 | .orderby(tinv.name) 110 | ) 111 | 112 | if filters.get("filter_based_on") == "Fiscal Year": 113 | query = query.where(month(tinv.report_date) == filters.get("month")) 114 | query = query.where(year(tinv.report_date) == filters.get("year")) 115 | 116 | if filters.get("filter_based_on") == "Date Range": 117 | query = query.where(tinv.report_date >= filters.get("start_date")) 118 | query = query.where(tinv.report_date <= filters.get("end_date")) 119 | 120 | if filters.get("company_tax_address"): 121 | query = query.where(tinv.company_tax_address == filters.get("company_tax_address")) 122 | 123 | result = query.run(as_dict=True) 124 | 125 | return result 126 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/workspace/thai_tax/thai_tax.json: -------------------------------------------------------------------------------- 1 | { 2 | "charts": [], 3 | "content": "[{\"id\":\"Uty0KUCJZZ\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Thai Tax\",\"col\":12}},{\"id\":\"0xFTodMRF1\",\"type\":\"card\",\"data\":{\"card_name\":\"Tax Invoices\",\"col\":4}},{\"id\":\"MOY2MzVVaE\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"NLSyjJJigj\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]", 4 | "creation": "2023-06-22 11:35:38.341907", 5 | "custom_blocks": [], 6 | "docstatus": 0, 7 | "doctype": "Workspace", 8 | "for_user": "", 9 | "hide_custom": 0, 10 | "icon": "milestone", 11 | "idx": 0, 12 | "is_hidden": 0, 13 | "label": "Thai Tax", 14 | "links": [ 15 | { 16 | "hidden": 0, 17 | "is_query_report": 0, 18 | "label": "Settings", 19 | "link_count": 2, 20 | "onboard": 0, 21 | "type": "Card Break" 22 | }, 23 | { 24 | "hidden": 0, 25 | "is_query_report": 0, 26 | "label": "Tax Invoice Setting", 27 | "link_count": 0, 28 | "link_to": "Tax Invoice Settings", 29 | "link_type": "DocType", 30 | "onboard": 0, 31 | "type": "Link" 32 | }, 33 | { 34 | "hidden": 0, 35 | "is_query_report": 0, 36 | "label": "Withholding Tax Type", 37 | "link_count": 0, 38 | "link_to": "Withholding Tax Type", 39 | "link_type": "DocType", 40 | "onboard": 0, 41 | "type": "Link" 42 | }, 43 | { 44 | "hidden": 0, 45 | "is_query_report": 0, 46 | "label": "Tax Invoices", 47 | "link_count": 2, 48 | "onboard": 0, 49 | "type": "Card Break" 50 | }, 51 | { 52 | "hidden": 0, 53 | "is_query_report": 0, 54 | "label": "Sales Tax Invoice", 55 | "link_count": 0, 56 | "link_to": "Sales Tax Invoice", 57 | "link_type": "DocType", 58 | "onboard": 0, 59 | "type": "Link" 60 | }, 61 | { 62 | "hidden": 0, 63 | "is_query_report": 0, 64 | "label": "Purchase Tax Invoice", 65 | "link_count": 0, 66 | "link_to": "Purchase Tax Invoice", 67 | "link_type": "DocType", 68 | "onboard": 0, 69 | "type": "Link" 70 | }, 71 | { 72 | "hidden": 0, 73 | "is_query_report": 0, 74 | "label": "Reports", 75 | "link_count": 4, 76 | "onboard": 0, 77 | "type": "Card Break" 78 | }, 79 | { 80 | "hidden": 0, 81 | "is_query_report": 0, 82 | "label": "Sales Tax Report", 83 | "link_count": 0, 84 | "link_to": "Sales Tax Report", 85 | "link_type": "Report", 86 | "onboard": 0, 87 | "type": "Link" 88 | }, 89 | { 90 | "hidden": 0, 91 | "is_query_report": 0, 92 | "label": "Purchase Tax Report", 93 | "link_count": 0, 94 | "link_to": "Purchase Tax Report", 95 | "link_type": "Report", 96 | "onboard": 0, 97 | "type": "Link" 98 | }, 99 | { 100 | "hidden": 0, 101 | "is_query_report": 0, 102 | "label": "PND3", 103 | "link_count": 0, 104 | "link_to": "PND3", 105 | "link_type": "Report", 106 | "onboard": 0, 107 | "type": "Link" 108 | }, 109 | { 110 | "hidden": 0, 111 | "is_query_report": 0, 112 | "label": "PND53", 113 | "link_count": 0, 114 | "link_to": "PND53", 115 | "link_type": "Report", 116 | "onboard": 0, 117 | "type": "Link" 118 | } 119 | ], 120 | "modified": "2024-05-03 10:25:42.626039", 121 | "modified_by": "Administrator", 122 | "module": "Thai Tax", 123 | "name": "Thai Tax", 124 | "number_cards": [], 125 | "owner": "Administrator", 126 | "parent_page": "Accounting", 127 | "public": 1, 128 | "quick_lists": [], 129 | "roles": [ 130 | { 131 | "role": "Accounts User" 132 | }, 133 | { 134 | "role": "Accounts Manager" 135 | } 136 | ], 137 | "sequence_id": 7.0, 138 | "shortcuts": [], 139 | "title": "Thai Tax" 140 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 11, 9 | "sourceType": "module" 10 | }, 11 | "extends": "eslint:recommended", 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab", 16 | { "SwitchCase": 1 } 17 | ], 18 | "brace-style": [ 19 | "error", 20 | "1tbs" 21 | ], 22 | "space-unary-ops": [ 23 | "error", 24 | { "words": true } 25 | ], 26 | "linebreak-style": [ 27 | "error", 28 | "unix" 29 | ], 30 | "quotes": [ 31 | "off" 32 | ], 33 | "semi": [ 34 | "warn", 35 | "always" 36 | ], 37 | "camelcase": [ 38 | "off" 39 | ], 40 | "no-unused-vars": [ 41 | "warn" 42 | ], 43 | "no-redeclare": [ 44 | "warn" 45 | ], 46 | "no-console": [ 47 | "warn" 48 | ], 49 | "no-extra-boolean-cast": [ 50 | "off" 51 | ], 52 | "no-control-regex": [ 53 | "off" 54 | ], 55 | "space-before-blocks": "warn", 56 | "keyword-spacing": "warn", 57 | "comma-spacing": "warn", 58 | "key-spacing": "warn", 59 | }, 60 | "root": true, 61 | "globals": { 62 | "frappe": true, 63 | "Vue": true, 64 | "__": true, 65 | "repl": true, 66 | "Class": true, 67 | "locals": true, 68 | "cint": true, 69 | "cstr": true, 70 | "cur_frm": true, 71 | "cur_dialog": true, 72 | "cur_page": true, 73 | "cur_list": true, 74 | "cur_tree": true, 75 | "msg_dialog": true, 76 | "is_null": true, 77 | "in_list": true, 78 | "has_common": true, 79 | "has_words": true, 80 | "validate_email": true, 81 | "validate_name": true, 82 | "validate_phone": true, 83 | "validate_url": true, 84 | "get_number_format": true, 85 | "format_number": true, 86 | "format_currency": true, 87 | "comment_when": true, 88 | "open_url_post": true, 89 | "toTitle": true, 90 | "lstrip": true, 91 | "rstrip": true, 92 | "strip": true, 93 | "strip_html": true, 94 | "replace_all": true, 95 | "flt": true, 96 | "precision": true, 97 | "CREATE": true, 98 | "AMEND": true, 99 | "CANCEL": true, 100 | "copy_dict": true, 101 | "get_number_format_info": true, 102 | "strip_number_groups": true, 103 | "print_table": true, 104 | "Layout": true, 105 | "web_form_settings": true, 106 | "$c": true, 107 | "$a": true, 108 | "$i": true, 109 | "$bg": true, 110 | "$y": true, 111 | "$c_obj": true, 112 | "refresh_many": true, 113 | "refresh_field": true, 114 | "toggle_field": true, 115 | "get_field_obj": true, 116 | "get_query_params": true, 117 | "unhide_field": true, 118 | "hide_field": true, 119 | "set_field_options": true, 120 | "getCookie": true, 121 | "getCookies": true, 122 | "get_url_arg": true, 123 | "md5": true, 124 | "$": true, 125 | "jQuery": true, 126 | "moment": true, 127 | "hljs": true, 128 | "Awesomplete": true, 129 | "Sortable": true, 130 | "Showdown": true, 131 | "Taggle": true, 132 | "Gantt": true, 133 | "Slick": true, 134 | "Webcam": true, 135 | "PhotoSwipe": true, 136 | "PhotoSwipeUI_Default": true, 137 | "io": true, 138 | "JsBarcode": true, 139 | "L": true, 140 | "Chart": true, 141 | "DataTable": true, 142 | "Cypress": true, 143 | "cy": true, 144 | "it": true, 145 | "describe": true, 146 | "expect": true, 147 | "context": true, 148 | "before": true, 149 | "beforeEach": true, 150 | "after": true, 151 | "qz": true, 152 | "localforage": true, 153 | "extend_cscript": true 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/purchase_tax_report/purchase_tax_report.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.query_builder import CustomFunction 7 | 8 | 9 | def execute(filters=None): 10 | columns = get_columns() 11 | data = get_data(filters) 12 | return columns, data, None, None, None 13 | 14 | 15 | def get_columns(): 16 | return [ 17 | { 18 | "label": _("Tax Address"), 19 | "fieldname": "company_tax_address", 20 | "fieldtype": "Link", 21 | "options": "Address", 22 | "width": 0, 23 | }, 24 | { 25 | "label": _("Report Date"), 26 | "fieldname": "report_date", 27 | "fieldtype": "Date", 28 | "width": 0, 29 | }, 30 | { 31 | "label": _("Number"), 32 | "fieldname": "name", 33 | "fieldtype": "Data", 34 | "width": 0, 35 | }, 36 | { 37 | "label": _("Supplier"), 38 | "fieldname": "party_name", 39 | "fieldtype": "Data", 40 | "width": 0, 41 | }, 42 | { 43 | "label": _("Tax ID"), 44 | "fieldname": "tax_id", 45 | "fieldtype": "Data", 46 | "width": 0, 47 | }, 48 | { 49 | "label": _("Branch"), 50 | "fieldname": "branch_code", 51 | "fieldtype": "Data", 52 | "width": 0, 53 | }, 54 | { 55 | "label": _("Supplier Address"), 56 | "fieldname": "supplier_address", 57 | "fieldtype": "Data", 58 | "width": 0, 59 | }, 60 | { 61 | "label": _("Tax Base"), 62 | "fieldname": "tax_base", 63 | "fieldtype": "Currency", 64 | "options": "Company:company:default_currency", 65 | "width": 0, 66 | }, 67 | { 68 | "label": _("Tax Amount"), 69 | "fieldname": "tax_amount", 70 | "fieldtype": "Currency", 71 | "options": "Company:company:default_currency", 72 | "width": 0, 73 | }, 74 | { 75 | "label": _("Ref Voucher Type"), 76 | "fieldname": "voucher_type", 77 | "fieldtype": "Data", 78 | "width": 0, 79 | }, 80 | { 81 | "label": _("Ref Voucher No"), 82 | "fieldname": "voucher_no", 83 | "fieldtype": "Dynamic Link", 84 | "options": "voucher_type", 85 | "width": 0, 86 | }, 87 | { 88 | "label": _("Ref Tax Invoice"), 89 | "fieldname": "tax_invoice", 90 | "fieldtype": "Link", 91 | "options": "Purchase Tax Invoice", 92 | "width": 0, 93 | }, 94 | ] 95 | 96 | 97 | def get_data(filters): 98 | 99 | tinv = frappe.qb.DocType("Purchase Tax Invoice") 100 | sup = frappe.qb.DocType("Supplier") 101 | addr = frappe.qb.DocType("Address") 102 | round = CustomFunction("round", ["value", "digit"]) 103 | coalesce = CustomFunction("coalesce", ["value1", "value2"]) 104 | month = CustomFunction("month", ["date"]) 105 | year = CustomFunction("year", ["date"]) 106 | concat_ws = CustomFunction("concat_ws", ["separator", "1", "2", "3", "4", "5", "6"]) 107 | 108 | query = ( 109 | frappe.qb.from_(tinv) 110 | .left_join(sup) 111 | .on(sup.name == tinv.party) 112 | .left_join(addr) 113 | .on(addr.name == sup.supplier_primary_address) 114 | .select( 115 | tinv.company_tax_address.as_("company_tax_address"), 116 | tinv.report_date.as_("report_date"), 117 | coalesce(tinv.number, tinv.name).as_("name"), 118 | sup.supplier_name.as_("party_name"), 119 | sup.tax_id.as_("tax_id"), 120 | sup.branch_code.as_("branch_code"), 121 | concat_ws( 122 | " ", 123 | addr.address_line1, 124 | addr.address_line2, 125 | addr.city, 126 | addr.county, 127 | addr.state, 128 | addr.pincode, 129 | ).as_("supplier_address"), 130 | round(tinv.tax_base, 2).as_("tax_base"), 131 | round(tinv.tax_amount, 2).as_("tax_amount"), 132 | tinv.voucher_type.as_("voucher_type"), 133 | tinv.voucher_no.as_("voucher_no"), 134 | tinv.name.as_("tax_invoice"), 135 | ) 136 | .where(tinv.docstatus == 1) 137 | .orderby(tinv.report_date) 138 | ) 139 | 140 | if filters.get("filter_based_on") == "Fiscal Year": 141 | query = query.where(month(tinv.report_date) == filters.get("month")) 142 | query = query.where(year(tinv.report_date) == filters.get("year")) 143 | 144 | if filters.get("filter_based_on") == "Date Range": 145 | query = query.where(tinv.report_date >= filters.get("start_date")) 146 | query = query.where(tinv.report_date <= filters.get("end_date")) 147 | 148 | if filters.get("company_tax_address"): 149 | query = query.where(tinv.company_tax_address == filters.get("company_tax_address")) 150 | 151 | result = query.run(as_dict=True) 152 | 153 | return result 154 | -------------------------------------------------------------------------------- /thai_tax/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import frappe 4 | from frappe import _ 5 | from num2words import num2words 6 | import requests 7 | from lxml import etree 8 | 9 | def amount_in_bahttext(amount): 10 | return num2words(amount, to="currency", lang="th") 11 | 12 | 13 | def full_thai_date(date_str): 14 | if not date_str: 15 | return "" 16 | date = datetime.datetime.strptime(str(date_str), "%Y-%m-%d") 17 | month_name = "x มกราคม กุมภาพันธ์ มีนาคม เมษายน พฤษภาคม มิถุนายน กรกฎาคม สิงหาคม กันยายน ตุลาคม พฤศจิกายน ธันวาคม".split()[ 18 | date.month 19 | ] 20 | thai_year = date.year + 543 21 | return "%d %s %d" % (date.day, month_name, thai_year) # 30 ตุลาคม 2560 22 | 23 | 24 | @frappe.whitelist() 25 | def get_address_by_tax_id(tax_id=False, branch=False): 26 | """Get address information from Revenue Department Web Service by Tax ID and Branch number. 27 | 28 | Args: 29 | tax_id (str): Tax ID of the company 30 | branch (str): Branch number of the company 31 | 32 | Returns: 33 | dict: Dictionary containing address information 34 | Empty dict if there's an error 35 | 36 | Raises: 37 | frappe.ValidationError: If tax_id or branch is not provided 38 | """ 39 | if not (tax_id and branch): 40 | frappe.throw(_('Please provide both Tax ID and Branch number')) 41 | 42 | # API Configuration 43 | url = "https://rdws.rd.go.th/serviceRD3/vatserviceRD3.asmx" 44 | querystring = {"wsdl": ""} 45 | headers = {"content-type": "application/soap+xml; charset=utf-8"} 46 | 47 | # Convert branch number, default to "0" if not numeric 48 | branch_number = int(branch if branch.isnumeric() else "0") 49 | 50 | # Prepare SOAP payload 51 | payload = ( 52 | '' 54 | '' 55 | '' 56 | '' 57 | 'anonymous' 58 | 'anonymous' 59 | f'{tax_id}' 60 | '' 61 | '0' 62 | f'{branch_number}' 63 | '0' 64 | '' 65 | '' 66 | '' 67 | ) 68 | 69 | # Setup session with SSL verification disabled 70 | session = requests.Session() 71 | session.verify = False 72 | 73 | # Make the API request 74 | response = session.post(url, data=payload, headers=headers, params=querystring) 75 | response.raise_for_status() # Raise exception for HTTP errors 76 | 77 | # Parse XML response 78 | result = etree.fromstring(response.content) 79 | # Process response data 80 | data = {} 81 | value = False 82 | for element in result.iter(): 83 | tag = etree.QName(element).localname 84 | if not value and tag[:1] == "v": 85 | value = tag 86 | continue 87 | if value and tag == "anyType": 88 | data[value] = element.text.strip() 89 | value = False 90 | 91 | if data.get("vmsgerr"): 92 | frappe.throw(data.get("vmsgerr")) 93 | 94 | return finalize_address_dict(data) 95 | 96 | 97 | def finalize_address_dict(data): 98 | 99 | def get_part(data, key, value): 100 | return data.get(key, "-") != "-" and value % (map[key], data.get(key)) or "" 101 | 102 | map = { 103 | "vBuildingName": "อาคาร", 104 | "vFloorNumber": "ชั้น", 105 | "vVillageName": "หมู่บ้าน", 106 | "vRoomNumber": "ห้อง", 107 | # "vHouseNumber": "เลขที่", 108 | "vMooNumber": "หมู่ที่", 109 | "vSoiName": "ซอย", 110 | "vStreetName": "ถนน", 111 | "vThambol": "ต.", 112 | "vAmphur": "อ.", 113 | "vProvince": "จ.", 114 | } 115 | name = f"{data.get('vBranchTitleName')} {data.get('vBranchName')}" 116 | if "vSurname" in data and data["vSurname"] not in ("-", "", None): 117 | name = f"{name} {data['vSurname']}" 118 | house = data.get("vHouseNumber", "") 119 | village = get_part(data, "vVillageName", "%s %s") 120 | soi = get_part(data, "vSoiName", "%s %s") 121 | moo = get_part(data, "vMooNumber", "%s %s") 122 | building = get_part(data, "vBuildingName", "%s %s") 123 | floor = get_part(data, "vFloorNumber", "%s %s") 124 | room = get_part(data, "vRoomNumber", "%s %s") 125 | street = get_part(data, "vStreetName", "%s%s") 126 | thambon = get_part(data, "vThambol", "%s%s") 127 | amphur = get_part(data, "vAmphur", "%s%s") 128 | province = get_part(data, "vProvince", "%s%s") 129 | postal = data.get("vPostCode", "") 130 | 131 | if province == "จ.กรุงเทพมหานคร": 132 | thambon = data.get("vThambol") and f"แขวง{data['vThambol']}" or "" 133 | amphur = data.get("vAmphur") and f"เขต{data['vAmphur']}" or "" 134 | province = data.get("vProvince") and f"{data['vProvince']}" or "" 135 | 136 | address_parts = filter( 137 | lambda x: x != "", [house, village, soi, moo, building, floor, room, street] 138 | ) 139 | return { 140 | "name": name, 141 | "address_line1": " ".join(address_parts), 142 | "city": thambon, 143 | "county": amphur, 144 | "state": province, 145 | "pincode": postal, 146 | } 147 | 148 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd53/pnd53.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.query_builder import Case, CustomFunction 7 | 8 | 9 | def execute(filters=None): 10 | columns = get_columns() 11 | data = get_data(filters) 12 | return columns, data, None, None, None 13 | 14 | 15 | def get_columns(): 16 | return [ 17 | { 18 | "label": _("No."), 19 | "fieldname": "no", 20 | "fieldtype": "Int", 21 | "width": 0, 22 | }, 23 | { 24 | "label": _("Supplier Tax ID"), 25 | "fieldname": "supplier_tax_id", 26 | "fieldtype": "Data", 27 | "width": 0, 28 | }, 29 | { 30 | "label": _("Branch"), 31 | "fieldname": "branch", 32 | "fieldtype": "Data", 33 | "width": 0, 34 | }, 35 | { 36 | "label": _("Supplier Name"), 37 | "fieldname": "supplier_name", 38 | "fieldtype": "Data", 39 | "width": 0, 40 | }, 41 | { 42 | "label": _("Address"), 43 | "fieldname": "address_line1", 44 | "fieldtype": "Data", 45 | "width": 0, 46 | }, 47 | { 48 | "label": _("Tambon"), 49 | "fieldname": "city", 50 | "fieldtype": "Data", 51 | "width": 0, 52 | }, 53 | { 54 | "label": _("Amphur"), 55 | "fieldname": "county", 56 | "fieldtype": "Data", 57 | "width": 0, 58 | }, 59 | { 60 | "label": _("Province"), 61 | "fieldname": "state", 62 | "fieldtype": "Data", 63 | "width": 0, 64 | }, 65 | { 66 | "label": _("Zip Code"), 67 | "fieldname": "pincode", 68 | "fieldtype": "Data", 69 | "width": 0, 70 | }, 71 | { 72 | "label": _("Date"), 73 | "fieldname": "date", 74 | "fieldtype": "Date", 75 | "width": 0, 76 | }, 77 | { 78 | "label": _("Description"), 79 | "fieldname": "description", 80 | "fieldtype": "Data", 81 | "width": 0, 82 | }, 83 | { 84 | "label": _("Tax Base"), 85 | "fieldname": "tax_base", 86 | "fieldtype": "Currency", 87 | "options": "Company:company:default_currency", 88 | "width": 0, 89 | }, 90 | { 91 | "label": _("Tax Rate"), 92 | "fieldname": "tax_rate", 93 | "fieldtype": "Int", 94 | "width": 0, 95 | }, 96 | { 97 | "label": _("Tax Amount"), 98 | "fieldname": "tax_amount", 99 | "fieldtype": "Currency", 100 | "options": "Company:company:default_currency", 101 | "width": 0, 102 | }, 103 | { 104 | "label": _("Tax Payer"), 105 | "fieldname": "tax_payer", 106 | "fieldtype": "Data", 107 | "width": 0, 108 | }, 109 | { 110 | "label": _("WHT Cert."), 111 | "fieldname": "name", 112 | "fieldtype": "Link", 113 | "options": "Withholding Tax Cert", 114 | "width": 0, 115 | }, 116 | { 117 | "label": _("Voucher Type"), 118 | "fieldname": "voucher_type", 119 | "fieldtype": "Data", 120 | "width": 0, 121 | }, 122 | { 123 | "label": _("Voucher No"), 124 | "fieldname": "voucher_no", 125 | "fieldtype": "Dynamic Link", 126 | "options": "voucher_type", 127 | "width": 0, 128 | }, 129 | ] 130 | 131 | 132 | def get_data(filters): 133 | 134 | wht_cert = frappe.qb.DocType("Withholding Tax Cert") 135 | wht_items = frappe.qb.DocType("Withholding Tax Items") 136 | supplier = frappe.qb.DocType("Supplier") 137 | address = frappe.qb.DocType("Address") 138 | round = CustomFunction("round", ["value", "digit"]) 139 | month = CustomFunction("month", ["date"]) 140 | year = CustomFunction("year", ["date"]) 141 | 142 | query = ( 143 | frappe.qb.from_(wht_cert) 144 | .join(wht_items) 145 | .on(wht_items.parent == wht_cert.name) 146 | .join(supplier) 147 | .on(supplier.name == wht_cert.supplier) 148 | .left_join(address) 149 | .on(address.name == wht_cert.supplier_address) 150 | .select( 151 | supplier.tax_id.as_("supplier_tax_id"), 152 | supplier.branch_code.as_("branch"), 153 | supplier.supplier_name.as_("supplier_name"), 154 | address.address_line1.as_("address_line1"), 155 | address.city.as_("city"), 156 | address.county.as_("county"), 157 | address.state.as_("state"), 158 | address.pincode.as_("pincode"), 159 | wht_cert.date.as_("date"), 160 | wht_items.description.as_("description"), 161 | round(wht_items.tax_base, 2).as_("tax_base"), 162 | wht_items.tax_rate.as_("tax_rate"), 163 | round(wht_items.tax_amount, 2).as_("tax_amount"), 164 | Case() 165 | .when(wht_cert.tax_payer == "Withholding", "1") 166 | .when(wht_cert.tax_payer == "Paid One Time", "3") 167 | .else_(wht_cert.tax_payer) 168 | .as_("tax_payer"), 169 | wht_cert.name.as_("name"), 170 | wht_cert.voucher_type.as_("voucher_type"), 171 | wht_cert.voucher_no.as_("voucher_no"), 172 | ) 173 | .distinct() 174 | .where( 175 | (wht_cert.docstatus == 1) 176 | & (wht_cert.income_tax_form == "PND53") 177 | & (month(wht_cert.date) == filters.get("month")) 178 | & (year(wht_cert.date) == filters.get("year")) 179 | ) 180 | .orderby(wht_cert.date, wht_cert.name) 181 | ) 182 | 183 | if filters.get("company_address"): 184 | query = query.where(wht_cert.company_address == filters.get("company_address")) 185 | 186 | result = query.run(as_dict=True) 187 | 188 | # Add row number column 189 | i = 0 190 | for r in result: 191 | r["no"] = i = i + 1 192 | 193 | return result 194 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_cert/withholding_tax_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "WHT-CERT-.######", 5 | "creation": "2023-03-04 12:09:51.339897", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "editable_grid": 1, 9 | "engine": "InnoDB", 10 | "field_order": [ 11 | "company", 12 | "company_tax_id", 13 | "company_address", 14 | "column_break_3", 15 | "supplier", 16 | "supplier_name", 17 | "supplier_tax_id", 18 | "supplier_address", 19 | "section_break_6", 20 | "income_tax_form", 21 | "tax_payer", 22 | "column_break_9", 23 | "date", 24 | "section_break_11", 25 | "withholding_tax_items", 26 | "section_break_12", 27 | "voucher_type", 28 | "column_break_15", 29 | "voucher_no", 30 | "amended_from" 31 | ], 32 | "fields": [ 33 | { 34 | "fieldname": "company", 35 | "fieldtype": "Link", 36 | "in_list_view": 1, 37 | "label": "Company", 38 | "options": "Company", 39 | "reqd": 1 40 | }, 41 | { 42 | "fetch_from": "company.tax_id", 43 | "fieldname": "company_tax_id", 44 | "fieldtype": "Data", 45 | "label": "Company Tax ID", 46 | "read_only": 1 47 | }, 48 | { 49 | "fieldname": "company_address", 50 | "fieldtype": "Link", 51 | "label": "Company Address", 52 | "options": "Address", 53 | "reqd": 1 54 | }, 55 | { 56 | "fieldname": "column_break_3", 57 | "fieldtype": "Column Break" 58 | }, 59 | { 60 | "allow_on_submit": 1, 61 | "fieldname": "supplier", 62 | "fieldtype": "Link", 63 | "in_standard_filter": 1, 64 | "label": "Supplier", 65 | "options": "Supplier", 66 | "reqd": 1 67 | }, 68 | { 69 | "allow_on_submit": 1, 70 | "fetch_from": "supplier.supplier_name", 71 | "fieldname": "supplier_name", 72 | "fieldtype": "Data", 73 | "label": "Supplier Name" 74 | }, 75 | { 76 | "allow_on_submit": 1, 77 | "fetch_from": "supplier.tax_id", 78 | "fieldname": "supplier_tax_id", 79 | "fieldtype": "Data", 80 | "label": "Supplier Tax ID", 81 | "read_only": 1 82 | }, 83 | { 84 | "allow_on_submit": 1, 85 | "fetch_from": "supplier.supplier_primary_address", 86 | "fetch_if_empty": 1, 87 | "fieldname": "supplier_address", 88 | "fieldtype": "Link", 89 | "label": "Supplier Address", 90 | "options": "Address", 91 | "reqd": 1 92 | }, 93 | { 94 | "fieldname": "section_break_6", 95 | "fieldtype": "Section Break" 96 | }, 97 | { 98 | "fieldname": "income_tax_form", 99 | "fieldtype": "Select", 100 | "label": "Income Tax Form", 101 | "options": "\nPND3\nPND53", 102 | "reqd": 1 103 | }, 104 | { 105 | "fieldname": "tax_payer", 106 | "fieldtype": "Select", 107 | "label": "Tax Payer", 108 | "options": "Withholding\nPaid One Time", 109 | "reqd": 1 110 | }, 111 | { 112 | "fieldname": "column_break_9", 113 | "fieldtype": "Column Break" 114 | }, 115 | { 116 | "fieldname": "date", 117 | "fieldtype": "Date", 118 | "label": "Date", 119 | "reqd": 1 120 | }, 121 | { 122 | "fieldname": "section_break_11", 123 | "fieldtype": "Section Break" 124 | }, 125 | { 126 | "fieldname": "withholding_tax_items", 127 | "fieldtype": "Table", 128 | "label": "Withholding Tax Items", 129 | "options": "Withholding Tax Items", 130 | "reqd": 1 131 | }, 132 | { 133 | "fieldname": "section_break_12", 134 | "fieldtype": "Section Break", 135 | "label": "Reference" 136 | }, 137 | { 138 | "default": "Payment Entry", 139 | "fieldname": "voucher_type", 140 | "fieldtype": "Link", 141 | "label": "Voucher Type", 142 | "options": "DocType", 143 | "search_index": 1 144 | }, 145 | { 146 | "fieldname": "column_break_15", 147 | "fieldtype": "Column Break" 148 | }, 149 | { 150 | "fieldname": "voucher_no", 151 | "fieldtype": "Dynamic Link", 152 | "in_list_view": 1, 153 | "in_standard_filter": 1, 154 | "label": "Voucher No", 155 | "options": "voucher_type", 156 | "search_index": 1 157 | }, 158 | { 159 | "fieldname": "amended_from", 160 | "fieldtype": "Link", 161 | "label": "Amended From", 162 | "no_copy": 1, 163 | "options": "Withholding Tax Cert", 164 | "print_hide": 1, 165 | "read_only": 1 166 | } 167 | ], 168 | "index_web_pages_for_search": 1, 169 | "is_submittable": 1, 170 | "links": [], 171 | "modified": "2023-03-24 13:29:02.023369", 172 | "modified_by": "Administrator", 173 | "module": "Thai Tax", 174 | "name": "Withholding Tax Cert", 175 | "naming_rule": "Expression (old style)", 176 | "owner": "Administrator", 177 | "permissions": [ 178 | { 179 | "amend": 1, 180 | "cancel": 1, 181 | "create": 1, 182 | "delete": 1, 183 | "email": 1, 184 | "export": 1, 185 | "print": 1, 186 | "read": 1, 187 | "report": 1, 188 | "role": "Accounts Manager", 189 | "share": 1, 190 | "submit": 1, 191 | "write": 1 192 | }, 193 | { 194 | "create": 1, 195 | "email": 1, 196 | "export": 1, 197 | "print": 1, 198 | "read": 1, 199 | "report": 1, 200 | "role": "Accounts User", 201 | "share": 1, 202 | "submit": 1, 203 | "write": 1 204 | } 205 | ], 206 | "sort_field": "modified", 207 | "sort_order": "DESC", 208 | "states": [], 209 | "title_field": "supplier_name", 210 | "track_changes": 1 211 | } -------------------------------------------------------------------------------- /thai_tax/custom/payment_entry.py: -------------------------------------------------------------------------------- 1 | import json 2 | from ast import literal_eval 3 | 4 | import frappe 5 | import pandas as pd 6 | from frappe import _ 7 | 8 | REF_DOCTYPES = ["Purchase Invoice", "Expense Claim", "Journal Entry"] 9 | 10 | 11 | @frappe.whitelist() 12 | def test_require_withholding_tax(doc): 13 | """Check if any of the payment references has withholding tax type""" 14 | pay = json.loads(doc) 15 | for d in pay.get("references"): 16 | # Purchase Invoice 17 | if d.get("reference_doctype") in ["Purchase Invoice", "Sales Invoice"]: 18 | ref_doc = frappe.get_doc(d.get("reference_doctype"), d.get("reference_name")) 19 | for item in ref_doc.items: 20 | if item.custom_withholding_tax_type: 21 | return True 22 | return False 23 | 24 | 25 | @frappe.whitelist() 26 | def get_withholding_tax_from_type(filters, doc): 27 | filters = literal_eval(filters) 28 | pay = json.loads(doc) 29 | wht = frappe.get_doc("Withholding Tax Type", filters["wht_type"]) 30 | company = frappe.get_doc("Company", pay["company"]) 31 | base_amount = 0 32 | for ref in pay.get("references"): 33 | if ref.get("reference_doctype") not in [ 34 | "Sales Invoice", 35 | "Purchase Invoice", 36 | "Expense Claim", 37 | "Journal Entry", 38 | ]: 39 | return 40 | if not ref.get("allocated_amount") or not ref.get("total_amount"): 41 | continue 42 | # Find gl entry of ref doc that has undue amount 43 | gl_entries = frappe.db.get_all( 44 | "GL Entry", 45 | filters={ 46 | "voucher_type": ref["reference_doctype"], 47 | "voucher_no": ref["reference_name"], 48 | }, 49 | fields=[ 50 | "name", 51 | "account", 52 | "debit", 53 | "credit", 54 | ], 55 | ) 56 | for gl in gl_entries: 57 | credit = gl["credit"] 58 | debit = gl["debit"] 59 | alloc_percent = ref["allocated_amount"] / ref["total_amount"] 60 | report_type = frappe.get_cached_value("Account", gl["account"], "report_type") 61 | if report_type == "Profit and Loss": 62 | base_amount += alloc_percent * (credit - debit) 63 | if not base_amount: 64 | frappe.throw(_("There is nothing to withhold tax for")) 65 | sign = -1 if pay.get("party_type") == "Receive" else 1 66 | return { 67 | "withholding_tax_type": wht.name, 68 | "account": wht.account, 69 | "cost_center": company.cost_center, 70 | "base": base_amount * sign, 71 | "rate": wht.percent, 72 | "amount": wht.percent / 100 * base_amount * sign, 73 | } 74 | 75 | 76 | @frappe.whitelist() 77 | def get_withholding_tax_from_docs_items(doc): 78 | pay = json.loads(doc) 79 | company = frappe.get_doc("Company", pay["company"]) 80 | result = [] 81 | wht_types = frappe.get_all( 82 | "Withholding Tax Type", 83 | fields=["name", "percent", "account"], 84 | as_list=True, 85 | ) 86 | wht_rates = frappe._dict({x[0]: {"percent": x[1], "account": x[2]} for x in wht_types}) 87 | for ref in pay.get("references"): 88 | # Purchase Invoice 89 | if ref.get("reference_doctype") in ["Purchase Invoice", "Sales Invoice"]: 90 | if not ref.get("allocated_amount") or not ref.get("total_amount"): 91 | continue 92 | sign = -1 if pay.get("payment_type") == "Pay" else 1 93 | ref_doc = frappe.get_doc(ref.get("reference_doctype"), ref.get("reference_name")) 94 | for item in ref_doc.items: 95 | if item.custom_withholding_tax_type: 96 | wht = item.custom_withholding_tax_type 97 | result.append( 98 | { 99 | "withholding_tax_type": wht, 100 | "account": wht_rates[wht]["account"], 101 | "cost_center": company.cost_center, 102 | "base": sign * item.amount, 103 | "rate": wht_rates[wht]["percent"], 104 | "amount": wht_rates[wht]["percent"] / 100 * sign * item.amount, 105 | } 106 | ) 107 | # Group by and sum 108 | df = pd.DataFrame(result) 109 | group_fields = ["withholding_tax_type", "account", "cost_center", "rate"] 110 | sum_fields = ["base", "amount"] 111 | dict_sum_fields = {x: sum for x in sum_fields} 112 | result = df.groupby(group_fields, as_index=False).aggregate(dict_sum_fields) 113 | result = result.to_dict(orient="records") 114 | return result 115 | 116 | 117 | @frappe.whitelist() 118 | def make_withholding_tax_cert(filters, doc): 119 | filters = literal_eval(filters) 120 | pay = json.loads(doc) 121 | cert = frappe.new_doc("Withholding Tax Cert") 122 | cert.supplier = pay.get("party_type") == "Supplier" and pay.get("party") or "" 123 | if cert.supplier != "": 124 | supplier = frappe.get_doc("Supplier", cert.supplier) 125 | cert.supplier_name = supplier and supplier.supplier_name or "" 126 | cert.supplier_address = supplier and supplier.supplier_primary_address or "" 127 | cert.voucher_type = "Payment Entry" 128 | cert.voucher_no = pay.get("name") 129 | cert.company_address = filters.get("company_address") 130 | cert.income_tax_form = filters.get("income_tax_form") 131 | cert.date = filters.get("date") 132 | for d in pay.get("deductions"): 133 | base = d.get("custom_withholding_tax_base", 0) 134 | amount = d.get("amount", 0) 135 | rate = 0 136 | wht_type = d.get("custom_withholding_tax_type") 137 | if wht_type: 138 | rate = frappe.get_cached_value("Withholding Tax Type", wht_type, "percent") 139 | cert.append( 140 | "withholding_tax_items", 141 | { 142 | "tax_base": -base, 143 | "tax_rate": rate, 144 | "tax_amount": -amount, 145 | }, 146 | ) 147 | return cert 148 | 149 | 150 | def reconcile_undue_tax(doc, method): 151 | """ If bs_reconcile is installed, unreconcile undue tax gls """ 152 | vouchers = [doc.name] + [r.reference_name for r in doc.references] 153 | reconcile_undue_tax_gls(vouchers) 154 | 155 | 156 | def reconcile_undue_tax_gls(vouchers, unreconcile=False): 157 | """ Only if bs_reconcile app is install, reconcile/unreconcile undue tax gl entries """ 158 | if "bs_reconcile" not in frappe.get_installed_apps(): 159 | return 160 | try: 161 | from bs_reconcile.balance_sheet_reconciliation import utils 162 | except ImportError: 163 | pass 164 | tax = frappe.get_single("Tax Invoice Settings") 165 | undue_taxes = [tax.purchase_tax_account_undue, tax.sales_tax_account_undue] 166 | gl_entries = utils.get_gl_entries_by_vouchers(vouchers) 167 | undue_tax_gls = list(filter(lambda x: x.account in undue_taxes, gl_entries)) 168 | if unreconcile: 169 | utils.unreconcile_gl(undue_tax_gls) 170 | else: 171 | utils.reconcile_gl(undue_tax_gls) 172 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/report/pnd3/pnd3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, Kitti U. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe import _ 6 | from frappe.query_builder import Case, CustomFunction 7 | from frappe.query_builder.custom import ConstantColumn 8 | 9 | 10 | def execute(filters=None): 11 | columns = get_columns() 12 | data = get_data(filters) 13 | return columns, data, None, None, None 14 | 15 | 16 | def get_columns(): 17 | return [ 18 | { 19 | "label": _("No."), 20 | "fieldname": "no", 21 | "fieldtype": "Int", 22 | "width": 0, 23 | }, 24 | { 25 | "label": _("Supplier Tax ID"), 26 | "fieldname": "supplier_tax_id", 27 | "fieldtype": "Data", 28 | "width": 0, 29 | }, 30 | { 31 | "label": _("Branch"), 32 | "fieldname": "branch", 33 | "fieldtype": "Data", 34 | "width": 0, 35 | }, 36 | { 37 | "label": _("Title"), 38 | "fieldname": "title", 39 | "fieldtype": "Data", 40 | "width": 0, 41 | }, 42 | { 43 | "label": _("First Name"), 44 | "fieldname": "firstname", 45 | "fieldtype": "Data", 46 | "width": 0, 47 | }, 48 | { 49 | "label": _("Last Name"), 50 | "fieldname": "lastname", 51 | "fieldtype": "Data", 52 | "width": 0, 53 | }, 54 | { 55 | "label": _("Address"), 56 | "fieldname": "address_line1", 57 | "fieldtype": "Data", 58 | "width": 0, 59 | }, 60 | { 61 | "label": _("Tambon"), 62 | "fieldname": "city", 63 | "fieldtype": "Data", 64 | "width": 0, 65 | }, 66 | { 67 | "label": _("Amphur"), 68 | "fieldname": "county", 69 | "fieldtype": "Data", 70 | "width": 0, 71 | }, 72 | { 73 | "label": _("Province"), 74 | "fieldname": "state", 75 | "fieldtype": "Data", 76 | "width": 0, 77 | }, 78 | { 79 | "label": _("Zip Code"), 80 | "fieldname": "pincode", 81 | "fieldtype": "Data", 82 | "width": 0, 83 | }, 84 | { 85 | "label": _("Date"), 86 | "fieldname": "date", 87 | "fieldtype": "Date", 88 | "width": 0, 89 | }, 90 | { 91 | "label": _("Description"), 92 | "fieldname": "description", 93 | "fieldtype": "Data", 94 | "width": 0, 95 | }, 96 | { 97 | "label": _("Tax Base"), 98 | "fieldname": "tax_base", 99 | "fieldtype": "Currency", 100 | "options": "Company:company:default_currency", 101 | "width": 0, 102 | }, 103 | { 104 | "label": _("Tax Rate"), 105 | "fieldname": "tax_rate", 106 | "fieldtype": "Int", 107 | "width": 0, 108 | }, 109 | { 110 | "label": _("Tax Amount"), 111 | "fieldname": "tax_amount", 112 | "fieldtype": "Currency", 113 | "options": "Company:company:default_currency", 114 | "width": 0, 115 | }, 116 | { 117 | "label": _("Tax Payer"), 118 | "fieldname": "tax_payer", 119 | "fieldtype": "Data", 120 | "width": 0, 121 | }, 122 | { 123 | "label": _("WHT Cert."), 124 | "fieldname": "name", 125 | "fieldtype": "Link", 126 | "options": "Withholding Tax Cert", 127 | "width": 0, 128 | }, 129 | { 130 | "label": _("Voucher Type"), 131 | "fieldname": "voucher_type", 132 | "fieldtype": "Data", 133 | "width": 0, 134 | }, 135 | { 136 | "label": _("Voucher No"), 137 | "fieldname": "voucher_no", 138 | "fieldtype": "Dynamic Link", 139 | "options": "voucher_type", 140 | "width": 0, 141 | }, 142 | ] 143 | 144 | 145 | def get_data(filters): 146 | 147 | # SUBSTRING(s.supplier_name, 1, CHAR_LENGTH(s.supplier_name) - LOCATE(' ', REVERSE(s.supplier_name))+1) as firstname, 148 | # SUBSTRING_INDEX(s.supplier_name, " ", -1) as lastname, 149 | 150 | wht_cert = frappe.qb.DocType("Withholding Tax Cert") 151 | wht_items = frappe.qb.DocType("Withholding Tax Items") 152 | supplier = frappe.qb.DocType("Supplier") 153 | address = frappe.qb.DocType("Address") 154 | round = CustomFunction("round", ["value", "digit"]) 155 | month = CustomFunction("month", ["date"]) 156 | year = CustomFunction("year", ["date"]) 157 | substring = CustomFunction("substring", ["string", "pos1", "pos2"]) 158 | substring_index = CustomFunction("substring_index", ["string", "char", "pos"]) 159 | char_length = CustomFunction("char_length", ["string"]) 160 | locate = CustomFunction("locate", ["char", "string"]) 161 | reverse = CustomFunction("reverse", ["string"]) 162 | 163 | query = ( 164 | frappe.qb.from_(wht_cert) 165 | .join(wht_items) 166 | .on(wht_items.parent == wht_cert.name) 167 | .join(supplier) 168 | .on(supplier.name == wht_cert.supplier) 169 | .left_join(address) 170 | .on(address.name == wht_cert.supplier_address) 171 | .select( 172 | supplier.tax_id.as_("supplier_tax_id"), 173 | supplier.branch_code.as_("branch"), 174 | ConstantColumn("").as_("title"), 175 | substring( 176 | supplier.supplier_name, 177 | 1, 178 | char_length(supplier.supplier_name) 179 | - locate(" ", reverse(supplier.supplier_name)) 180 | + 1, 181 | ).as_("firstname"), 182 | substring_index(supplier.supplier_name, " ", -1).as_("lastname"), 183 | address.address_line1.as_("address_line1"), 184 | address.city.as_("city"), 185 | address.county.as_("county"), 186 | address.state.as_("state"), 187 | address.pincode.as_("pincode"), 188 | wht_cert.date.as_("date"), 189 | wht_items.description.as_("description"), 190 | round(wht_items.tax_base, 2).as_("tax_base"), 191 | wht_items.tax_rate.as_("tax_rate"), 192 | round(wht_items.tax_amount, 2).as_("tax_amount"), 193 | Case() 194 | .when(wht_cert.tax_payer == "Withholding", "1") 195 | .when(wht_cert.tax_payer == "Paid One Time", "3") 196 | .else_(wht_cert.tax_payer) 197 | .as_("tax_payer"), 198 | wht_cert.name.as_("name"), 199 | wht_cert.voucher_type.as_("voucher_type"), 200 | wht_cert.voucher_no.as_("voucher_no"), 201 | ) 202 | .distinct() 203 | .where( 204 | (wht_cert.docstatus == 1) 205 | & (wht_cert.income_tax_form == "PND3") 206 | & (month(wht_cert.date) == filters.get("month")) 207 | & (year(wht_cert.date) == filters.get("year")) 208 | ) 209 | .orderby(wht_cert.date, wht_cert.name) 210 | ) 211 | 212 | if filters.get("company_address"): 213 | query = query.where(wht_cert.company_address == filters.get("company_address")) 214 | 215 | result = query.run(as_dict=True) 216 | 217 | # Add row number column 218 | i = 0 219 | for r in result: 220 | r["no"] = i = i + 1 221 | 222 | return result 223 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/sales_tax_invoice/sales_tax_invoice.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_guest_to_view": 1, 4 | "allow_rename": 1, 5 | "autoname": "naming_series:", 6 | "creation": "2023-03-04 12:10:47.401639", 7 | "default_view": "List", 8 | "doctype": "DocType", 9 | "editable_grid": 1, 10 | "engine": "InnoDB", 11 | "field_order": [ 12 | "naming_series", 13 | "date", 14 | "months_delayed", 15 | "report_date", 16 | "column_break_6", 17 | "account", 18 | "tax_base", 19 | "tax_amount", 20 | "column_break_15", 21 | "voucher_type", 22 | "voucher_no", 23 | "company_tax_address", 24 | "customer_section", 25 | "party", 26 | "party_name", 27 | "column_break_9", 28 | "tax_id", 29 | "branch_code", 30 | "reference_section", 31 | "company", 32 | "gl_entry", 33 | "column_break_13", 34 | "against_voucher_type", 35 | "against_voucher", 36 | "amended_from" 37 | ], 38 | "fields": [ 39 | { 40 | "fieldname": "naming_series", 41 | "fieldtype": "Select", 42 | "label": "Series", 43 | "options": "CI.YY.", 44 | "reqd": 1 45 | }, 46 | { 47 | "fetch_from": "gl_entry.posting_date", 48 | "fetch_if_empty": 1, 49 | "fieldname": "date", 50 | "fieldtype": "Date", 51 | "in_list_view": 1, 52 | "in_standard_filter": 1, 53 | "label": "Date" 54 | }, 55 | { 56 | "allow_on_submit": 1, 57 | "default": "0", 58 | "fieldname": "months_delayed", 59 | "fieldtype": "Select", 60 | "label": "Delayed (months)", 61 | "options": "0\n1\n2\n3\n4\n5\n6", 62 | "reqd": 1 63 | }, 64 | { 65 | "allow_on_submit": 1, 66 | "fieldname": "report_date", 67 | "fieldtype": "Date", 68 | "label": "Report Date", 69 | "read_only": 1 70 | }, 71 | { 72 | "fieldname": "column_break_6", 73 | "fieldtype": "Column Break" 74 | }, 75 | { 76 | "fetch_from": "gl_entry.account", 77 | "fieldname": "account", 78 | "fieldtype": "Link", 79 | "in_list_view": 1, 80 | "in_standard_filter": 1, 81 | "label": "Account", 82 | "options": "Account", 83 | "search_index": 1 84 | }, 85 | { 86 | "allow_on_submit": 1, 87 | "fieldname": "tax_base", 88 | "fieldtype": "Currency", 89 | "in_list_view": 1, 90 | "label": "Tax Base", 91 | "options": "Company:company:default_currency" 92 | }, 93 | { 94 | "fieldname": "tax_amount", 95 | "fieldtype": "Currency", 96 | "in_list_view": 1, 97 | "label": "Tax Amount", 98 | "options": "Company:company:default_currency" 99 | }, 100 | { 101 | "fieldname": "column_break_15", 102 | "fieldtype": "Column Break" 103 | }, 104 | { 105 | "fetch_from": "gl_entry.voucher_type", 106 | "fieldname": "voucher_type", 107 | "fieldtype": "Link", 108 | "label": "Voucher Type", 109 | "options": "DocType", 110 | "search_index": 1 111 | }, 112 | { 113 | "fetch_from": "gl_entry.voucher_no", 114 | "fieldname": "voucher_no", 115 | "fieldtype": "Dynamic Link", 116 | "in_list_view": 1, 117 | "in_standard_filter": 1, 118 | "label": "Voucher No", 119 | "options": "voucher_type", 120 | "search_index": 1 121 | }, 122 | { 123 | "allow_on_submit": 1, 124 | "fieldname": "company_tax_address", 125 | "fieldtype": "Link", 126 | "label": "Company Tax Address", 127 | "options": "Address" 128 | }, 129 | { 130 | "fieldname": "customer_section", 131 | "fieldtype": "Section Break", 132 | "label": "Customer" 133 | }, 134 | { 135 | "fieldname": "party", 136 | "fieldtype": "Link", 137 | "in_standard_filter": 1, 138 | "label": "Party", 139 | "options": "Customer", 140 | "reqd": 1, 141 | "search_index": 1 142 | }, 143 | { 144 | "fetch_from": "party.customer_name", 145 | "fieldname": "party_name", 146 | "fieldtype": "Data", 147 | "label": "Party Name" 148 | }, 149 | { 150 | "fieldname": "column_break_9", 151 | "fieldtype": "Column Break" 152 | }, 153 | { 154 | "fieldname": "tax_id", 155 | "fieldtype": "Data", 156 | "is_virtual": 1, 157 | "label": "Tax ID", 158 | "options": "frappe.get_doc('Customer', doc.party).tax_id" 159 | }, 160 | { 161 | "fieldname": "branch_code", 162 | "fieldtype": "Data", 163 | "is_virtual": 1, 164 | "label": "Branch Code", 165 | "options": "frappe.get_doc('Customer', doc.party).branch_code" 166 | }, 167 | { 168 | "fieldname": "reference_section", 169 | "fieldtype": "Section Break", 170 | "label": "Reference" 171 | }, 172 | { 173 | "fieldname": "company", 174 | "fieldtype": "Link", 175 | "in_standard_filter": 1, 176 | "label": "Company", 177 | "options": "Company", 178 | "print_hide": 1, 179 | "remember_last_selected_value": 1, 180 | "reqd": 1 181 | }, 182 | { 183 | "fieldname": "gl_entry", 184 | "fieldtype": "Link", 185 | "label": "GL Entry", 186 | "options": "GL Entry" 187 | }, 188 | { 189 | "fieldname": "column_break_13", 190 | "fieldtype": "Column Break" 191 | }, 192 | { 193 | "fieldname": "against_voucher_type", 194 | "fieldtype": "Link", 195 | "label": "Against Voucher Type", 196 | "options": "DocType" 197 | }, 198 | { 199 | "fieldname": "against_voucher", 200 | "fieldtype": "Dynamic Link", 201 | "label": "Against Voucher", 202 | "options": "against_voucher_type" 203 | }, 204 | { 205 | "fieldname": "amended_from", 206 | "fieldtype": "Link", 207 | "label": "Amended From", 208 | "no_copy": 1, 209 | "options": "Sales Tax Invoice", 210 | "print_hide": 1, 211 | "read_only": 1 212 | } 213 | ], 214 | "is_submittable": 1, 215 | "links": [], 216 | "modified": "2023-03-04 12:13:55.095080", 217 | "modified_by": "Administrator", 218 | "module": "Thai Tax", 219 | "name": "Sales Tax Invoice", 220 | "naming_rule": "By \"Naming Series\" field", 221 | "owner": "Administrator", 222 | "permissions": [ 223 | { 224 | "cancel": 1, 225 | "create": 1, 226 | "delete": 1, 227 | "email": 1, 228 | "export": 1, 229 | "print": 1, 230 | "read": 1, 231 | "report": 1, 232 | "role": "Accounts Manager", 233 | "share": 1, 234 | "submit": 1, 235 | "write": 1 236 | }, 237 | { 238 | "cancel": 1, 239 | "create": 1, 240 | "email": 1, 241 | "export": 1, 242 | "print": 1, 243 | "read": 1, 244 | "report": 1, 245 | "role": "Accounts User", 246 | "share": 1, 247 | "submit": 1, 248 | "write": 1 249 | } 250 | ], 251 | "sort_field": "modified", 252 | "sort_order": "DESC", 253 | "states": [], 254 | "track_changes": 1 255 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/purchase_tax_invoice/purchase_tax_invoice.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_guest_to_view": 1, 4 | "allow_rename": 1, 5 | "autoname": "naming_series:", 6 | "creation": "2023-03-04 12:10:20.931148", 7 | "default_view": "List", 8 | "doctype": "DocType", 9 | "editable_grid": 1, 10 | "engine": "InnoDB", 11 | "field_order": [ 12 | "naming_series", 13 | "number", 14 | "date", 15 | "months_delayed", 16 | "report_date", 17 | "column_break_6", 18 | "account", 19 | "tax_base", 20 | "tax_amount", 21 | "column_break_10", 22 | "voucher_type", 23 | "voucher_no", 24 | "company_tax_address", 25 | "supplier_section", 26 | "party", 27 | "party_name", 28 | "column_break_9", 29 | "tax_id", 30 | "branch_code", 31 | "reference_section", 32 | "company", 33 | "gl_entry", 34 | "column_break_13", 35 | "against_voucher_type", 36 | "against_voucher", 37 | "amended_from" 38 | ], 39 | "fields": [ 40 | { 41 | "fieldname": "naming_series", 42 | "fieldtype": "Select", 43 | "label": "Series", 44 | "options": "SI.YY.", 45 | "reqd": 1 46 | }, 47 | { 48 | "allow_on_submit": 1, 49 | "fieldname": "number", 50 | "fieldtype": "Data", 51 | "in_list_view": 1, 52 | "in_standard_filter": 1, 53 | "label": "Number" 54 | }, 55 | { 56 | "allow_on_submit": 1, 57 | "fetch_from": "gl_entry.posting_date", 58 | "fetch_if_empty": 1, 59 | "fieldname": "date", 60 | "fieldtype": "Date", 61 | "in_list_view": 1, 62 | "in_standard_filter": 1, 63 | "label": "Date" 64 | }, 65 | { 66 | "allow_on_submit": 1, 67 | "default": "0", 68 | "fieldname": "months_delayed", 69 | "fieldtype": "Select", 70 | "label": "Delayed (months)", 71 | "options": "0\n1\n2\n3\n4\n5\n6", 72 | "reqd": 1 73 | }, 74 | { 75 | "allow_on_submit": 1, 76 | "fieldname": "report_date", 77 | "fieldtype": "Date", 78 | "label": "Report Date", 79 | "read_only": 1 80 | }, 81 | { 82 | "fieldname": "column_break_6", 83 | "fieldtype": "Column Break" 84 | }, 85 | { 86 | "fetch_from": "gl_entry.account", 87 | "fieldname": "account", 88 | "fieldtype": "Link", 89 | "in_list_view": 1, 90 | "in_standard_filter": 1, 91 | "label": "Account", 92 | "options": "Account", 93 | "search_index": 1 94 | }, 95 | { 96 | "allow_on_submit": 1, 97 | "fieldname": "tax_base", 98 | "fieldtype": "Currency", 99 | "in_list_view": 1, 100 | "label": "Tax Base", 101 | "options": "Company:company:default_currency" 102 | }, 103 | { 104 | "allow_on_submit": 1, 105 | "fieldname": "tax_amount", 106 | "fieldtype": "Currency", 107 | "in_list_view": 1, 108 | "label": "Tax Amount", 109 | "options": "Company:company:default_currency" 110 | }, 111 | { 112 | "fieldname": "column_break_10", 113 | "fieldtype": "Column Break" 114 | }, 115 | { 116 | "fetch_from": "gl_entry.voucher_type", 117 | "fieldname": "voucher_type", 118 | "fieldtype": "Link", 119 | "label": "Voucher Type", 120 | "options": "DocType", 121 | "search_index": 1 122 | }, 123 | { 124 | "fetch_from": "gl_entry.voucher_no", 125 | "fieldname": "voucher_no", 126 | "fieldtype": "Dynamic Link", 127 | "in_list_view": 1, 128 | "in_standard_filter": 1, 129 | "label": "Voucher No", 130 | "options": "voucher_type", 131 | "search_index": 1 132 | }, 133 | { 134 | "allow_on_submit": 1, 135 | "fieldname": "company_tax_address", 136 | "fieldtype": "Link", 137 | "label": "Company Tax Address", 138 | "options": "Address" 139 | }, 140 | { 141 | "fieldname": "supplier_section", 142 | "fieldtype": "Section Break", 143 | "label": "Supplier" 144 | }, 145 | { 146 | "allow_on_submit": 1, 147 | "fieldname": "party", 148 | "fieldtype": "Link", 149 | "in_standard_filter": 1, 150 | "label": "Party", 151 | "options": "Supplier", 152 | "reqd": 1, 153 | "search_index": 1 154 | }, 155 | { 156 | "allow_on_submit": 1, 157 | "fetch_from": "party.supplier_name", 158 | "fieldname": "party_name", 159 | "fieldtype": "Data", 160 | "label": "Party Name" 161 | }, 162 | { 163 | "fieldname": "column_break_9", 164 | "fieldtype": "Column Break" 165 | }, 166 | { 167 | "allow_on_submit": 1, 168 | "fieldname": "tax_id", 169 | "fieldtype": "Data", 170 | "is_virtual": 1, 171 | "label": "Tax ID", 172 | "options": "frappe.get_doc('Supplier', doc.party).tax_id" 173 | }, 174 | { 175 | "fieldname": "branch_code", 176 | "fieldtype": "Data", 177 | "is_virtual": 1, 178 | "label": "Branch Code", 179 | "options": "frappe.get_doc('Supplier', doc.party).branch_code" 180 | }, 181 | { 182 | "fieldname": "reference_section", 183 | "fieldtype": "Section Break", 184 | "label": "Reference" 185 | }, 186 | { 187 | "fieldname": "company", 188 | "fieldtype": "Link", 189 | "in_standard_filter": 1, 190 | "label": "Company", 191 | "options": "Company", 192 | "print_hide": 1, 193 | "remember_last_selected_value": 1, 194 | "reqd": 1 195 | }, 196 | { 197 | "fieldname": "gl_entry", 198 | "fieldtype": "Link", 199 | "label": "GL Entry", 200 | "options": "GL Entry" 201 | }, 202 | { 203 | "fieldname": "column_break_13", 204 | "fieldtype": "Column Break" 205 | }, 206 | { 207 | "fieldname": "against_voucher_type", 208 | "fieldtype": "Link", 209 | "label": "Against Voucher Type", 210 | "options": "DocType" 211 | }, 212 | { 213 | "fieldname": "against_voucher", 214 | "fieldtype": "Dynamic Link", 215 | "label": "Against Voucher", 216 | "options": "against_voucher_type" 217 | }, 218 | { 219 | "fieldname": "amended_from", 220 | "fieldtype": "Link", 221 | "label": "Amended From", 222 | "no_copy": 1, 223 | "options": "Purchase Tax Invoice", 224 | "print_hide": 1, 225 | "read_only": 1 226 | } 227 | ], 228 | "is_submittable": 1, 229 | "links": [], 230 | "modified": "2023-03-04 12:14:49.270413", 231 | "modified_by": "Administrator", 232 | "module": "Thai Tax", 233 | "name": "Purchase Tax Invoice", 234 | "naming_rule": "By \"Naming Series\" field", 235 | "owner": "Administrator", 236 | "permissions": [ 237 | { 238 | "cancel": 1, 239 | "create": 1, 240 | "delete": 1, 241 | "email": 1, 242 | "export": 1, 243 | "print": 1, 244 | "read": 1, 245 | "report": 1, 246 | "role": "Accounts Manager", 247 | "share": 1, 248 | "submit": 1, 249 | "write": 1 250 | }, 251 | { 252 | "cancel": 1, 253 | "create": 1, 254 | "email": 1, 255 | "export": 1, 256 | "print": 1, 257 | "read": 1, 258 | "report": 1, 259 | "role": "Accounts User", 260 | "share": 1, 261 | "submit": 1, 262 | "write": 1 263 | } 264 | ], 265 | "show_title_field_in_link": 1, 266 | "sort_field": "modified", 267 | "sort_order": "DESC", 268 | "states": [], 269 | "title_field": "number", 270 | "track_changes": 1 271 | } -------------------------------------------------------------------------------- /thai_tax/thai_tax/doctype/withholding_tax_items/withholding_tax_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-03-04 12:09:11.802696", 5 | "default_view": "List", 6 | "doctype": "DocType", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "type_of_income", 11 | "description", 12 | "column_break_2", 13 | "tax_base", 14 | "tax_rate", 15 | "tax_amount", 16 | "section_break_7", 17 | "incoming_types" 18 | ], 19 | "fields": [ 20 | { 21 | "columns": 1, 22 | "fieldname": "type_of_income", 23 | "fieldtype": "Select", 24 | "in_list_view": 1, 25 | "label": "Type Of Income", 26 | "options": "\n1\n2\n3\n4\n4.1.1\n4.1.2\n4.1.3\n4.1.4\n4.2.1\n4.2.2\n4.2.3\n4.2.4\n4.2.5\n5\n6", 27 | "reqd": 1 28 | }, 29 | { 30 | "columns": 3, 31 | "fieldname": "description", 32 | "fieldtype": "Data", 33 | "in_list_view": 1, 34 | "label": "Description", 35 | "length": 240 36 | }, 37 | { 38 | "fieldname": "column_break_2", 39 | "fieldtype": "Column Break" 40 | }, 41 | { 42 | "columns": 2, 43 | "fieldname": "tax_base", 44 | "fieldtype": "Currency", 45 | "in_list_view": 1, 46 | "label": "Tax Base", 47 | "options": "Company:company:default_currency" 48 | }, 49 | { 50 | "columns": 1, 51 | "fieldname": "tax_rate", 52 | "fieldtype": "Percent", 53 | "in_list_view": 1, 54 | "label": "Tax Rate", 55 | "precision": "0" 56 | }, 57 | { 58 | "columns": 1, 59 | "fieldname": "tax_amount", 60 | "fieldtype": "Currency", 61 | "in_list_view": 1, 62 | "label": "Tax Amount", 63 | "options": "Company:company:default_currency" 64 | }, 65 | { 66 | "fieldname": "section_break_7", 67 | "fieldtype": "Section Break" 68 | }, 69 | { 70 | "fieldname": "incoming_types", 71 | "fieldtype": "HTML", 72 | "label": "Incoming Types", 73 | "options": "

Types of Income

\n1. \u0e40\u0e07\u0e34\u0e19\u0e40\u0e14\u0e37\u0e2d\u0e19 \u0e04\u0e48\u0e32\u0e08\u0e49\u0e32\u0e07 \u0e2f\u0e25\u0e2f 40(1)
\n2. \u0e04\u0e48\u0e32\u0e18\u0e23\u0e23\u0e21\u0e40\u0e19\u0e35\u0e22\u0e21 \u0e04\u0e48\u0e32\u0e19\u0e32\u0e22\u0e2b\u0e19\u0e49\u0e32 \u0e2f\u0e25\u0e2f 40(2)
\n3. \u0e04\u0e48\u0e32\u0e41\u0e2b\u0e48\u0e07\u0e25\u0e34\u0e02\u0e2a\u0e34\u0e17\u0e18\u0e34\u0e4c \u0e2f\u0e25\u0e2f 40(3)
\n4. \u0e14\u0e2d\u0e01\u0e40\u0e1a\u0e35\u0e49\u0e22 \u0e2f\u0e25\u0e2f 40(4)\u0e01
\n4.1.1 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (1.1) \u0e01\u0e34\u0e08\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e2a\u0e35\u0e22\u0e20\u0e32\u0e29\u0e35\u0e40\u0e07\u0e34\u0e19\u0e44\u0e14\u0e49\u0e19\u0e34\u0e15\u0e34\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e23\u0e49\u0e2d\u0e22\u0e25\u0e30 30 \u0e02\u0e2d\u0e07\u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34
\n4.1.2 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (1.2) \u0e01\u0e34\u0e08\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e2a\u0e35\u0e22\u0e20\u0e32\u0e29\u0e35\u0e40\u0e07\u0e34\u0e19\u0e44\u0e14\u0e49\u0e19\u0e34\u0e15\u0e34\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e23\u0e49\u0e2d\u0e22\u0e25\u0e30 25 \u0e02\u0e2d\u0e07\u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34
\n4.1.3 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (1.3) \u0e01\u0e34\u0e08\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e2a\u0e35\u0e22\u0e20\u0e32\u0e29\u0e35\u0e40\u0e07\u0e34\u0e19\u0e44\u0e14\u0e49\u0e19\u0e34\u0e15\u0e34\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e23\u0e49\u0e2d\u0e22\u0e25\u0e30 20 \u0e02\u0e2d\u0e07\u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34
\n4.1.4 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (1.4) \u0e01\u0e34\u0e08\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e2a\u0e35\u0e22\u0e20\u0e32\u0e29\u0e35\u0e40\u0e07\u0e34\u0e19\u0e44\u0e14\u0e49\u0e19\u0e34\u0e15\u0e34\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e23\u0e49\u0e2d\u0e22\u0e25\u0e30 \u0e2d\u0e37\u0e48\u0e19\u0e46 (\u0e23\u0e30\u0e1a\u0e38) \u0e02\u0e2d\u0e07\u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34
\n4.2.1 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (2.1) \u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34\u0e01\u0e34\u0e08\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e22\u0e01\u0e40\u0e27\u0e49\u0e19\u0e20\u0e32\u0e29\u0e35\u0e40\u0e07\u0e34\u0e19\u0e44\u0e14\u0e49\u0e19\u0e34\u0e15\u0e34\u0e1a\u0e38\u0e04\u0e04\u0e25
\n4.2.2 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (2.2) \u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e22\u0e01\u0e40\u0e27\u0e49\u0e19\u0e44\u0e21\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e19\u0e33\u0e21\u0e32\u0e23\u0e27\u0e21\u0e04\u0e33\u0e19\u0e27\u0e13\u0e40\u0e1b\u0e47\u0e19\u0e23\u0e32\u0e22\u0e44\u0e14\u0e49
\n4.2.3 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (2.3) \u0e01\u0e33\u0e44\u0e23\u0e2a\u0e38\u0e17\u0e18\u0e34\u0e2a\u0e48\u0e27\u0e19\u0e17\u0e35\u0e48\u0e2b\u0e31\u0e01\u0e1c\u0e25\u0e02\u0e32\u0e14\u0e17\u0e38\u0e19\u0e2a\u0e38\u0e17\u0e18\u0e34\u0e22\u0e01\u0e21\u0e32\u0e44\u0e21\u0e48\u0e40\u0e01\u0e34\u0e19 5 \u0e1b\u0e35
\n4.2.4 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (2.4) \u0e01\u0e33\u0e44\u0e23\u0e17\u0e35\u0e48\u0e23\u0e31\u0e1a\u0e23\u0e39\u0e49\u0e17\u0e32\u0e07\u0e1a\u0e31\u0e0d\u0e0a\u0e35\u0e42\u0e14\u0e22\u0e27\u0e34\u0e18\u0e35\u0e2a\u0e48\u0e27\u0e19\u0e44\u0e14\u0e49\u0e40\u0e2a\u0e35\u0e22
\n4.2.5 \u0e40\u0e07\u0e34\u0e19\u0e1b\u0e31\u0e19\u0e1c\u0e25 \u0e40\u0e07\u0e34\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e41\u0e1a\u0e48\u0e07\u0e01\u0e33\u0e44\u0e23 \u0e2f\u0e25\u0e2f 40(4)\u0e02 (2.5) \u0e2d\u0e37\u0e48\u0e19\u0e46 (\u0e23\u0e30\u0e1a\u0e38)
\n5. \u0e04\u0e48\u0e32\u0e08\u0e49\u0e32\u0e07\u0e17\u0e33\u0e02\u0e2d\u0e07 \u0e04\u0e48\u0e32\u0e1a\u0e23\u0e34\u0e01\u0e32\u0e23 \u0e04\u0e48\u0e32\u0e40\u0e0a\u0e48\u0e32 \u0e04\u0e48\u0e32\u0e02\u0e19\u0e2a\u0e48\u0e07 \u0e2f\u0e25\u0e2f 3 \u0e40\u0e15\u0e23\u0e2a
\n6. \u0e2d\u0e37\u0e48\u0e19\u0e46 (\u0e23\u0e30\u0e1a\u0e38)" 74 | } 75 | ], 76 | "index_web_pages_for_search": 1, 77 | "istable": 1, 78 | "links": [], 79 | "modified": "2023-03-04 12:14:18.208930", 80 | "modified_by": "Administrator", 81 | "module": "Thai Tax", 82 | "name": "Withholding Tax Items", 83 | "owner": "Administrator", 84 | "permissions": [], 85 | "sort_field": "modified", 86 | "sort_order": "DESC", 87 | "states": [] 88 | } -------------------------------------------------------------------------------- /thai_tax/public/js/payment_entry.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on("Payment Entry", { 2 | refresh(frm) { 3 | // Filter company tax address 4 | frm.set_query("company_tax_address", function () { 5 | return { 6 | filters: { 7 | is_your_company_address: true, 8 | }, 9 | }; 10 | }); 11 | // Create Deduct Withholding Tax button 12 | if (frm.doc.docstatus == 0 && frm.doc.references && frm.doc.references.length > 0) { 13 | frm.trigger("add_withholding_tax_deduction_buttons"); 14 | } 15 | // Add button to create withholding tax cert 16 | if ( 17 | frm.doc.docstatus == 1 && 18 | frm.doc.payment_type == "Pay" && 19 | frm.doc.deductions.length > 0 20 | ) { 21 | frm.trigger("add_create_withholding_tax_cert_button"); 22 | } 23 | // Create Clear Undue VAT Journal Entry 24 | if (frm.doc.docstatus == 1) { 25 | frm.trigger("add_create_undue_vat_journal_entry_button"); 26 | } 27 | }, 28 | 29 | add_withholding_tax_deduction_buttons: function (frm) { 30 | // 1. Based On Manual Selection 31 | frm.add_custom_button( 32 | __("Based On Manual Selection"), 33 | function () { 34 | frm.trigger("manual_deduct_withholding_tax"); 35 | }, 36 | __("Withhold Tax") 37 | ); 38 | // 2. Based On Paying Document's Line Items 39 | frappe.call({ 40 | method: "thai_tax.custom.payment_entry.test_require_withholding_tax", 41 | args: { 42 | doc: frm.doc, 43 | }, 44 | callback: function (r) { 45 | if (r.message) { 46 | frm.dashboard.add_comment( 47 | __( 48 | "Please be noted that, some paying documents may require Withholding Tax Deduction" 49 | ), 50 | "blue", 51 | true 52 | ); 53 | frm.add_custom_button( 54 | __("Based On Paying Document's Line Items"), 55 | function () { 56 | frm.trigger("auto_deduct_withholding_tax"); 57 | }, 58 | __("Withhold Tax") 59 | ); 60 | } 61 | }, 62 | }); 63 | }, 64 | 65 | add_create_withholding_tax_cert_button: function (frm) { 66 | frm.add_custom_button(__("Create Withholding Tax Cert"), async function () { 67 | let income_tax_form = ""; 68 | if (frm.doc.party_type == "Supplier") { 69 | income_tax_form = ( 70 | await frappe.db.get_value( 71 | frm.doc.party_type, 72 | frm.doc.party, 73 | "custom_default_income_tax_form" 74 | ) 75 | ).message.custom_default_income_tax_form; 76 | } 77 | const fields = [ 78 | { 79 | fieldtype: "Date", 80 | label: __("Date"), 81 | fieldname: "date", 82 | reqd: 1, 83 | }, 84 | { 85 | fieldtype: "Select", 86 | label: __("Income Tax Form"), 87 | fieldname: "income_tax_form", 88 | options: "PND3\nPND53", 89 | default: income_tax_form, 90 | }, 91 | { 92 | fieldtype: "Link", 93 | label: __("Company Address"), 94 | fieldname: "company_address", 95 | options: "Address", 96 | get_query: () => { 97 | return { 98 | filters: { 99 | is_your_company_address: 1, 100 | }, 101 | }; 102 | }, 103 | }, 104 | ]; 105 | frappe.prompt( 106 | fields, 107 | function (filters) { 108 | frm.events.make_withholding_tax_cert(frm, filters); 109 | }, 110 | __("Withholding Tax Cert"), 111 | __("Create Withholding Tax Cert") 112 | ); 113 | }); 114 | }, 115 | 116 | add_create_undue_vat_journal_entry_button: function (frm) { 117 | // Check first whether all tax has been cleared, to add button 118 | frappe.call({ 119 | method: "thai_tax.custom.custom_api.to_clear_undue_tax", 120 | args: { 121 | dt: cur_frm.doc.doctype, 122 | dn: cur_frm.doc.name, 123 | }, 124 | callback: function (r) { 125 | if (r.message == true) { 126 | // Add button 127 | frm.add_custom_button(__("Clear Undue Tax"), function () { 128 | frm.trigger("make_clear_vat_journal_entry"); 129 | }); 130 | } 131 | }, 132 | }); 133 | }, 134 | 135 | manual_deduct_withholding_tax: function (frm) { 136 | const fields = [ 137 | { 138 | fieldtype: "Link", 139 | label: __("WHT Type"), 140 | fieldname: "wht_type", 141 | options: "Withholding Tax Type", 142 | reqd: 1, 143 | }, 144 | ]; 145 | frappe.prompt( 146 | fields, 147 | (filters) => { 148 | frm.events.manual_add_withholding_tax_deduction(frm, filters); 149 | }, 150 | __("Deduct Withholding Tax"), 151 | __("Add Withholding Tax Deduction") 152 | ); 153 | }, 154 | 155 | auto_deduct_withholding_tax: function (frm) { 156 | frappe.confirm( 157 | __( 158 | "Scan through all reference documents for line items that require tax withholding." 159 | ), 160 | () => { 161 | frm.trigger("auto_add_withholding_tax_deduction"); 162 | } 163 | ); 164 | }, 165 | 166 | manual_add_withholding_tax_deduction: function (frm, filters) { 167 | return frappe.call({ 168 | method: "thai_tax.custom.payment_entry.get_withholding_tax_from_type", 169 | args: { 170 | filters: filters, 171 | doc: frm.doc, 172 | }, 173 | callback: function (r) { 174 | var d = frm.add_child("deductions"); 175 | d.custom_withholding_tax_type = r.message["withholding_tax_type"]; 176 | d.account = r.message["account"]; 177 | d.cost_center = r.message["cost_center"]; 178 | d.custom_withholding_tax_base = r.message["base"]; 179 | d.amount = r.message["amount"]; 180 | frm.doc.paid_amount = frm.doc.paid_amount - Math.abs(d.amount); 181 | frm.refresh(); 182 | frappe.show_alert( 183 | { 184 | message: __("Deducted {0} for Withholding Tax", [ 185 | d.amount.toLocaleString(), 186 | ]), 187 | indicator: "green", 188 | }, 189 | 5 190 | ); 191 | }, 192 | }); 193 | }, 194 | 195 | auto_add_withholding_tax_deduction: function (frm) { 196 | return frappe.call({ 197 | method: "thai_tax.custom.payment_entry.get_withholding_tax_from_docs_items", 198 | args: { 199 | doc: frm.doc, 200 | }, 201 | callback: function (r) { 202 | var deduct = 0; 203 | r.message.forEach(function (item) { 204 | var d = frm.add_child("deductions"); 205 | d.custom_withholding_tax_type = item["withholding_tax_type"]; 206 | d.account = item["account"]; 207 | d.cost_center = item["cost_center"]; 208 | d.custom_withholding_tax_base = item["base"]; 209 | d.amount = item["amount"]; 210 | deduct += d.amount; 211 | }); 212 | frm.doc.paid_amount = frm.doc.paid_amount - Math.abs(deduct); 213 | frm.refresh(); 214 | frappe.show_alert( 215 | { 216 | message: __("Deducted {0} for Withholding Tax", [deduct.toLocaleString()]), 217 | indicator: "green", 218 | }, 219 | 5 220 | ); 221 | }, 222 | }); 223 | }, 224 | 225 | make_withholding_tax_cert: function (frm, filters) { 226 | return frappe.call({ 227 | method: "thai_tax.custom.payment_entry.make_withholding_tax_cert", 228 | args: { 229 | filters: filters, 230 | doc: frm.doc, 231 | }, 232 | callback: function (r) { 233 | var doclist = frappe.model.sync(r.message); 234 | frappe.set_route("Form", doclist[0].doctype, doclist[0].name); 235 | }, 236 | }); 237 | }, 238 | 239 | make_clear_vat_journal_entry() { 240 | return frappe.call({ 241 | method: "thai_tax.custom.custom_api.make_clear_vat_journal_entry", 242 | args: { 243 | dt: cur_frm.doc.doctype, 244 | dn: cur_frm.doc.name, 245 | }, 246 | callback: function (r) { 247 | var doclist = frappe.model.sync(r.message); 248 | frappe.set_route("Form", doclist[0].doctype, doclist[0].name); 249 | }, 250 | }); 251 | }, 252 | }); 253 | -------------------------------------------------------------------------------- /thai_tax/hooks.py: -------------------------------------------------------------------------------- 1 | from . import __version__ as app_version 2 | 3 | app_name = "thai_tax" 4 | app_title = "Thai Tax" 5 | app_publisher = "Kitti U." 6 | app_description = "Thailand Taxation Compliance" 7 | app_email = "kittiu@gmail.com" 8 | app_license = "MIT" 9 | required_apps = ["erpnext"] 10 | 11 | # Includes in 12 | # ------------------ 13 | 14 | # include js, css files in header of desk.html 15 | # app_include_css = "/assets/thai_tax/css/thai_tax.css" 16 | # app_include_js = "/assets/thai_tax/js/thai_tax.js" 17 | 18 | # include js, css files in header of web template 19 | # web_include_css = "/assets/thai_tax/css/thai_tax.css" 20 | # web_include_js = "/assets/thai_tax/js/thai_tax.js" 21 | 22 | # include custom scss in every website theme (without file extension ".scss") 23 | # website_theme_scss = "thai_tax/public/scss/website" 24 | 25 | # include js, css files in header of web form 26 | # webform_include_js = {"doctype": "public/js/doctype.js"} 27 | # webform_include_css = {"doctype": "public/css/doctype.css"} 28 | 29 | # include js in page 30 | # page_js = {"page" : "public/js/file.js"} 31 | 32 | # include js in doctype views 33 | # doctype_js = {"doctype" : "public/js/doctype.js"} 34 | 35 | doctype_js = { 36 | "Journal Entry": "public/js/journal_entry.js", 37 | "Payment Entry": "public/js/payment_entry.js", 38 | "Expense Claim": "public/js/expense_claim.js", 39 | "Purchase Tax Invoice": "public/js/purchase_tax_invoice.js", 40 | "Sales Tax Invoice": "public/js/sales_tax_invoice.js", 41 | "Withholding Tax Cert": "public/js/withholding_tax_cert.js", 42 | "Address": "public/js/address.js", 43 | } 44 | 45 | 46 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 47 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 48 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 49 | 50 | # Home Pages 51 | # ---------- 52 | 53 | # application home page (will override Website Settings) 54 | # home_page = "login" 55 | 56 | # website user home page (by Role) 57 | # role_home_page = { 58 | # "Role": "home_page" 59 | # } 60 | 61 | # Generators 62 | # ---------- 63 | 64 | # automatically create page for each record of this doctype 65 | # website_generators = ["Web Page"] 66 | 67 | # Jinja 68 | # ---------- 69 | 70 | # add methods and filters to jinja environment 71 | # jinja = { 72 | # "methods": "thai_tax.utils.jinja_methods", 73 | # "filters": "thai_tax.utils.jinja_filters" 74 | # } 75 | 76 | jinja = { 77 | "methods": [ 78 | "thai_tax.utils.amount_in_bahttext", 79 | "thai_tax.utils.full_thai_date", 80 | ], 81 | } 82 | 83 | # Installation 84 | # ------------ 85 | 86 | # before_install = "thai_tax.install.before_install" 87 | after_install = "thai_tax.install.after_install" 88 | after_app_install = "thai_tax.install.after_app_install" 89 | 90 | # Uninstallation 91 | # ------------ 92 | 93 | # before_uninstall = "thai_tax.uninstall.before_uninstall" 94 | # after_uninstall = "thai_tax.uninstall.after_uninstall" 95 | before_app_uninstall = "thai_tax.uninstall.before_app_uninstall" 96 | 97 | # Desk Notifications 98 | # ------------------ 99 | # See frappe.core.notifications.get_notification_config 100 | 101 | # notification_config = "thai_tax.notifications.get_notification_config" 102 | 103 | # Permissions 104 | # ----------- 105 | # Permissions evaluated in scripted ways 106 | 107 | # permission_query_conditions = { 108 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 109 | # } 110 | # 111 | # has_permission = { 112 | # "Event": "frappe.desk.doctype.event.event.has_permission", 113 | # } 114 | 115 | # DocType Class 116 | # --------------- 117 | # Override standard doctype classes 118 | override_doctype_class = { 119 | "Employee Advance": "thai_tax.custom.employee_advance.ThaiTaxEmployeeAdvance", 120 | } 121 | 122 | # Document Events 123 | # --------------- 124 | # Hook on document methods and events 125 | 126 | # doc_events = { 127 | # "*": { 128 | # "on_update": "method", 129 | # "on_cancel": "method", 130 | # "on_trash": "method" 131 | # } 132 | # } 133 | 134 | doc_events = { 135 | "GL Entry": { 136 | "after_insert": "thai_tax.custom.custom_api.create_tax_invoice_on_gl_tax", 137 | }, 138 | "Payment Entry": { 139 | "validate": "thai_tax.custom.custom_api.validate_company_address", 140 | "on_update": "thai_tax.custom.custom_api.clear_invoice_undue_tax", 141 | "on_submit": "thai_tax.custom.payment_entry.reconcile_undue_tax", 142 | }, 143 | "Unreconcile Payment": { 144 | "on_submit": "thai_tax.custom.unreconcile_payment.unreconcile_undue_tax", 145 | }, 146 | "Purchase Invoice": { 147 | "after_insert": "thai_tax.custom.custom_api.validate_tax_invoice", 148 | "on_update": "thai_tax.custom.custom_api.validate_tax_invoice", 149 | }, 150 | "Expense Claim": { 151 | "after_insert": "thai_tax.custom.custom_api.validate_tax_invoice", 152 | "on_update": "thai_tax.custom.custom_api.validate_tax_invoice", 153 | }, 154 | "Journal Entry": { 155 | "on_update": "thai_tax.custom.custom_api.prepare_journal_entry_tax_invoice_detail", 156 | "on_submit": "thai_tax.custom.journal_entry.reconcile_undue_tax", 157 | }, 158 | } 159 | 160 | # Scheduled Tasks 161 | # --------------- 162 | 163 | # scheduler_events = { 164 | # "all": [ 165 | # "thai_tax.tasks.all" 166 | # ], 167 | # "daily": [ 168 | # "thai_tax.tasks.daily" 169 | # ], 170 | # "hourly": [ 171 | # "thai_tax.tasks.hourly" 172 | # ], 173 | # "weekly": [ 174 | # "thai_tax.tasks.weekly" 175 | # ], 176 | # "monthly": [ 177 | # "thai_tax.tasks.monthly" 178 | # ], 179 | # } 180 | 181 | # Testing 182 | # ------- 183 | 184 | # before_tests = "thai_tax.install.before_tests" 185 | 186 | # Overriding Methods 187 | # ------------------------------ 188 | # 189 | # override_whitelisted_methods = { 190 | # "frappe.desk.doctype.event.event.get_events": "thai_tax.event.get_events" 191 | # } 192 | # 193 | # each overriding function accepts a `data` argument; 194 | # generated from the base implementation of the doctype dashboard, 195 | # along with any modifications made in other Frappe apps 196 | 197 | override_doctype_dashboards = { 198 | "Purchase Invoice": "thai_tax.custom.dashboard_overrides.get_dashboard_data_for_purchase_invoice", 199 | "Sales Invoice": "thai_tax.custom.dashboard_overrides.get_dashboard_data_for_sales_invoice", 200 | "Expense Claim": "thai_tax.custom.dashboard_overrides.get_dashboard_data_for_expense_claim", 201 | } 202 | 203 | # override_doctype_dashboards = { 204 | # "Task": "thai_tax.task.get_dashboard_data" 205 | # } 206 | # exempt linked doctypes from being automatically cancelled 207 | # 208 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 209 | 210 | # Ignore links to specified DocTypes when deleting documents 211 | # ----------------------------------------------------------- 212 | 213 | # ignore_links_on_delete = ["Communication", "ToDo"] 214 | 215 | # Request Events 216 | # ---------------- 217 | # before_request = ["thai_tax.utils.before_request"] 218 | # after_request = ["thai_tax.utils.after_request"] 219 | 220 | # Job Events 221 | # ---------- 222 | # before_job = ["thai_tax.utils.before_job"] 223 | # after_job = ["thai_tax.utils.after_job"] 224 | 225 | # User Data Protection 226 | # -------------------- 227 | 228 | # user_data_fields = [ 229 | # { 230 | # "doctype": "{doctype_1}", 231 | # "filter_by": "{filter_by}", 232 | # "redact_fields": ["{field_1}", "{field_2}"], 233 | # "partial": 1, 234 | # }, 235 | # { 236 | # "doctype": "{doctype_2}", 237 | # "filter_by": "{filter_by}", 238 | # "partial": 1, 239 | # }, 240 | # { 241 | # "doctype": "{doctype_3}", 242 | # "strict": False, 243 | # }, 244 | # { 245 | # "doctype": "{doctype_4}" 246 | # } 247 | # ] 248 | 249 | # Authentication and authorization 250 | # -------------------------------- 251 | 252 | # auth_hooks = [ 253 | # "thai_tax.auth.validate" 254 | # ] 255 | 256 | on_gle_rename = "thai_tax.custom.gl_entry.rename_gl_entry_in_tax_invoice" -------------------------------------------------------------------------------- /thai_tax/constants.py: -------------------------------------------------------------------------------- 1 | ERP_CUSTOM_FIELDS = { 2 | "Payment Entry": [ 3 | { 4 | "depends_on": "eval:doc.payment_type=='Pay'", 5 | "fieldname": "has_purchase_tax_invoice", 6 | "fieldtype": "Check", 7 | "insert_after": "payment_order_status", 8 | "label": "Has Purchase Tax Invoice", 9 | }, 10 | { 11 | "fieldname": "tax_invoice", 12 | "fieldtype": "Tab Break", 13 | "insert_after": "title", 14 | "label": "Tax Invoice", 15 | }, 16 | { 17 | "fieldname": "company_tax_address", 18 | "fieldtype": "Link", 19 | "insert_after": "tax_invoice", 20 | "label": "Company Tax Address", 21 | "options": "Address", 22 | }, 23 | { 24 | "fieldname": "column_break_bqyze", 25 | "fieldtype": "Column Break", 26 | "insert_after": "company_tax_address", 27 | }, 28 | { 29 | "fieldname": "tax_base_amount", 30 | "fieldtype": "Float", 31 | "insert_after": "column_break_bqyze", 32 | "label": "Tax Base Amount", 33 | }, 34 | { 35 | "fieldname": "section_break_owjbn", 36 | "fieldtype": "Section Break", 37 | "insert_after": "tax_base_amount", 38 | }, 39 | { 40 | "allow_on_submit": 1, 41 | "fieldname": "tax_invoice_number", 42 | "fieldtype": "Data", 43 | "in_standard_filter": 1, 44 | "insert_after": "section_break_owjbn", 45 | "label": "Tax Invoice Number", 46 | "no_copy": 1, 47 | "read_only_depends_on": "eval:doc.docstatus!=0", 48 | }, 49 | { 50 | "allow_on_submit": 1, 51 | "depends_on": "eval:doc.party_type=='Employee'", 52 | "fieldname": "supplier", 53 | "fieldtype": "Link", 54 | "insert_after": "tax_invoice_number", 55 | "label": "Supplier", 56 | "no_copy": 1, 57 | "options": "Supplier", 58 | }, 59 | { 60 | "fieldname": "column_break_yio5c", 61 | "fieldtype": "Column Break", 62 | "insert_after": "supplier", 63 | }, 64 | { 65 | "allow_on_submit": 1, 66 | "fieldname": "tax_invoice_date", 67 | "fieldtype": "Date", 68 | "insert_after": "column_break_yio5c", 69 | "label": "Tax Invoice Date", 70 | "no_copy": 1, 71 | "read_only_depends_on": "eval:doc.docstatus!=0", 72 | }, 73 | { 74 | "allow_on_submit": 1, 75 | "depends_on": "eval:doc.party_type=='Employee'", 76 | "fetch_from": "supplier.supplier_name", 77 | "fieldname": "supplier_name", 78 | "fieldtype": "Data", 79 | "insert_after": "tax_invoice_date", 80 | "label": "Supplier Name", 81 | "no_copy": 1, 82 | "translatable": 1, 83 | }, 84 | ], 85 | "Payment Entry Deduction": [ 86 | { 87 | "fieldname": "custom_section_break_s4fwa", 88 | "fieldtype": "Section Break", 89 | "insert_after": "description", 90 | }, 91 | { 92 | "fieldname": "custom_withholding_tax_type", 93 | "fieldtype": "Link", 94 | "insert_after": "custom_section_break_s4fwa", 95 | "label": "Withholding Tax Type", 96 | "options": "Withholding Tax Type", 97 | }, 98 | { 99 | "fieldname": "custom_column_break_lx8hk", 100 | "fieldtype": "Column Break", 101 | "insert_after": "custom_withholding_tax_type", 102 | }, 103 | { 104 | "fieldname": "custom_withholding_tax_base", 105 | "fieldtype": "Float", 106 | "insert_after": "custom_column_break_lx8hk", 107 | "label": "Withholding Tax Base", 108 | }, 109 | ], 110 | "Supplier": [ 111 | { 112 | "default": "00000", 113 | "fieldname": "branch_code", 114 | "fieldtype": "Data", 115 | "insert_after": "irs_1099", 116 | "label": "Branch Code", 117 | }, 118 | { 119 | "fieldname": "custom_wht", 120 | "fieldtype": "Section Break", 121 | "insert_after": "tax_withholding_category", 122 | "label": "WHT", 123 | }, 124 | { 125 | "fieldname": "custom_default_income_tax_form", 126 | "fieldtype": "Select", 127 | "insert_after": "custom_wht", 128 | "label": "Default Income Tax Form", 129 | "options": "\nPND3\nPND53", 130 | }, 131 | { 132 | "fieldname": "custom_column_break_7q1md", 133 | "fieldtype": "Column Break", 134 | "insert_after": "custom_default_income_tax_form", 135 | }, 136 | ], 137 | "Customer": [ 138 | { 139 | "default": "00000", 140 | "fieldname": "branch_code", 141 | "fieldtype": "Data", 142 | "insert_after": "tax_id", 143 | "label": "Branch Code", 144 | }, 145 | ], 146 | "Journal Entry": [ 147 | { 148 | "fieldname": "tax_invoice", 149 | "fieldtype": "Tab Break", 150 | "insert_after": "auto_repeat", 151 | "label": "Tax Invoice", 152 | }, 153 | { 154 | "fieldname": "company_tax_address", 155 | "fieldtype": "Link", 156 | "insert_after": "tax_invoice", 157 | "label": "Company Tax Address", 158 | "options": "Address", 159 | }, 160 | { 161 | "fieldname": "column_break_3djv9", 162 | "fieldtype": "Column Break", 163 | "insert_after": "company_tax_address", 164 | }, 165 | { 166 | "fieldname": "for_payment", 167 | "fieldtype": "Link", 168 | "insert_after": "column_break_3djv9", 169 | "label": "For Payment", 170 | "options": "Payment Entry", 171 | }, 172 | { 173 | "fieldname": "section_break_pxm0e", 174 | "fieldtype": "Section Break", 175 | "insert_after": "for_payment", 176 | }, 177 | { 178 | "fieldname": "tax_invoice_details", 179 | "fieldtype": "Table", 180 | "insert_after": "section_break_pxm0e", 181 | "label": "Tax Invoice Details", 182 | "no_copy": 1, 183 | "options": "Journal Entry Tax Invoice Detail", 184 | }, 185 | ], 186 | "Sales Invoice": [ 187 | { 188 | "fieldname": "tax_invoice", 189 | "fieldtype": "Tab Break", 190 | "insert_after": "total_billing_amount", 191 | "label": "Tax Invoice", 192 | }, 193 | { 194 | "allow_on_submit": 1, 195 | "fieldname": "tax_invoice_number", 196 | "fieldtype": "Data", 197 | "in_list_view": 1, 198 | "in_standard_filter": 1, 199 | "insert_after": "tax_invoice", 200 | "label": "Tax Invoice Number", 201 | "no_copy": 1, 202 | }, 203 | { 204 | "fieldname": "column_break_cijbv", 205 | "fieldtype": "Column Break", 206 | "insert_after": "tax_invoice_number", 207 | }, 208 | { 209 | "allow_on_submit": 1, 210 | "fieldname": "tax_invoice_date", 211 | "fieldtype": "Date", 212 | "insert_after": "column_break_cijbv", 213 | "label": "Tax Invoice Date", 214 | "no_copy": 1, 215 | }, 216 | ], 217 | "Purchase Invoice": [ 218 | { 219 | "fieldname": "tax_invoice", 220 | "fieldtype": "Tab Break", 221 | "insert_after": "supplied_items", 222 | "label": "Tax Invoice", 223 | }, 224 | { 225 | "allow_on_submit": 1, 226 | "fieldname": "tax_invoice_number", 227 | "fieldtype": "Data", 228 | "in_list_view": 1, 229 | "in_standard_filter": 1, 230 | "insert_after": "tax_invoice", 231 | "label": "Tax Invoice Number", 232 | "no_copy": 1, 233 | "read_only_depends_on": "eval:doc.docstatus!=0", 234 | }, 235 | { 236 | "fieldname": "column_break_t0qgt", 237 | "fieldtype": "Column Break", 238 | "insert_after": "tax_invoice_number", 239 | }, 240 | { 241 | "allow_on_submit": 1, 242 | "fieldname": "tax_invoice_date", 243 | "fieldtype": "Date", 244 | "insert_after": "column_break_t0qgt", 245 | "label": "Tax Invoice Date", 246 | "no_copy": 1, 247 | "read_only_depends_on": "eval:doc.docstatus!=0", 248 | }, 249 | ], 250 | "Item": [ 251 | { 252 | "fieldname": "custom_section_break_6buh1", 253 | "fieldtype": "Section Break", 254 | "insert_after": "taxes", 255 | }, 256 | { 257 | "description": "Select withholding tax type for service item to be deducted during payment.", 258 | "fieldname": "custom_withholding_tax_type", 259 | "fieldtype": "Link", 260 | "insert_after": "custom_section_break_6buh1", 261 | "label": "Withholding Tax Type", 262 | "options": "Withholding Tax Type", 263 | }, 264 | ], 265 | "Purchase Invoice Item": [ 266 | { 267 | "description": "Default Withholding Tax Type setup on Item", 268 | "fetch_from": "item_code.custom_withholding_tax_type", 269 | "fetch_if_empty": 1, 270 | "fieldname": "custom_withholding_tax_type", 271 | "fieldtype": "Link", 272 | "insert_after": "item_tax_template", 273 | "label": "Withholding Tax Type", 274 | "options": "Withholding Tax Type", 275 | "print_hide": 1, 276 | "read_only": 1, 277 | }, 278 | ], 279 | "Sales Invoice Item": [ 280 | { 281 | "description": "Default Withholding Tax Type setup on Item", 282 | "fetch_from": "item_code.custom_withholding_tax_type", 283 | "fetch_if_empty": 1, 284 | "fieldname": "custom_withholding_tax_type", 285 | "fieldtype": "Link", 286 | "insert_after": "item_tax_template", 287 | "label": "Withholding Tax Type", 288 | "options": "Withholding Tax Type", 289 | "print_hide": 1, 290 | "read_only": 1, 291 | }, 292 | ], 293 | "Journal Entry Account": [ 294 | { 295 | "collapsible": 1, 296 | "depends_on": "eval:doc.account_type == 'Tax'", 297 | "fieldname": "overwrite_tax_invoice", 298 | "fieldtype": "Section Break", 299 | "insert_after": "credit", 300 | "label": "Overwrite Tax Invoice", 301 | }, 302 | { 303 | "fieldname": "tax_invoice_number", 304 | "fieldtype": "Data", 305 | "insert_after": "overwrite_tax_invoice", 306 | "label": "Tax Invoice Number", 307 | }, 308 | { 309 | "fieldname": "tax_invoice_date", 310 | "fieldtype": "Date", 311 | "insert_after": "tax_invoice_number", 312 | "label": "Tax Invoice Date", 313 | }, 314 | { 315 | "fieldname": "column_break_cun7x", 316 | "fieldtype": "Column Break", 317 | "insert_after": "tax_invoice_date", 318 | }, 319 | { 320 | "fieldname": "supplier", 321 | "fieldtype": "Link", 322 | "insert_after": "column_break_cun7x", 323 | "label": "Supplier", 324 | "options": "Supplier", 325 | }, 326 | { 327 | "fieldname": "customer", 328 | "fieldtype": "Link", 329 | "insert_after": "supplier", 330 | "label": "Customer", 331 | "options": "Customer", 332 | }, 333 | { 334 | "description": "Leave this value to 0 and system will auto calculate based on tax rate.", 335 | "fieldname": "tax_base_amount", 336 | "fieldtype": "Float", 337 | "insert_after": "customer", 338 | "label": "Tax Base Amount", 339 | }, 340 | ], 341 | } 342 | 343 | HRMS_CUSTOM_FIELDS = { 344 | "Expense Claim": [ 345 | { 346 | "fieldname": "tax_invoice", 347 | "fieldtype": "Tab Break", 348 | "insert_after": "total_amount_reimbursed", 349 | "label": "Tax Invoice", 350 | }, 351 | { 352 | "allow_on_submit": 1, 353 | "fieldname": "company_tax_address", 354 | "fieldtype": "Link", 355 | "insert_after": "tax_invoice", 356 | "label": "Company Tax Address", 357 | "options": "Address", 358 | }, 359 | { 360 | "fieldname": "column_break_rqacr", 361 | "fieldtype": "Column Break", 362 | "insert_after": "company_tax_address", 363 | }, 364 | { 365 | "description": "Use this field only when you want to overwrite", 366 | "fieldname": "base_amount_overwrite", 367 | "fieldtype": "Currency", 368 | "insert_after": "column_break_rqacr", 369 | "label": "Base Amount Overwrite", 370 | "no_copy": 1, 371 | "options": "Company:company:default_currency", 372 | }, 373 | { 374 | "fieldname": "section_break_uodhb", 375 | "fieldtype": "Section Break", 376 | "insert_after": "base_amount_overwrite", 377 | }, 378 | { 379 | "allow_on_submit": 1, 380 | "fieldname": "tax_invoice_number", 381 | "fieldtype": "Data", 382 | "in_standard_filter": 1, 383 | "insert_after": "section_break_uodhb", 384 | "label": "Tax Invoice Number", 385 | "read_only_depends_on": "eval:doc.docstatus!=0", 386 | }, 387 | { 388 | "allow_on_submit": 1, 389 | "fieldname": "supplier", 390 | "fieldtype": "Link", 391 | "insert_after": "tax_invoice_number", 392 | "label": "Supplier", 393 | "no_copy": 1, 394 | "options": "Supplier", 395 | }, 396 | { 397 | "fieldname": "column_break_6atpw", 398 | "fieldtype": "Column Break", 399 | "insert_after": "supplier", 400 | }, 401 | { 402 | "allow_on_submit": 1, 403 | "fieldname": "tax_invoice_date", 404 | "fieldtype": "Date", 405 | "insert_after": "column_break_6atpw", 406 | "label": "Tax Invoice Date", 407 | "no_copy": 1, 408 | "read_only_depends_on": "eval:doc.docstatus!=0", 409 | }, 410 | { 411 | "allow_on_submit": 1, 412 | "fetch_from": "supplier.supplier_name", 413 | "fieldname": "supplier_name", 414 | "fieldtype": "Data", 415 | "insert_after": "tax_invoice_date", 416 | "label": "Supplier Name", 417 | "no_copy": 1, 418 | }, 419 | ], 420 | } 421 | 422 | ERP_PROPERTY_SETTERS = { 423 | "Purchase Taxes and Charges": [ 424 | ("rate", "precision", "6", "Select"), 425 | ], 426 | "Sales Taxes and Charges": [ 427 | ("rate", "precision", "6", "Select"), 428 | ], 429 | "Advance Taxes and Charges": [ 430 | ("rate", "precision", "6", "Select"), 431 | ], 432 | } 433 | -------------------------------------------------------------------------------- /thai_tax/custom/custom_api.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | import urllib3 3 | from frappe import _ 4 | from frappe.model.meta import get_field_precision 5 | from frappe.utils import flt 6 | 7 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 8 | 9 | 10 | def create_tax_invoice_on_gl_tax(doc, method): 11 | if doc.flags.from_repost: 12 | return 13 | # Auto create Tax Invoice only when account equal to tax account. 14 | setting = frappe.get_doc("Tax Invoice Settings") 15 | doctype = False 16 | tax_amount = 0.0 17 | voucher = frappe.get_doc(doc.voucher_type, doc.voucher_no) 18 | is_return = False 19 | if doc.voucher_type in ["Sales Invoice", "Purchase Invoice"]: 20 | is_return = voucher.is_return # Case Debit/Credit Note 21 | if doc.voucher_type == "Journal Entry": 22 | is_return = voucher.reversal_of and True or False 23 | sign = is_return and -1 or 1 24 | # Tax amount, use Dr/Cr to ensure it support every case 25 | if doc.account in [setting.sales_tax_account, setting.purchase_tax_account]: 26 | tax_amount = doc.credit - doc.debit 27 | if (tax_amount > 0 and not is_return) or (tax_amount < 0 and is_return): 28 | doctype = "Sales Tax Invoice" 29 | if (tax_amount < 0 and not is_return) or (tax_amount > 0 and is_return): 30 | doctype = "Purchase Tax Invoice" 31 | tax_amount = abs(tax_amount) * sign 32 | if doctype: 33 | if voucher.docstatus == 2: 34 | tax_amount = 0 35 | if tax_amount != 0 and voucher.doctype in ["Expense Claim", "Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]: 36 | # Base amount, use base amount from origin document 37 | if voucher.doctype == "Expense Claim": 38 | base_amount = voucher.base_amount_overwrite or voucher.total_sanctioned_amount 39 | elif voucher.doctype in ["Purchase Invoice", "Sales Invoice"]: 40 | base_amount = voucher.base_net_total 41 | elif voucher.doctype == "Payment Entry": 42 | base_amount = voucher.tax_base_amount 43 | elif voucher.doctype == "Journal Entry": 44 | # For Journal Entry, use Tax Invoice Detail table as voucher 45 | voucher = frappe.get_doc("Journal Entry Tax Invoice Detail", doc.voucher_detail_no) 46 | base_amount = voucher.tax_base_amount 47 | base_amount = abs(base_amount) * sign 48 | # Validate base amount 49 | tax_rate = frappe.get_cached_value("Account", doc.account, "tax_rate") 50 | if abs((base_amount * tax_rate / 100) - tax_amount) > 0.1: 51 | frappe.throw( 52 | _( 53 | "Tax should be {}% of the base amount
" 54 | "Note: To correct base amount, fill in Base Amount Overwrite.".format( 55 | tax_rate 56 | ) 57 | ) 58 | ) 59 | tinv = create_tax_invoice(doc, doctype, base_amount, tax_amount, voucher) 60 | tinv = update_voucher_tinv(doctype, voucher, tinv) 61 | tinv.submit() 62 | 63 | 64 | def create_tax_invoice(doc, doctype, base_amount, tax_amount, voucher): 65 | tinv_dict = {} 66 | # For sales invoice / purchase invoice / payment and journal entry, we can get the party from GL 67 | gl = frappe.db.get_all( 68 | "GL Entry", 69 | filters={ 70 | "voucher_type": doc.voucher_type, 71 | "voucher_no": doc.voucher_no, 72 | "party": ["!=", ""], 73 | }, 74 | fields=["party", "party_type"], 75 | ) 76 | party = gl and gl[0].get("party") 77 | party_type = gl and gl[0].get("party_type") 78 | if doc.voucher_type == "Journal Entry": 79 | if doctype == "Sales Tax Invoice": 80 | party = voucher.customer or party 81 | if not party: 82 | frappe.throw( 83 | _( 84 | "Customer is required for Sales Tax Invoice!
" 85 | "Please edit accounting entry with Tax account and choose Customer under Overwrite Tax invoice section." 86 | ) 87 | ) 88 | if doctype == "Purchase Tax Invoice": 89 | party = voucher.supplier or party 90 | if not party: 91 | frappe.throw( 92 | _( 93 | "Supplier is required for Purchase Tax Invoice!
" 94 | "Please edit accounting entry with Tax account and choose Supplier under Overwrite Tax invoice section." 95 | ) 96 | ) 97 | je = frappe.get_doc(doc.voucher_type, doc.voucher_no) 98 | if je.for_payment: 99 | tinv_dict.update( 100 | { 101 | "against_voucher_type": "Payment Entry", 102 | "against_voucher": je.for_payment, 103 | } 104 | ) 105 | # Case Payment Entry, party must be of type customer/supplier only 106 | if doc.voucher_type == "Payment Entry" and doc.party_type == "Employee": 107 | party = voucher.supplier 108 | # Case expense claim, partner should be supplier, not employee 109 | if doc.voucher_type == "Expense Claim": 110 | party = voucher.supplier 111 | if not party: 112 | frappe.throw(_("Please fill in Supplier for Purchase Tax Invoice")) 113 | # Create Tax Invoice 114 | tinv_dict.update( 115 | { 116 | "doctype": doctype, 117 | "gl_entry": doc.name, 118 | "tax_amount": tax_amount, 119 | "tax_base": base_amount, 120 | "party": party, 121 | } 122 | ) 123 | tinv = frappe.get_doc(tinv_dict) 124 | tinv.insert(ignore_permissions=True) 125 | return tinv 126 | 127 | 128 | def update_voucher_tinv(doctype, voucher, tinv): 129 | # Set company tax address 130 | def update_company_tax_address(voucher, tinv): 131 | # From Sales Invoice and Purchase Invoice, use voucher address 132 | if tinv.voucher_type == "Sales Invoice": 133 | tinv.company_tax_address = voucher.company_address 134 | elif tinv.voucher_type == "Purchase Invoice": 135 | tinv.company_tax_address = voucher.billing_address 136 | else: # From Payment Entry, Expense Claim and Journal Entry 137 | tinv.company_tax_address = voucher.company_tax_address 138 | if not tinv.company_tax_address: 139 | frappe.throw(_("No Company Billing/Tax Address")) 140 | 141 | update_company_tax_address(voucher, tinv) 142 | 143 | # Sales Invoice - use Sales Tax Invoice as Tax Invoice 144 | # Purchase Invoice - use Bill No as Tax Invoice 145 | if doctype == "Sales Tax Invoice": 146 | voucher.tax_invoice_number = tinv.name 147 | voucher.tax_invoice_date = tinv.date 148 | tinv.report_date = tinv.date 149 | if doctype == "Purchase Tax Invoice": 150 | if not (voucher.tax_invoice_number and voucher.tax_invoice_date): 151 | frappe.throw(_("Please enter Tax Invoice Number / Tax Invoice Date")) 152 | voucher.save() 153 | tinv.number = voucher.tax_invoice_number 154 | tinv.report_date = tinv.date = voucher.tax_invoice_date 155 | voucher.save() 156 | tinv.save() 157 | return tinv 158 | 159 | 160 | def validate_company_address(doc, method): 161 | if not doc.company_tax_address: 162 | addresses = frappe.db.get_all( 163 | "Address", 164 | filters={"is_your_company_address": 1, "address_type": "Billing"}, 165 | fields=["name", "address_type"], 166 | ) 167 | if len(addresses) == 1: 168 | doc.company_tax_address = addresses[0]["name"] 169 | 170 | 171 | def validate_tax_invoice(doc, method): 172 | # If taxes contain tax account, tax invoice is required. 173 | tax_account = frappe.db.get_single_value( 174 | "Tax Invoice Settings", "purchase_tax_account" 175 | ) 176 | voucher = frappe.get_doc(doc.doctype, doc.name) 177 | has_vat = False 178 | for tax in voucher.taxes: 179 | if tax.account_head == tax_account: 180 | has_vat = True 181 | break 182 | if has_vat and not doc.tax_invoice_number: 183 | frappe.throw(_("This document require Tax Invoice Number")) 184 | if not has_vat and doc.tax_invoice_number: 185 | frappe.throw(_("This document has no due VAT, please remove Tax Invoice Number")) 186 | 187 | 188 | @frappe.whitelist() 189 | def to_clear_undue_tax(dt, dn): 190 | if is_tax_invoice_exists(dt, dn): 191 | return False 192 | if not make_clear_vat_journal_entry(dt, dn): 193 | return False 194 | return True 195 | 196 | 197 | def is_tax_invoice_exists(dt, dn): 198 | doc = frappe.get_doc(dt, dn) 199 | ptax = frappe.get_all( 200 | "Purchase Tax Invoice", 201 | or_filters={"voucher_no": doc.name, "against_voucher": doc.name}, 202 | pluck="name", 203 | ) 204 | return True if ptax else False 205 | 206 | 207 | @frappe.whitelist() 208 | def make_clear_vat_journal_entry(dt, dn): 209 | tax = frappe.get_single("Tax Invoice Settings") 210 | doc = frappe.get_doc(dt, dn) 211 | je = frappe.new_doc("Journal Entry") 212 | je.entry_type = "Journal Entry" 213 | je.supplier = doc.party_type == "Supplier" and doc.party or False 214 | je.company_tax_address = doc.company_tax_address 215 | je.for_payment = doc.name 216 | je.user_remark = _("Clear Undue Tax on %s" % doc.name) 217 | # Loop through all paid doc, pick only ones with Undue Tax 218 | base_total = 0 219 | tax_total = 0 220 | references = filter( 221 | lambda x: x.reference_doctype in ("Purchase Invoice", "Expense Claim"), doc.references 222 | ) 223 | for ref in references: 224 | if not ref.allocated_amount or not ref.total_amount: 225 | continue 226 | # Find gl entry of ref doc that has undue amount 227 | gl_entries = frappe.db.get_all( 228 | "GL Entry", 229 | filters={ 230 | "voucher_type": ref.reference_doctype, 231 | "voucher_no": ref.reference_name, 232 | }, 233 | fields=["*"], 234 | ) 235 | for gl in gl_entries: 236 | (undue_tax, base_amount, account_undue, account) = get_undue_tax(doc, ref, gl, tax) 237 | if ref.reference_doctype in ("Purchase Invoice", "Expense Claim"): 238 | undue_tax = -undue_tax 239 | base_amount = -base_amount 240 | base_total += base_amount 241 | if undue_tax: 242 | je.append( 243 | "accounts", 244 | { 245 | "account": account_undue, 246 | "credit_in_account_currency": undue_tax > 0 and undue_tax, 247 | "debit_in_account_currency": undue_tax < 0 and abs(undue_tax), 248 | "tax_base_amount": base_total, 249 | }, 250 | ) 251 | tax_total += undue_tax 252 | if not tax_total: 253 | return False 254 | # To due tax 255 | je.append( 256 | "accounts", 257 | { 258 | "account": account, 259 | "credit_in_account_currency": tax_total < 0 and abs(tax_total), 260 | "debit_in_account_currency": tax_total > 0 and tax_total, 261 | }, 262 | ) 263 | return je 264 | 265 | 266 | def clear_invoice_undue_tax(doc, method): 267 | old_doc = doc.get_doc_before_save() 268 | if ( 269 | old_doc 270 | and old_doc.total_allocated_amount == doc.total_allocated_amount 271 | and old_doc.has_purchase_tax_invoice == doc.has_purchase_tax_invoice 272 | ): 273 | return 274 | doc.taxes = [] 275 | tax = frappe.get_single("Tax Invoice Settings") 276 | base_total = 0 277 | tax_total = 0 278 | references = filter( 279 | lambda x: x.reference_doctype 280 | in ("Sales Invoice", "Purchase Invoice", "Expense Claim"), 281 | doc.references, 282 | ) 283 | for ref in references: 284 | if ( 285 | ref.reference_doctype in ("Purchase Invoice", "Expense Claim") 286 | and not doc.has_purchase_tax_invoice 287 | ): 288 | return 289 | if not ref.allocated_amount or not ref.total_amount: 290 | continue 291 | # Find gl entry of ref doc that has undue amount 292 | gl_entries = frappe.db.get_all( 293 | "GL Entry", 294 | filters={ 295 | "voucher_type": ref.reference_doctype, 296 | "voucher_no": ref.reference_name, 297 | }, 298 | fields=["*"], 299 | ) 300 | for gl in gl_entries: 301 | (undue_tax, base_amount, account_undue, account) = get_undue_tax(doc, ref, gl, tax) 302 | if ref.reference_doctype in ("Purchase Invoice", "Expense Claim"): 303 | undue_tax = -undue_tax 304 | base_amount = -base_amount 305 | base_total += base_amount 306 | if undue_tax: 307 | doc.append( 308 | "taxes", 309 | { 310 | # 'add_deduct_tax': undue_tax > 0 and 'Deduct' or 'Add', 311 | "add_deduct_tax": "Add", 312 | "description": "Clear Undue Tax", 313 | "charge_type": "Actual", 314 | "account_head": account_undue, 315 | "tax_amount": -undue_tax, 316 | }, 317 | ) 318 | tax_total += undue_tax 319 | if not tax_total: 320 | if doc.has_purchase_tax_invoice: 321 | frappe.throw( 322 | _("No undue tax amount to clear. Please uncheck 'Has Purchase Tax Invoice'") 323 | ) 324 | return 325 | # To due tax 326 | doc.append( 327 | "taxes", 328 | { 329 | # 'add_deduct_tax': tax_total > 0 and 'Add' or 'Deduct', 330 | "add_deduct_tax": "Add", 331 | "description": "Clear Undue Tax", 332 | "charge_type": "Actual", 333 | "account_head": account, 334 | "tax_amount": tax_total, 335 | }, 336 | ) 337 | doc.tax_base_amount = base_total 338 | doc.calculate_taxes() 339 | doc.save() 340 | 341 | 342 | def get_undue_tax(doc, ref, gl, tax): 343 | # Prepration 344 | undue_tax = 0 345 | base_amount = 0 346 | tax_account_undue = tax.sales_tax_account_undue 347 | tax_account = tax.sales_tax_account 348 | if ref.reference_doctype in ("Purchase Invoice", "Expense Claim"): 349 | tax_account_undue = tax.purchase_tax_account_undue 350 | tax_account = tax.purchase_tax_account 351 | credit = gl["credit"] 352 | debit = gl["debit"] 353 | alloc_percent = ref.allocated_amount / ref.total_amount 354 | # Find Base 355 | report_type = frappe.get_cached_value("Account", gl["account"], "report_type") 356 | if report_type == "Profit and Loss": 357 | base_amount = alloc_percent * (credit - debit) 358 | # Find Tax 359 | if gl["account"] == tax_account_undue: 360 | undue_tax = alloc_percent * (credit - debit) 361 | # kittiu: For now, as residual from bs_reconcile is not stable, do not use. 362 | # undue_remain = get_uncleared_tax_amount(gl, doc.payment_type) 363 | # if not undue_remain: 364 | # undue_tax = 0 365 | # else: 366 | # undue_tax = undue_tax if undue_tax < undue_remain else undue_remain 367 | # -- 368 | return (undue_tax, base_amount, tax_account_undue, tax_account) 369 | 370 | # kittiu: For now, as residual from bs_reconcile is not stable, do not use. 371 | # def get_uncleared_tax_amount(gl, payment_type): 372 | # # If module bs_reconcile is installed, uncleared_tax = residual amount 373 | # # else uncleared_tax is the debit - credit amount 374 | # uncleared_tax = gl.debit - gl.credit 375 | # if gl.get("is_reconcile"): 376 | # uncleared_tax = gl.get("residual") 377 | # if payment_type == "Receive": 378 | # uncleared_tax = -uncleared_tax 379 | # return uncleared_tax 380 | # -- 381 | 382 | def is_tax_reset(doc, tax_accounts): 383 | # For new doc, or has tax changes, do the reset 384 | if doc.docstatus != 0: 385 | return False 386 | old_doc = doc.get_doc_before_save() 387 | if old_doc: 388 | old_tax_lines = list(filter(lambda l: l.account in tax_accounts, old_doc.accounts)) 389 | new_tax_lines = list(filter(lambda l: l.account in tax_accounts, doc.accounts)) 390 | if len(old_tax_lines) != len(new_tax_lines): 391 | return True 392 | else: 393 | for tax_line in list(zip(old_tax_lines, new_tax_lines)): 394 | old_line = tax_line[0] 395 | new_line = tax_line[1] 396 | if ( 397 | old_line.tax_base_amount != new_line.tax_base_amount 398 | or old_line.debit != new_line.debit 399 | or old_line.credit != new_line.credit 400 | or old_line.supplier != new_line.supplier 401 | or old_line.customer != new_line.customer 402 | or old_line.tax_invoice_number != new_line.tax_invoice_number 403 | or str(old_line.tax_invoice_date) != str(new_line.tax_invoice_date) 404 | ): 405 | return True 406 | else: 407 | return True 408 | return False 409 | 410 | 411 | def prepare_journal_entry_tax_invoice_detail(doc, method): 412 | setting = frappe.get_doc("Tax Invoice Settings") 413 | tax_accounts = [setting.sales_tax_account, setting.purchase_tax_account] 414 | precision = get_field_precision( 415 | frappe.get_meta("Journal Entry Tax Invoice Detail").get_field("tax_base_amount") 416 | ) 417 | # Reset Tax Invoice Table 418 | reset_tax = is_tax_reset(doc, tax_accounts) 419 | if reset_tax: 420 | for d in doc.tax_invoice_details: 421 | d.delete() 422 | for tax_line in filter(lambda l: l.account in tax_accounts, doc.accounts): 423 | tax_rate = frappe.get_cached_value("Account", tax_line.account, "tax_rate") 424 | tax_amount = abs(tax_line.debit - tax_line.credit) 425 | tax_base_amount = tax_line.tax_base_amount or ( 426 | tax_rate > 0 and tax_amount * 100 / tax_rate or 0 427 | ) 428 | company_tax_address = doc.company_tax_address 429 | if not company_tax_address: 430 | addrs = frappe.get_all("Address", {"is_your_company_address": 1}, pluck="name") 431 | company_tax_address = len(addrs) == 1 and addrs[0] or "" 432 | party_name = "" 433 | if tax_line.customer: 434 | party_name = frappe.get_doc("Customer", tax_line.customer).customer_name 435 | if tax_line.supplier: 436 | party_name = frappe.get_doc("Supplier", tax_line.supplier).supplier_name 437 | tinv_detail = frappe.get_doc( 438 | { 439 | "doctype": "Journal Entry Tax Invoice Detail", 440 | "parenttype": "Journal Entry", 441 | "parentfield": "tax_invoice_details", 442 | "parent": doc.name, 443 | "company_tax_address": company_tax_address, 444 | "supplier": tax_line.supplier, 445 | "customer": tax_line.customer, 446 | "party_name": party_name, 447 | "tax_invoice_number": tax_line.tax_invoice_number, 448 | "tax_invoice_date": tax_line.tax_invoice_date, 449 | "tax_base_amount": flt(tax_base_amount, precision), 450 | "tax_amount": flt(tax_amount, precision), 451 | } 452 | ) 453 | tax_line.reference_detail_no = tinv_detail.insert().name 454 | tax_line.save() 455 | doc.reload() 456 | -------------------------------------------------------------------------------- /thai_tax/thai_tax/print_format/withholding_tax_cert/withholding_tax_cert.json: -------------------------------------------------------------------------------- 1 | { 2 | "absolute_value": 0, 3 | "align_labels_right": 0, 4 | "creation": "2023-03-07 08:30:48.850312", 5 | "css": ".o_report_withholding_tax_cert {\n background-image:url('/assets/thai_tax/images/wht/WithholdingTaxCert.png');\n background-size:210mm 297mm; width:210mm; height:297mm;\n position: relative;\n}\n.copy {\n font-size: 12px;\n position: absolute;\n top: 30px;\n left: 50%;\n transform: translateX(-50%);\n}\n.signature {\n font-size: 12px;\n position: absolute;\n top: 990px;\n left: 470px;\n}\n.sign_date {\n font-size: 12px;\n position: absolute;\n top: 1007px;\n left: 490px;\n}\n.number {\n font-size: 12px;\n position: absolute;\n top: 65px;\n left: 50px;\n}\n.number_payment {\n font-size: 12px;\n position: absolute;\n top: 85px;\n left: 50px;\n}\n.company_vat {\n font-size: 16px;\n }\n .company_vat .company_vat_1 { position: absolute; top: 110px; left: 503px; }\n .company_vat .company_vat_2 { position: absolute; top: 110px; left: 528px; }\n .company_vat .company_vat_3 { position: absolute; top: 110px; left: 544px; }\n .company_vat .company_vat_4 { position: absolute; top: 110px; left: 560px; }\n .company_vat .company_vat_5 { position: absolute; top: 110px; left: 576px; }\n .company_vat .company_vat_6 { position: absolute; top: 110px; left: 600px; }\n .company_vat .company_vat_7 { position: absolute; top: 110px; left: 616px; }\n .company_vat .company_vat_8 { position: absolute; top: 110px; left: 632px; }\n .company_vat .company_vat_9 { position: absolute; top: 110px; left: 648px; }\n .company_vat .company_vat_10 { position: absolute; top: 110px; left: 664px; }\n .company_vat .company_vat_11 { position: absolute; top: 110px; left: 688px; }\n .company_vat .company_vat_12 { position: absolute; top: 110px; left: 704px; }\n .company_vat .company_vat_13 { position: absolute; top: 110px; left: 730px; }\n.company_name {\n font-size: 14px;\n position: absolute;\n top: 128px;\n left: 80px; \n}\n.company_address {\n font-size: 12px;\n position: absolute;\n top: 166px;\n left: 80px; \n}\n.supplier_vat {\n font-size: 16px;\n }\n .supplier_vat .supplier_vat_1 { position: absolute; top: 201px; left: 503px; }\n .supplier_vat .supplier_vat_2 { position: absolute; top: 201px; left: 528px; }\n .supplier_vat .supplier_vat_3 { position: absolute; top: 201px; left: 544px; }\n .supplier_vat .supplier_vat_4 { position: absolute; top: 201px; left: 560px; }\n .supplier_vat .supplier_vat_5 { position: absolute; top: 201px; left: 576px; }\n .supplier_vat .supplier_vat_6 { position: absolute; top: 201px; left: 600px; }\n .supplier_vat .supplier_vat_7 { position: absolute; top: 201px; left: 616px; }\n .supplier_vat .supplier_vat_8 { position: absolute; top: 201px; left: 632px; }\n .supplier_vat .supplier_vat_9 { position: absolute; top: 201px; left: 648px; }\n .supplier_vat .supplier_vat_10 { position: absolute; top: 201px; left: 664px; }\n .supplier_vat .supplier_vat_11 { position: absolute; top: 201px; left: 688px; }\n .supplier_vat .supplier_vat_12 { position: absolute; top: 201px; left: 704px; }\n .supplier_vat .supplier_vat_13 { position: absolute; top: 201px; left: 730px; }\n\n.supplier_name {\n font-size: 14px;\n position: absolute;\n top: 225px;\n left: 80px; \n}\n.supplier_address {\n font-size: 12px;\n position: absolute;\n top: 266px;\n left: 80px; \n}\n.income_tax_form {\n font-size: 16px;\n }\n .income_tax_form .choice_pnd1a {position: absolute; top: 302px; left: 282px; }\n .income_tax_form .choice_pnd3 {position: absolute; top: 302px; left: 632px; }\n .income_tax_form .choice_pnd3a {position: absolute; top: 326px; left: 386px; }\n .income_tax_form .choice_pnd53 {position: absolute; top: 326px; left: 530px; }\n.wht_items {\n font-size: 12px;\n }\n .wht_items .type_1_date {position: absolute; top: 393px; left: 450px; }\n .wht_items .type_1_base {position: absolute; top: 393px; right: 145px; }\n .wht_items .type_1_tax {position: absolute; top: 393px; right: 50px; }\n \n .wht_items .type_2_date {position: absolute; top: 413px; left: 450px; }\n .wht_items .type_2_base {position: absolute; top: 413px; right: 145px; }\n .wht_items .type_2_tax {position: absolute; top: 413px; right: 50px; }\n \n .wht_items .type_3_date {position: absolute; top: 433px; left: 450px; }\n .wht_items .type_3_base {position: absolute; top: 433px; right: 145px; }\n .wht_items .type_3_tax {position: absolute; top: 433px; right: 50px; }\n \n .wht_items .type_4_date {position: absolute; top: 453px; left: 450px; }\n .wht_items .type_4_base {position: absolute; top: 453px; right: 145px; }\n .wht_items .type_4_tax {position: absolute; top: 453px; right: 50px; }\n \n .wht_items .type_411_date {position: absolute; top: 528px; left: 450px; }\n .wht_items .type_411_base {position: absolute; top: 528px; right: 145px; }\n .wht_items .type_411_tax {position: absolute; top: 528px; right: 50px; }\n \n .wht_items .type_412_date {position: absolute; top: 548px; left: 450px; }\n .wht_items .type_412_base {position: absolute; top: 548px; right: 145px; }\n .wht_items .type_412_tax {position: absolute; top: 548px; right: 50px; }\n \n .wht_items .type_413_date {position: absolute; top: 568px; left: 450px; }\n .wht_items .type_413_base {position: absolute; top: 568px; right: 145px; }\n .wht_items .type_413_tax {position: absolute; top: 568px; right: 50px; }\n \n .wht_items .type_414_date {position: absolute; top: 588px; left: 450px; }\n .wht_items .type_414_base {position: absolute; top: 588px; right: 145px; }\n .wht_items .type_414_tax {position: absolute; top: 588px; right: 50px; }\n \n .wht_items .type_421_date {position: absolute; top: 626px; left: 450px; }\n .wht_items .type_421_base {position: absolute; top: 626px; right: 145px; }\n .wht_items .type_421_tax {position: absolute; top: 626px; right: 50px; }\n \n .wht_items .type_422_date {position: absolute; top: 665px; left: 450px; }\n .wht_items .type_422_base {position: absolute; top: 665px; right: 145px; }\n .wht_items .type_422_tax {position: absolute; top: 665px; right: 50px; }\n \n .wht_items .type_423_date {position: absolute; top: 704px; left: 450px; }\n .wht_items .type_423_base {position: absolute; top: 704px; right: 145px; }\n .wht_items .type_423_tax {position: absolute; top: 704px; right: 50px; }\n \n .wht_items .type_424_date {position: absolute; top: 726px; left: 450px; }\n .wht_items .type_424_base {position: absolute; top: 726px; right: 145px; }\n .wht_items .type_424_tax {position: absolute; top: 726px; right: 50px; }\n \n .wht_items .type_425_date {position: absolute; top: 744px; left: 450px; }\n .wht_items .type_425_base {position: absolute; top: 744px; right: 145px; }\n .wht_items .type_425_tax {position: absolute; top: 744px; right: 50px; }\n \n .wht_items .type_5_date {position: absolute; top: 820px; left: 450px; }\n .wht_items .type_5_base {position: absolute; top: 820px; right: 145px; }\n .wht_items .type_5_tax {position: absolute; top: 820px; right: 50px; }\n \n .wht_items .type_6_desc {position: absolute; top: 842px; left: 130px; }\n .wht_items .type_6_date {position: absolute; top: 839px; left: 450px; }\n .wht_items .type_6_base {position: absolute; top: 839px; right: 145px; }\n .wht_items .type_6_tax {position: absolute; top: 839px; right: 50px; }\n\n.total_base {\n font-size: 12px;\n position: absolute;\n top: 865px;\n right: 145px;\n}\n.total_tax {\n font-size: 12px;\n position: absolute;\n top: 865px;\n right: 50px;\n}\n.amount_in_words {\n font-size: 12px;\n position: absolute;\n top: 894px;\n left: 250px;\n}\n.tax_payer {\n font-size: 14px;\n}\n.tax_payer .tax_payer_withholding {position: absolute; top: 945px; left: 113px; }\n.tax_payer .tax_payer_paid_one_time {position: absolute; top: 945px; left: 382px; }\n \n\n.amended_from_label {\n font-size: 12px;\n position: absolute;\n top: 15px;\n left: 650px;\n}\n.amended_from {\n font-size: 12px;\n position: absolute;\n top: 30px;\n left: 650px;\n}\n.watermark {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n font-size: 70px;\n color: rgba(0, 0, 0, 0.2); /* Semi-transparent black */\n pointer-events: none; /* Prevent interaction with the watermark */\n z-index: 1;\n}\n", 6 | "custom_format": 1, 7 | "default_print_language": "en", 8 | "disabled": 0, 9 | "doc_type": "Withholding Tax Cert", 10 | "docstatus": 0, 11 | "doctype": "Print Format", 12 | "font": "Default", 13 | "font_size": 0, 14 | "html": "\n\n{% set copies = [\n '\u0e09\u0e1a\u0e31\u0e1a\u0e17\u0e35\u0e48 1 (\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e16\u0e39\u0e01\u0e2b\u0e31\u0e01\u0e20\u0e32\u0e29\u0e35 \u0e13 \u0e17\u0e35\u0e48\u0e08\u0e48\u0e32\u0e22 \u0e43\u0e0a\u0e49\u0e41\u0e19\u0e1a\u0e1e\u0e23\u0e49\u0e2d\u0e21\u0e01\u0e31\u0e1a\u0e41\u0e1a\u0e1a\u0e41\u0e2a\u0e14\u0e07\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e20\u0e32\u0e29\u0e35)',\n '\u0e09\u0e1a\u0e31\u0e1a\u0e17\u0e35\u0e48 2 (\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e16\u0e39\u0e01\u0e2b\u0e31\u0e01\u0e20\u0e32\u0e29\u0e35 \u0e13 \u0e17\u0e35\u0e48\u0e08\u0e48\u0e32\u0e22 \u0e40\u0e01\u0e47\u0e1a\u0e44\u0e27\u0e49\u0e40\u0e1b\u0e47\u0e19\u0e2b\u0e25\u0e31\u0e01\u0e10\u0e32\u0e19)',\n '\u0e09\u0e1a\u0e31\u0e1a\u0e17\u0e35\u0e48 3 (\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e16\u0e39\u0e01\u0e2b\u0e31\u0e01\u0e20\u0e32\u0e29\u0e35 \u0e13 \u0e17\u0e35\u0e48\u0e08\u0e48\u0e32\u0e22 \u0e43\u0e0a\u0e49\u0e41\u0e19\u0e1a\u0e41\u0e2a\u0e14\u0e07\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e20\u0e32\u0e29\u0e35)',\n '\u0e09\u0e1a\u0e31\u0e1a\u0e17\u0e35\u0e48 4 (\u0e2a\u0e33\u0e40\u0e19\u0e32\u0e15\u0e34\u0e14\u0e40\u0e25\u0e48\u0e21 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e2b\u0e31\u0e01\u0e20\u0e32\u0e29\u0e35 \u0e13 \u0e17\u0e35\u0e48\u0e08\u0e48\u0e32\u0e22 \u0e40\u0e01\u0e47\u0e1a\u0e44\u0e27\u0e49\u0e40\u0e1b\u0e47\u0e19\u0e2b\u0e25\u0e31\u0e01\u0e10\u0e32\u0e19)'\n] \n%}\n\n{% for i in range(4) %}\n\n
\n
\n {% if doc.docstatus == 2 %}\n
\u0e22\u0e01\u0e40\u0e25\u0e34\u0e01
\n {% endif %}\n {% if doc.amended_from %}\n
\u0e2d\u0e2d\u0e01\u0e41\u0e17\u0e19\u0e40\u0e2d\u0e01\u0e2a\u0e32\u0e23\u0e40\u0e25\u0e02\u0e17\u0e35\u0e48
\n
{{ doc.amended_from }}
\n {% endif %}\n \n
\n {{ frappe.get_fullname(doc.owner) }}\n
\n
\n {{ frappe.utils.format_date(doc.date) | replace(\"-\", \" / \")}}\n
\n
\n {{ copies[i] }}\n
\n
\n {{ doc.name }}\n
\n
\n {{ doc.voucher_no }}\n
\n {% if doc.company_tax_id %}\n
\n
\n {{ doc.company_tax_id[:1] }}\n
\n
\n {{ doc.company_tax_id[1:2] }}\n
\n
\n {{ doc.company_tax_id[2:3] }}\n
\n
\n {{ doc.company_tax_id[3:4] }}\n
\n
\n {{ doc.company_tax_id[4:5] }}\n
\n
\n {{ doc.company_tax_id[5:6] }}\n
\n
\n {{ doc.company_tax_id[6:7] }}\n
\n
\n {{ doc.company_tax_id[7:8] }}\n
\n
\n {{ doc.company_tax_id[8:9] }}\n
\n
\n {{ doc.company_tax_id[9:10] }}\n
\n
\n {{ doc.company_tax_id[10:11] }}\n
\n
\n {{ doc.company_tax_id[11:12] }}\n
\n
\n {{ doc.company_tax_id[12:13] }}\n
\n
\n {%- endif -%}\n
\n

\n {{ doc.company }}\n

\n
\n {% set company_address = [] %}\n {% set a = frappe.get_doc(\"Address\", doc.company_address) %}\n {% for name in [a.address_line1, a.address_line2, a.city, a.county, a.state, a.pincode] %}\n {% if name != None %}\n {{ company_address.append(name) or '' }}\n {% endif %} \n {% endfor %}\n
\n {{ company_address|join(\", \") }}\n
\n {% set sup = frappe.get_doc(\"Supplier\", doc.supplier) %}\n {% if sup.tax_id %}\n
\n
\n {{ sup.tax_id[:1] }}\n
\n
\n {{ sup.tax_id[1:2] }}\n
\n
\n {{ sup.tax_id[2:3] }}\n
\n
\n {{ sup.tax_id[3:4] }}\n
\n
\n {{ sup.tax_id[4:5] }}\n
\n
\n {{ sup.tax_id[5:6] }}\n
\n
\n {{ sup.tax_id[6:7] }}\n
\n
\n {{ sup.tax_id[7:8] }}\n
\n
\n {{ sup.tax_id[8:9] }}\n
\n
\n {{ sup.tax_id[9:10] }}\n
\n
\n {{ sup.tax_id[10:11] }}\n
\n
\n {{ sup.tax_id[11:12] }}\n
\n
\n {{ sup.tax_id[12:13] }}\n
\n
\n {%- endif -%}\n
\n

\n {{ sup.supplier_name }}\n

\n
\n {% set supplier_address = [] %}\n {% set a = frappe.get_doc(\"Address\", doc.supplier_address) %}\n {% for name in [a.address_line1, a.address_line2, a.city, a.county, a.state, a.pincode] %}\n {% if name != None %}\n {{ supplier_address.append(name) or ''}}\n {% endif %} \n {% endfor%} \n
\n {{ supplier_address|join(\", \") }}\n
\n
\n {% if doc.income_tax_form == 'PND1' %}\n
X
\n {% endif %} \n {% if doc.income_tax_form == 'PND3' %}\n
X
\n {% endif %} \n {% if doc.income_tax_form == 'PND3a' %}\n
X
\n {% endif %} \n {% if doc.income_tax_form == 'PND53' %}\n
X
\n {% endif %} \n
\n
\n {% for item in doc.withholding_tax_items %}\n {% set type = item.type_of_income|replace('.', '') %}\n {% if type == '6' %}\n
\n {{ item.description }}\n
\n {% endif %}\n
\n {{ frappe.utils.format_date(doc.date) }}\n
\n
\n {{ frappe.format(item.tax_base, {'fieldtype': 'Currency'}) }}\n
\n
\n {{ frappe.format(item.tax_amount, {'fieldtype': 'Currency'}) }}\n
\n {% endfor %}\n
\n
\n {{ frappe.format(doc.withholding_tax_items | sum(attribute='tax_base'), {'fieldtype': 'Currency'}) }}\n
\n
\n {{ frappe.format(doc.withholding_tax_items | sum(attribute='tax_amount'), {'fieldtype': 'Currency'}) }}\n
\n
\n {{ amount_in_bahttext(doc.withholding_tax_items | sum(attribute='tax_amount')) }}\n
\n
\n {% if doc.tax_payer == 'Withholding' %}\n
X
\n {% endif %} \n {% if doc.tax_payer == 'Paid One Time' %}\n
X
\n {% endif %}\n
\n
\n
\n\n{% endfor %}\n", 15 | "idx": 0, 16 | "line_breaks": 0, 17 | "margin_bottom": 0.0, 18 | "margin_left": 0.0, 19 | "margin_right": 0.0, 20 | "margin_top": 0.0, 21 | "modified": "2024-10-18 17:28:29.949917", 22 | "modified_by": "Administrator", 23 | "module": "Thai Tax", 24 | "name": "Withholding Tax Cert", 25 | "owner": "Administrator", 26 | "page_number": "Hide", 27 | "print_format_builder": 0, 28 | "print_format_builder_beta": 0, 29 | "print_format_type": "Jinja", 30 | "raw_printing": 0, 31 | "show_section_headings": 0, 32 | "standard": "Yes" 33 | } --------------------------------------------------------------------------------