diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18bcb07a..02681339 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: trailing-whitespace files: 'check_run.*' @@ -19,14 +19,14 @@ repos: - id: debug-statements - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.16.0 hooks: - id: mypy exclude: ^tests/ args: ['--install-types', '--non-interactive', '--ignore-missing-imports'] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.1 hooks: - id: codespell args: ['--ignore-words-list=notin'] @@ -34,7 +34,7 @@ repos: - tomli - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 + rev: v3.20.0 hooks: - id: pyupgrade args: ['--py310-plus'] @@ -52,13 +52,13 @@ repos: args: [--remove-all-unused-imports, --in-place] - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + rev: 7.2.0 hooks: - id: flake8 additional_dependencies: ['flake8-bugbear'] - repo: https://github.com/agritheory/test_utils/ - rev: v0.17.0 + rev: v1.0.0 hooks: - id: update_pre_commit_config - id: clean_customized_doctypes @@ -73,8 +73,8 @@ repos: hooks: - id: prettier name: prettier - entry: npx prettier . --write --ignore-path .prettierignore - language: node + entry: npx prettier -w . --config .prettierrc.cjs --ignore-path .prettierignore + language: system ci: autoupdate_schedule: weekly diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index c29b9cbe..154ec568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,15 +48,15 @@ Co-authored-by: Tyler Matteson <tyler@agritheory.com> ([`061be4e`](https:/ * print format changes -* comma saperated on bothe tablec +* comma separated on both tables * commit to solve linter test -* secondary print format with same attechment +* secondary print format with same attachment * change field name in print format -* split pdf in two attechment +* split pdf in two attachment * comment to run lint @@ -170,7 +170,7 @@ Co-authored-by: viralpatel15 <viralkansodiya167@gmail.com> ([`0b4bff0`](ht * Override a function to improve error msg (#205) -* Allocated ammount validation override for msg improvement +* Allocated amount validation override for msg improvement * comment add @@ -242,7 +242,7 @@ Co-authored-by: viralpatel15 <viralkansodiya167@gmail.com> ([`80fa4cf`](ht * feat: improvement -* feat: file preview treshold +* feat: file preview threshold * fix: refactor filters to work with prettier, also fix rendering bug @@ -502,7 +502,7 @@ Resolution for `TypeError: the JSON object must be str, bytes or bytearray, not * fix: move bank validation out of override class into hook (#142) ([`f07ad5a`](https://github.com/agritheory/check_run/commit/f07ad5a1c4a9b7e424ad534ec40731f26f6feb0c)) -* fix: only increment if check numer is numeric (#139) ([`079aa52`](https://github.com/agritheory/check_run/commit/079aa52bf013e13d3350848ef011b74b99b64bf1)) +* fix: only increment if check number is numeric (#139) ([`079aa52`](https://github.com/agritheory/check_run/commit/079aa52bf013e13d3350848ef011b74b99b64bf1)) ### Unknown @@ -548,7 +548,7 @@ Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`4ccba65`] * fix: refactor frappe.db.sql to query builder for outstanding -* fix: refactor postive pay to query builder +* fix: refactor positive pay to query builder * chore: remove print statement @@ -666,9 +666,9 @@ Co-authored-by: agritheory <agritheory@users.noreply.github.com> ([`6f5023 * V14 ports (#98) -* chore: port payement entry check number fetch/save to V14 +* chore: port payment entry check number fetch/save to V14 -* chore: port ach_post procesing hook and company disc data +* chore: port ach_post processing hook and company disc data * chore: port docstatus fix for ach-only crs @@ -680,9 +680,9 @@ Co-authored-by: agritheory <agritheory@users.noreply.github.com> ([`6f5023 * The hook jenv is deprecated New variable is jinja -* chore: port ach_post procesing hook and company disc data +* chore: port ach_post processing hook and company disc data -* fix: fix savepoint wierdness +* fix: fix savepoint weirdness * fix: company discretionary data fix @@ -918,7 +918,7 @@ Not implemented yet: * test: stub UI test yaml - copied from Frappe -* test: add helper shell files, remove job contitionals +* test: add helper shell files, remove job conditionals * test: remove producer/consumer test dbs from install script @@ -956,7 +956,7 @@ Not implemented yet: * test: allow empty password = yes -* test: file wasnt saved +* test: file wasn't saved * test: mariadb version 10.5 => 10.3 @@ -992,7 +992,7 @@ Not implemented yet: * test: install apps -* test: add site adn skip assets +* test: add site and skip assets * test: ci=yes install-app diff --git a/check_run/.editorconfig b/check_run/.editorconfig new file mode 100644 index 00000000..b72767b9 --- /dev/null +++ b/check_run/.editorconfig @@ -0,0 +1,15 @@ +# Root editor config file +root = true + +# Common settings +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# python, js indentation settings +[{*.py,*.js,*.vue,*.css,*.scss,*.html}] +indent_style = tab +indent_size = 2 +max_line_length = 99 diff --git a/check_run/check_run/doctype/check_run/check_run.js b/check_run/check_run/doctype/check_run/check_run.js index c45e028a..f1fd5040 100644 --- a/check_run/check_run/doctype/check_run/check_run.js +++ b/check_run/check_run/doctype/check_run/check_run.js @@ -9,7 +9,7 @@ frappe.ui.form.on('Check Run', { show_progress_bar(frm, data, 'Processing') }) frappe.realtime.on('render_check_progress', data => { - show_progress_bar(frm, data, 'Printing') + show_progress_bar(frm, data, 'Rendering') }) }, validate: frm => { @@ -66,6 +66,17 @@ frappe.ui.form.on('Check Run', { frm.set_df_property('final_check_number', 'read_only', 1) } check_settings(frm) + $('[data-original-title="Print"]').hide() + if (frappe.model.can_print(null, frm)) { + frm.page.add_action_icon( + 'printer', + () => { + frappe.set_route('print-check-run', frm.doc.name) + }, + '', + __('Print') + ) + } }, onload_post_render: frm => { frm.page.wrapper.find('.layout-side-section').hide() @@ -343,6 +354,9 @@ function confirm_print(frm) { } function reprint_checks(frm) { + if (frm.settings.print_preview == 'Print from Print Preview') { + return + } frm.set_value('status', 'Submitted') let d = new frappe.ui.Dialog({ title: __('Re-Print'), @@ -375,43 +389,51 @@ function reprint_checks(frm) { } function ach_only(frm) { - frappe - .xcall('check_run.check_run.doctype.check_run.check_run.ach_only', { - docname: frm.doc.name, - }) - .then(r => { - if (!r.ach_only) { - if (frm.doc.docstatus == 1) { - if (frm.doc.print_count > 0 && frm.doc.status != 'Ready to Print') { - if (frappe.perm.has_perm('Check Run', 0, 'print')) { - frm.add_custom_button(__('Re-Print Checks'), () => { - reprint_checks(frm) - }) + if (frm.settings.print_preview != 'Print from Print Preview') { + frappe + .xcall('check_run.check_run.doctype.check_run.check_run.ach_only', { + docname: frm.doc.name, + }) + .then(r => { + if (!r.ach_only) { + if (frm.doc.docstatus == 1) { + if (frm.doc.print_count > 0 && frm.doc.status != 'Ready to Print') { + if (frappe.perm.has_perm('Check Run', 0, 'print')) { + frm.add_custom_button(__('Re-Print Checks'), () => { + reprint_checks(frm) + }) + } + } else if (frm.doc.print_count == 0 && frm.doc.status == 'Submitted') { + if (frappe.perm.has_perm('Check Run', 0, 'print')) { + render_checks(frm) + } } - } else if (frm.doc.print_count == 0 && frm.doc.status == 'Submitted') { + } + if (frm.doc.status == 'Ready to Print') { if (frappe.perm.has_perm('Check Run', 0, 'print')) { - render_checks(frm) + frm.add_custom_button(__('Download Checks'), () => { + download_checks(frm) + }) } + } else if ( + frm.doc.print_count == 0 && + frm.doc.status == 'Submitted' && + frm.doc.__onload.print_preview == 'Automatically Render PDF after Submit' + ) { + render_checks(frm) } } - if (frm.doc.status == 'Ready to Print') { - if (frappe.perm.has_perm('Check Run', 0, 'print')) { - frm.add_custom_button(__('Download Checks'), () => { - download_checks(frm) - }) - } - } - } - if (!r.print_checks_only) { - if (frm.doc.docstatus == 1) { - if (frappe.perm.has_perm('Check Run', 0, 'print')) { - frm.add_custom_button(__('Download NACHA File'), () => { - download_nacha(frm) - }) + if (!r.print_checks_only) { + if (frm.doc.docstatus == 1) { + if (frappe.perm.has_perm('Check Run', 0, 'print')) { + frm.add_custom_button(__('Download NACHA File'), () => { + download_nacha(frm) + }) + } } } - } - }) + }) + } } function validate_mode_of_payment_mandatory(frm) { diff --git a/check_run/check_run/doctype/check_run/check_run.py b/check_run/check_run/doctype/check_run/check_run.py index a85bf91f..f27a87a9 100644 --- a/check_run/check_run/doctype/check_run/check_run.py +++ b/check_run/check_run/doctype/check_run/check_run.py @@ -6,8 +6,11 @@ from itertools import groupby, zip_longest from io import StringIO from typing_extensions import Self - +from frappe.www.printview import validate_print_permission +from frappe.translate import print_language from PyPDF2 import PdfFileWriter +from bs4 import BeautifulSoup + import frappe from frappe.desk.query_report import run, build_xlsx_data, format_duration_fields from frappe.desk.utils import get_csv_bytes, pop_csv_params @@ -15,7 +18,6 @@ from frappe.utils import get_link_to_form from frappe.utils.data import flt from frappe.utils.data import nowdate, getdate, now, get_datetime -from frappe.utils.print_format import read_multi_pdf from frappe.utils.xlsxutils import make_xlsx from frappe.permissions import has_permission from frappe.utils.file_manager import save_file, remove_all @@ -426,14 +428,18 @@ def increment_print_count(self: Self, reprint_check_number: int | None = None) - frappe.enqueue_doc( self.doctype, self.name, - "render_check_pdf", + "render_check_run", reprint_check_number=reprint_check_number, queue="short", now=True, ) @frappe.whitelist() - def render_check_pdf(self: Self, reprint_check_number: int | None = None) -> None: + def render_check_run( + self: Self, reprint_check_number: int | None = None, pdf: bool = True + ) -> None | str: + from frappe.utils.print_format import read_multi_pdf # imported here to prevent circular imports + self.print_count = self.print_count + 1 self.set_status("Submitted") if not frappe.db.exists("File", "Home/Check Run"): @@ -449,6 +455,7 @@ def render_check_pdf(self: Self, reprint_check_number: int | None = None) -> Non self.initial_check_number = int(reprint_check_number) output = PdfFileWriter() secondary_print_output = PdfFileWriter() + html = [] transactions = json.loads(self.transactions) check_increment = 0 _transactions = [] @@ -467,20 +474,33 @@ def render_check_pdf(self: Self, reprint_check_number: int | None = None) -> Non "Payment Entry", pe, settings.secondary_print_format or frappe.get_meta("Payment Entry").default_print_format, - as_pdf=True, - output=secondary_print_output, + as_pdf=pdf, + output=secondary_print_output if pdf else "", no_letterhead=0, ) if docstatus == 1 and frappe.db.get_value("Mode of Payment", mode_of_payment, "type") == "Bank": if mode_of_payment == "Check": - output = frappe.get_print( - "Payment Entry", - pe, - settings.print_format or frappe.get_meta("Payment Entry").default_print_format, - as_pdf=True, - output=output, - no_letterhead=0, - ) + if pdf: + output = frappe.get_print( + "Payment Entry", + pe, + settings.print_format or frappe.get_meta("Payment Entry").default_print_format, + as_pdf=True, + output=output, + no_letterhead=0, + ) + else: + _output = frappe.get_print( + "Payment Entry", + pe, + settings.print_format or frappe.get_meta("Payment Entry").default_print_format, + as_pdf=False, + no_letterhead=0, + ) + soup = BeautifulSoup(_output, "html.parser") + soup.find("div", class_="action-banner").decompose() + soup.find("div", class_="print-format-gutter").unwrap() + html.append(soup.prettify()) if initial_check_number != reprint_check_number: frappe.db.set_value( "Payment Entry", pe, "reference_no", self.initial_check_number + check_increment @@ -513,22 +533,27 @@ def render_check_pdf(self: Self, reprint_check_number: int | None = None) -> Non self.db_set("status", "Ready to Print") self.db_set("print_count", self.print_count) frappe.db.set_value("Bank Account", self.bank_account, "check_number", self.final_check_number) - file_path = save_file( - f"{self.name}.pdf", read_multi_pdf(output), "Check Run", self.name, "Home/Check Run", False, 0 - ) - if settings.secondary_print_format: - save_file( - f"Supplementary {self.name}.pdf", - read_multi_pdf(secondary_print_output), - "Check Run", - self.name, - "Home/Check Run", - False, - 0, + if pdf: + file_path = save_file( + f"{self.name}.pdf", read_multi_pdf(output), "Check Run", self.name, "Home/Check Run", False, 0 + ) + if settings.secondary_print_format: + save_file( + f"Supplementary {self.name}.pdf", + read_multi_pdf(secondary_print_output), + "Check Run", + self.name, + "Home/Check Run", + False, + 0, + ) + frappe.db.commit() + frappe.publish_realtime("reload", "{}", doctype=self.doctype, docname=self.name) + return file_path + else: + return '


'.join( + html ) - frappe.db.commit() - frappe.publish_realtime("reload", "{}", doctype=self.doctype, docname=self.name) - return file_path def create_and_attach_positive_pay(self): settings = get_check_run_settings(self) @@ -1061,3 +1086,67 @@ def process_check_run(docname: str) -> None: ) doc = frappe.get_doc("Check Run", docname) doc.process_check_run() + + +@frappe.whitelist(allow_guest=True) +def download_pdf( + doctype, + name, + formattype=None, + print_format=None, + doc=None, + no_letterhead=0, + language=None, + letterhead=None, + baseurl=None, + printcss=None, +): + doc = doc or frappe.get_doc(doctype, name) + validate_print_permission(doc) + from check_run.www.print_check_run import get_check_run_format + + out = get_check_run_format( + doc, name=doc.name, doctype_to_print=formattype, print_format=print_format + ) + data = "" + for d in out.get("html"): + data += d[0] + + style = out.get("style") + + hide_image = """ + @media print { + @print { + margin: 0; + } + + .back_image { + background: none !important; + } + } + """ + + html = """""".format( + style, baseurl, printcss + ) + + html += f"" + + modified_html_string = html.replace("", hide_image + "") + html = modified_html_string + with print_language(language): + pdf_file = frappe.get_print( + doctype, + name, + html=html, + doc=doc, + as_pdf=True, + letterhead=letterhead, + no_letterhead=no_letterhead, + ) + + frappe.local.response.filename = "{name}.pdf".format( + name=name.replace(" ", "-").replace("/", "-") + ) + frappe.local.response.filecontent = pdf_file + frappe.local.response.type = "pdf" diff --git a/check_run/check_run/doctype/check_run_printable_mop/__init__.py b/check_run/check_run/doctype/check_run_printable_mop/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.json b/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.json new file mode 100644 index 00000000..237de0f9 --- /dev/null +++ b/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.json @@ -0,0 +1,30 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-07-31 17:10:07.841441", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["mode_of_payment"], + "fields": [ + { + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Mode of Payment", + "options": "Mode of Payment" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-07-31 17:10:56.744398", + "modified_by": "Administrator", + "module": "Check Run", + "name": "Check Run Printable MOP", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.py b/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.py new file mode 100644 index 00000000..093c50d6 --- /dev/null +++ b/check_run/check_run/doctype/check_run_printable_mop/check_run_printable_mop.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CheckRunPrintableMOP(Document): + pass diff --git a/check_run/check_run/doctype/check_run_settings/check_run_settings.js b/check_run/check_run/doctype/check_run_settings/check_run_settings.js index 9ba74675..3b09dad2 100644 --- a/check_run/check_run/doctype/check_run_settings/check_run_settings.js +++ b/check_run/check_run/doctype/check_run_settings/check_run_settings.js @@ -27,5 +27,13 @@ frappe.ui.form.on('Check Run Settings', { }, } }) + frm.set_query('secondary_print_format', () => { + return { + filters: { + disabled: 0, + doc_type: 'Payment Entry', + }, + } + }) }, }) diff --git a/check_run/check_run/doctype/check_run_settings/check_run_settings.json b/check_run/check_run/doctype/check_run_settings/check_run_settings.json index aae4a8fa..82e06ea5 100644 --- a/check_run/check_run/doctype/check_run_settings/check_run_settings.json +++ b/check_run/check_run/doctype/check_run_settings/check_run_settings.json @@ -11,7 +11,6 @@ "approver_role", "column_break_3", "pay_to_account", - "print_format", "section_break_4", "include_purchase_invoices", "include_journal_entries", @@ -24,11 +23,17 @@ "set_payment_entry_posting_date", "column_break_9", "number_of_invoices_per_voucher", - "secondary_print_format", "split_by_address", "automatically_release_on_hold_invoices", "file_preview_threshold", "allow_stand_alone_debit_notes", + "print_settings_section", + "print_preview", + "print_format", + "secondary_print_format", + "column_break_ctnqm", + "printable_mop_in_check_run", + "background_image", "default_modes_of_payment_section_section", "purchase_invoice", "journal_entry", @@ -247,6 +252,33 @@ "label": "Secondary Print Format", "options": "Print Format" }, + { + "fieldname": "background_image", + "fieldtype": "Attach", + "label": "Background Image" + }, + { + "default": "Automatically Render PDF After Submit", + "fieldname": "print_preview", + "fieldtype": "Select", + "label": "Print Preview", + "options": "Automatically Render PDF After Submit\nPrint from Print Preview" + }, + { + "fieldname": "print_settings_section", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "fieldname": "column_break_ctnqm", + "fieldtype": "Column Break" + }, + { + "fieldname": "printable_mop_in_check_run", + "fieldtype": "Table MultiSelect", + "label": "Printable Modes of Payment in Check Run", + "options": "Check Run Printable MOP" + }, { "default": "No", "fieldname": "allow_stand_alone_debit_notes", @@ -316,7 +348,7 @@ } ], "links": [], - "modified": "2025-06-04 16:39:03.097246", + "modified": "2025-06-12 05:33:02.366589", "modified_by": "Administrator", "module": "Check Run", "name": "Check Run Settings", diff --git a/check_run/check_run/page/__init__.py b/check_run/check_run/page/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/check_run/check_run/page/print_check_run/__init__.py b/check_run/check_run/page/print_check_run/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/check_run/check_run/page/print_check_run/print_check_run.js b/check_run/check_run/page/print_check_run/print_check_run.js new file mode 100644 index 00000000..2d1480ea --- /dev/null +++ b/check_run/check_run/page/print_check_run/print_check_run.js @@ -0,0 +1,750 @@ +frappe.pages['print-check-run'].on_page_load = function (wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + }) + + let print_view = new frappe.ui.form.PrintView(wrapper) + + $(wrapper).bind('show', () => { + const route = frappe.get_route() + const doctype = 'Check Run' + const docname = route[1] + if (!frappe.route_options || !frappe.route_options.frm) { + frappe.model.with_doc(doctype, docname, () => { + let frm = { doctype: doctype, docname: docname } + frm.doc = frappe.get_doc(doctype, docname) + frappe.model.with_doctype(doctype, () => { + frm.meta = frappe.get_meta(route[1]) + print_view.show(frm) + }) + }) + } else { + print_view.frm = frappe.route_options.frm.doctype ? frappe.route_options.frm : frappe.route_options.frm.frm + frappe.route_options.frm = null + print_view.show(print_view.frm) + } + }) +} + +frappe.ui.form.PrintView = class { + constructor(wrapper) { + this.wrapper = $(wrapper) + this.page = wrapper.page + this.make() + } + + make() { + this.print_wrapper = this.page.main.empty().html( + `` + ) + + this.print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings') + this.setup_menu() + this.setup_toolbar() + this.setup_sidebar() + this.setup_keyboard_shortcuts() + } + + set_title() { + this.page.set_title(this.frm.docname) + } + + setup_toolbar() { + this.page.set_primary_action(__('Print'), () => this.printit(), 'printer') + + this.page.add_button(__('Full Page'), () => this.render_page('/print_check_run?'), { + icon: 'full-page', + }) + + this.page.add_button(__('PDF'), () => this.render_pdf(), { icon: 'small-file' }) + + this.page.add_button(__('Refresh'), () => this.refresh_print_format(), { + icon: 'refresh', + }) + + this.page.add_action_icon( + 'file', + () => { + this.go_to_form_view() + }, + '', + __('Form') + ) + } + + get_language_options() { + return frappe.get_languages() + } + + setup_sidebar() { + frappe.db.get_doc('Check Run', frappe.get_route()[1]).then(checkrun => { + frappe.db + .get_value( + 'Check Run Settings', + { bank_account: checkrun.bank_account, pay_to_account: checkrun.pay_to_account, company: checkrun.company }, + ['number_of_invoices_per_voucher', 'print_format'] + ) + .then(r => { + let inv_per_voucher = + r && r.message && r.message.number_of_invoices_per_voucher > 0 + ? r.message.number_of_invoices_per_voucher + : 5 + let default_print_format = + r && r.message && r.message.print_format ? r.message.print_format : __('Select Print Format') + + this.sidebar = this.page.sidebar.addClass('print-preview-sidebar') + + this.doctype_to_print = this.add_sidebar_item({ + fieldtype: 'Select', + fieldname: 'doctype', + placeholder: 'Payment Entry', + options: ['Check Run', 'Payment Entry'], + default: 'Payment Entry', + change: () => { + this.print_sel[0].value = '' + this.preview() + this.refresh_print_options() + this.preview() + }, + }).$input + + this.print_sel = this.add_sidebar_item({ + fieldtype: 'Link', + fieldname: 'print_format', + label: 'Print Format', + options: 'Print Format', + change: () => this.refresh_print_format(), + default: __(default_print_format), + get_query: () => { + return { + filters: { doc_type: this.doctype_to_print[0].value }, + } + }, + }).$input + + this.invoices_per_voucher = this.add_sidebar_item({ + fieldtype: 'Int', + fieldname: 'invoices_per_voucher', + label: 'Invoices Per Voucher', + change: () => this.refresh_print_format(), + default: inv_per_voucher, + read_only: 1, + }).$input + + this.update_check_ref = this.add_sidebar_item({ + fieldtype: 'Select', + fieldname: 'update_check_ref', + label: 'Update Check Reference', + options: ['No', 'Yes'], + change: () => this.refresh_print_format(), + default: __('No'), + }).$input + }) + }) + } + + add_sidebar_item(df, is_dynamic) { + if (df.fieldtype == 'Select') { + df.input_class = 'btn btn-default btn-sm text-left' + } + + let field = frappe.ui.form.make_control({ + df: df, + parent: is_dynamic ? this.sidebar_dynamic_section : this.sidebar, + render_input: 1, + }) + + if (df.default != null) { + field.set_input(df.default) + } + + return field + } + + get_default_option_for_select(value) { + return { + label: value, + value: value, + disabled: true, + } + } + + setup_menu() { + this.page.clear_menu() + + this.page.add_menu_item(__('Print Settings'), () => { + frappe.set_route('Form', 'Print Settings') + }) + + if (this.print_settings.enable_raw_printing == '1') { + this.page.add_menu_item(__('Raw Printing Setting'), () => { + this.printer_setting_dialog() + }) + } + + if (cint(this.print_settings.enable_print_server)) { + this.page.add_menu_item(__('Select Network Printer'), () => this.network_printer_setting_dialog()) + } + } + + show(frm) { + this.frm = frm + this.set_title() + + let tasks = [this.refresh_print_options, this.preview].map(fn => fn.bind(this)) + + return frappe.run_serially(tasks) + } + + refresh_print_format() { + this.preview() + } + + setup_keyboard_shortcuts() { + this.wrapper.find('.print-toolbar a.btn-default').each((i, el) => { + frappe.ui.keys.get_shortcut_group(this.frm.page).add($(el)) + }) + } + + preview() { + let print_format = this.get_print_format() + if (print_format.print_format_builder_beta) { + this.preview_beta() + return + } + + const $print_format = this.print_wrapper.find('iframe') + this.$print_format_body = $print_format.contents() + this.get_print_html(out => { + if (!out.html) { + out.html = this.get_no_preview_html() + } + + this.setup_print_format_dom(out, $print_format) + + const print_height = $print_format.get(0).offsetHeight + const $message = this.wrapper.find('.page-break-message') + + const print_height_inches = frappe.dom.pixel_to_inches(print_height) + // if contents are large enough, indicate that it will get printed on multiple pages + // Maximum height for an A4 document is 11.69 inches + if (print_height_inches > 11.69) { + $message.text(__('This may get printed on multiple pages')) + } else { + $message.text('') + } + }) + } + + get_letterhead() { + return this.letterhead_selector.val() + } + + preview_beta() { + let print_format = this.get_print_format() + const iframe = this.print_wrapper.find('.preview-beta-wrapper iframe') + let params = new URLSearchParams({ + doctype: this.frm.doc.doctype, + name: this.frm.doc.name, + print_format: print_format.name, + }) + iframe.prop('src', `/printpreview?${params.toString()}`) + } + + setup_print_format_dom(out, $print_format) { + this.print_wrapper.find('.print-format-skeleton').remove() + let base_url = frappe.urllib.get_base_url() + let print_css = frappe.assets.bundled_asset('print.bundle.css', frappe.utils.is_rtl(this.lang_code)) + this.$print_format_body.find('html').attr('dir', frappe.utils.is_rtl(this.lang_code) ? 'rtl' : 'ltr') + this.$print_format_body.find('html').attr('lang', this.lang_code) + this.$print_format_body.find('head').html( + ` + ` + ) + if (this.doctype_to_print.val() == 'Check Run') { + this.$print_format_body.find('body').html(``) + } else { + this.$print_format_body.find('body').html(``) + + let $parentDiv = this.$print_format_body.find('.print-format-preview') + + // Use forEach to append each HTML string to the parent div + out.html.forEach(function (htmlContent) { + $parentDiv.append(htmlContent) + $parentDiv.append(`
`) + }) + } + + this.show_footer() + + this.$print_format_body.find('.print-format').css({ + display: 'flex', + flexDirection: 'column', + }) + + this.$print_format_body.find('.page-break').css({ + display: 'flex', + 'flex-direction': 'column', + flex: '1', + }) + + setTimeout(() => { + $print_format.height(this.$print_format_body.find('.print-format').outerHeight()) + }, 500) + } + + hide() { + if (this.frm.setup_done && this.frm.page.current_view_name === 'print') { + this.frm.page.set_view( + this.frm.page.previous_view_name === 'print' ? 'main' : this.frm.page.previous_view_name || 'main' + ) + } + } + + go_to_form_view() { + frappe.route_options = { + frm: this, + } + frappe.set_route('Form', this.frm.doctype, this.frm.docname) + } + + show_footer() { + // footer is hidden by default as reqd by pdf generation + // simple hack to show it in print preview + + this.$print_format_body.find('#footer-html').attr( + 'style', + ` + display: block !important; + order: 1; + margin-top: auto; + padding-top: var(--padding-xl) + ` + ) + } + + printit() { + let me = this + + if (cint(me.print_settings.enable_print_server)) { + if (localStorage.getItem('network_printer')) { + me.print_by_server() + } else { + me.network_printer_setting_dialog(() => me.print_by_server()) + } + } else if (me.get_mapped_printer().length === 1) { + // printer is already mapped in localstorage (applies for both raw and pdf ) + if (me.is_raw_printing()) { + me.get_raw_commands(function (out) { + frappe.ui.form + .qz_connect() + .then(function () { + let printer_map = me.get_mapped_printer()[0] + let data = [out.raw_commands] + let config = qz.configs.create(printer_map.printer) + return qz.print(config, data) + }) + .then(frappe.ui.form.qz_success) + .catch(err => { + frappe.ui.form.qz_fail(err) + }) + }) + } else { + frappe.show_alert( + { + message: __('PDF printing via "Raw Print" is not supported.'), + subtitle: __('Please remove the printer mapping in Printer Settings and try again.'), + indicator: 'info', + }, + 14 + ) + //Note: need to solve "Error: Cannot parse (FILE) as a PDF file" to enable qz pdf printing. + } + } else if (me.is_raw_printing()) { + // printer not mapped in localstorage and the current print format is raw printing + frappe.show_alert( + { + message: __('Printer mapping not set.'), + subtitle: __('Please set a printer mapping for this print format in the Printer Settings'), + indicator: 'warning', + }, + 14 + ) + me.printer_setting_dialog() + } else { + me.render_page('/print_check_run?', true) + } + this.confirm_print(me) + } + + print_by_server() { + let me = this + if (localStorage.getItem('network_printer')) { + frappe.call({ + method: 'frappe.utils.print_format.print_by_server', + args: { + doctype: me.frm.doc.doctype, + name: me.frm.doc.name, + printer_setting: localStorage.getItem('network_printer'), + print_format: me.selected_format(), + no_letterhead: true, + letterhead: null, + }, + callback: function () {}, + }) + } + } + network_printer_setting_dialog(callback) { + frappe.call({ + method: 'frappe.printing.doctype.network_printer_settings.network_printer_settings.get_network_printer_settings', + callback: function (r) { + if (r.message) { + let d = new frappe.ui.Dialog({ + title: __('Select Network Printer'), + fields: [ + { + label: 'Printer', + fieldname: 'printer', + fieldtype: 'Select', + reqd: 1, + options: r.message, + }, + ], + primary_action: function () { + localStorage.setItem('network_printer', d.get_values().printer) + if (typeof callback == 'function') { + callback() + } + d.hide() + }, + primary_action_label: __('Select'), + }) + d.show() + } + }, + }) + } + + render_pdf() { + let print_format = this.get_print_format() + if (print_format.print_format_builder_beta) { + let params = new URLSearchParams({ + doctype: this.frm.doc.doctype, + name: this.frm.doc.name, + print_format: print_format.name, + letterhead: null, + }) + let w = window.open(`/api/method/frappe.utils.weasyprint.download_pdf?${params}`) + if (!w) { + frappe.msgprint(__('Please enable pop-ups')) + return + } + } else { + this.render_check_run_pdf('/api/method/check_run.check_run.doctype.check_run.check_run.download_pdf?') + } + } + + set_user_lang() { + console.log(this.language_sel.val()) + this.lang_code = this.language_sel.val() + } + + render_check_run_pdf(method) { + let base_url = frappe.urllib.get_base_url() + let print_css = frappe.assets.bundled_asset('print.bundle.css', frappe.utils.is_rtl(this.lang_code)) + let w = window.open( + frappe.urllib.get_full_url(`${method} + doctype=${encodeURIComponent(this.frm.doc.doctype)} + &name=${encodeURIComponent(this.frm.doc.name)} + &formattype=${encodeURIComponent(this.doctype_to_print.val())} + &print_format=${encodeURIComponent(this.print_sel.val())} + &baseurl=${encodeURIComponent(base_url)} + &printcss=${encodeURIComponent(print_css)} + &lang=${encodeURIComponent('en')}`) + ) + } + + render_page(method, printit = false) { + let w = window.open( + frappe.urllib.get_full_url(`${method} + doctype=${encodeURIComponent(this.frm.doc.doctype)} + &name=${encodeURIComponent(this.frm.doc.name)} + &formattype=${encodeURIComponent(this.doctype_to_print)} + &lang=${encodeURIComponent('en')}`) + ) + + this.get_print_html(out => { + let base_url = frappe.urllib.get_base_url() + let print_css = frappe.assets.bundled_asset('print.bundle.css', frappe.utils.is_rtl(this.lang_code)) + w.document.write(` + `) + w.document.write(``) + w.document.close() + if (printit) { + w.print() + } + var afterPrint = function () { + w.close() + } + if (w.matchMedia) { + var mediaQueryList = w.matchMedia('print') + mediaQueryList.addListener(function (mql) { + if (!mql.matches) { + console.log('out') + afterPrint() + } + }) + } + window.onafterprint = afterPrint + }) + if (!w) { + frappe.msgprint(__('Please enable pop-ups')) + return + } + } + + get_print_html(callback) { + let print_format = this.get_print_format() + if (print_format.raw_printing) { + callback({ + html: this.get_no_preview_html(), + }) + return + } + if (this._req) { + this._req.abort() + } + this._req = frappe.call({ + method: 'check_run.www.print_check_run.get_html_and_style', + args: { + doc: this.frm.doc, + doctype_to_print: this.doctype_to_print.val(), + print_format: this.selected_format(), + no_letterhead: true, + letterhead: null, + settings: this.additional_settings, + _lang: this.lang_code, + }, + callback: function (r) { + if (!r.exc) { + callback(r.message) + } + }, + }) + } + + get_no_preview_html() { + return `
+ ${__('No Preview Available')} +
` + } + + get_mapped_printer() { + // returns a list of "print format: printer" mapping filtered by the current print format + let print_format_printer_map = this.get_print_format_printer_map() + if (print_format_printer_map[this.frm.doctype]) { + return print_format_printer_map[this.frm.doctype].filter( + printer_map => printer_map.print_format == this.selected_format() + ) + } else { + return [] + } + } + + get_print_format_printer_map() { + // returns the whole object "print_format_printer_map" stored in the localStorage. + try { + let print_format_printer_map = JSON.parse(localStorage.print_format_printer_map) + return print_format_printer_map + } catch (e) { + return {} + } + } + + async refresh_print_options() { + this.print_formats = frappe.meta.get_print_formats(this.frm.doctype) + if (this.doctype_to_print.val() == 'Payment Entry') { + this.print_formats = await frappe.xcall('check_run.www.print_check_run.get_formats', { doctype: 'Payment Entry' }) + } + if (this.doctype_to_print.val() == 'Payment Entry Secondary Format') { + this.print_formats = await frappe.xcall('check_run.www.print_check_run.get_formats', { + doctype: this.frm.docname, + }) + } + const print_format_select_val = this.print_sel.val() + this.print_sel + .empty() + .add_options([this.get_default_option_for_select(__('Select Print Format')), ...this.print_formats]) + return this.print_formats.includes(print_format_select_val) && this.print_sel.val(print_format_select_val) + } + + selected_format() { + return this.print_sel.val() || 'Standard' + } + + is_raw_printing(format) { + return this.get_print_format(format).raw_printing === 1 + } + + get_print_format(format) { + let print_format = {} + if (!format) { + format = this.selected_format() + } + + if (locals['Print Format'] && locals['Print Format'][format]) { + print_format = locals['Print Format'][format] + } + + return print_format + } + + with_letterhead() { + return cint(this.get_letterhead() !== __('No Letterhead')) + } + + set_style(style) { + frappe.dom.set_style(style || frappe.boot.print_css, 'print-style') + } + + confirm_print(me) { + if (this.update_check_ref.val() == 'No') { + return + } + + let frm = me.frm + let d = new frappe.ui.Dialog({ + title: __('Confirm Print'), + fields: [ + { + fieldname: 'ht', + fieldtype: 'HTML', + options: ` + +

`, + }, + { + fieldname: 'reprint_check_number', + fieldtype: 'Data', + label: __('New Initial Check Number'), + }, + ], + minimizable: false, + static: true, + }) + d.wrapper.find('#confirm-print').on('click', () => { + frappe + .xcall('check_run.check_run.doctype.check_run.check_run.confirm_print', { + docname: frm.doc.name, + }) + .then(() => { + d.hide() + }) + }) + d.wrapper.find('#reprint').on('click', () => { + d.fields_dict.reprint_check_number.df.reqd = 1 + let values = cur_dialog.get_values() + this.render_checks(me, frm, values.reprint_check_number || undefined) + frm.doc.status = 'Submitted' + me.page.set_indicator(__('Submitted'), 'blue') + d.hide() + }) + d.show() + } + + render_checks(me, frm, reprint_check_number = undefined) { + frappe + .call({ + method: 'increment_print_count', + doc: frm.doc, + args: { reprint_check_number: reprint_check_number }, + }) + .done(() => { + frappe.msgprint('Check Reference No updated successfully') + }) + .fail(r => {}) + } + + printer_setting_dialog() { + // dialog for the Printer Settings + this.print_format_printer_map = this.get_print_format_printer_map() + this.data = this.print_format_printer_map[this.frm.doctype] || [] + this.printer_list = [] + frappe.ui.form.qz_get_printer_list().then(data => { + this.printer_list = data + const dialog = new frappe.ui.Dialog({ + title: __('Printer Settings'), + fields: [ + { + fieldtype: 'Section Break', + }, + { + fieldname: 'printer_mapping', + fieldtype: 'Table', + label: __('Printer Mapping'), + in_place_edit: true, + data: this.data, + get_data: () => { + return this.data + }, + fields: [ + { + fieldtype: 'Select', + fieldname: 'print_format', + default: 0, + options: this.print_formats, + read_only: 0, + in_list_view: 1, + label: __('Print Format'), + }, + { + fieldtype: 'Select', + fieldname: 'printer', + default: 0, + options: this.printer_list, + read_only: 0, + in_list_view: 1, + label: __('Printer'), + }, + ], + }, + ], + primary_action: () => { + let printer_mapping = dialog.get_values()['printer_mapping'] + if (printer_mapping && printer_mapping.length) { + let print_format_list = printer_mapping.map(a => a.print_format) + let has_duplicate = print_format_list.some((item, idx) => print_format_list.indexOf(item) != idx) + if (has_duplicate) frappe.throw(__('Cannot have multiple printers mapped to a single print format.')) + } else { + printer_mapping = [] + } + dialog.print_format_printer_map = this.get_print_format_printer_map() + dialog.print_format_printer_map[this.frm.doctype] = printer_mapping + localStorage.print_format_printer_map = JSON.stringify(dialog.print_format_printer_map) + dialog.hide() + }, + primary_action_label: __('Save'), + }) + dialog.show() + if (!(this.printer_list && this.printer_list.length)) { + frappe.throw(__('No Printer is Available.')) + } + }) + } +} diff --git a/check_run/check_run/page/print_check_run/print_check_run.json b/check_run/check_run/page/print_check_run/print_check_run.json new file mode 100644 index 00000000..a40dce28 --- /dev/null +++ b/check_run/check_run/page/print_check_run/print_check_run.json @@ -0,0 +1,19 @@ +{ + "content": null, + "creation": "2024-05-14 16:02:02.168387", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2024-05-14 16:02:02.168387", + "modified_by": "Administrator", + "module": "Check Run", + "name": "print-check-run", + "owner": "Administrator", + "page_name": "print-check-run", + "roles": [], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "Check Run" +} diff --git a/check_run/check_run/page/print_check_run/print_skeleton_loading.html b/check_run/check_run/page/print_check_run/print_skeleton_loading.html new file mode 100644 index 00000000..9cdae686 --- /dev/null +++ b/check_run/check_run/page/print_check_run/print_skeleton_loading.html @@ -0,0 +1,159 @@ + diff --git a/check_run/check_run/print_format/check_run/__init__.py b/check_run/check_run/print_format/check_run/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/check_run/check_run/print_format/check_run/check_run.json b/check_run/check_run/print_format/check_run/check_run.json new file mode 100644 index 00000000..d2edbe75 --- /dev/null +++ b/check_run/check_run/print_format/check_run/check_run.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2024-06-04 04:59:24.275841", + "css": " .container_ {\n width:100%;\n display: flex;\n padding-right: 0px;\n padding-left: 0px;\n margin-right: auto;\n margin-left: auto;\n }\n.box {\n flex: 1; /* This makes each div take up equal space */\n margin: 10px;\n}\n.print-format td{\n padding:2px !important;\n}\n.print-format th{\n padding:2px !important;\n}\ntable{\n font-size:12px!important;\n}", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Check Run", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "html": "
\n\t
\n\t\t

\n\t\t\t
Check Run
\n\t\t\tACC-CR-2024-00001\n\t\t

\n\t
\n
\n
\n
\n

Check Run End Date:

\n {{doc.end_date or ''}}\n
\n
\n

Initial Check Number:

\n {{ doc.initial_check_number}}\n
\n
\n

Company

\n {{ doc.company }}\n
\n
\n
\n
\n

Posting Date:

\n {{doc.posting_date or ''}}\n
\n
\n

Final Check Number:

\n {{ doc.final_check_number }}\n
\n
\n

Paid From (Bank Account):

\n {{ doc.bank_account }}\n
\n
\n
\n
\n

Beginning Bank Account Balance:

\n {{frappe.format(doc.beg_balance,{'fieldtype':\"Currency\"} ) or ''}}\n
\n
\n

Amount in Check Run

\n {{frappe.format(doc.amount_check_run ,{'fieldtype':\"Currency\"} ) or ''}}\n
\n
\n

Accounts Payable:

\n {{ doc.pay_to_account }}\n
\n
\n{% set pe_list = frappe.db.get_list(\"Payment Entry\", {\"check_run\":doc.name}) %}\n\n \n \n \n \n \n \n \n \n \n \n {% for row in pe_list %}\n {% set doc_ = frappe.get_doc(\"Payment Entry\", row.name) %}\n {% for d in doc_.references %}\n \n \n \n \n \n \n \n \n {% endfor %}\n {% endfor %}\n \n
PartyDocumentDocument DateOutstanding AmountDue DateReference
{{ doc_.party }}{{ d.reference_name }}{{ frappe.db.get_value(d.reference_doctype, d.reference_name, \"posting_date\") or ''}}{{ doc_.paid_amount or '' }}{{ frappe.db.get_value(d.reference_doctype, d.reference_name, \"due_date\") or '' }}{{ doc_.name }}
\n\n \n \n\n", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2024-06-06 03:54:20.194111", + "modified_by": "Administrator", + "module": "Check Run", + "name": "Check Run", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} diff --git a/check_run/check_run/print_format/example_secondary_print_format/example_secondary_print_format.json b/check_run/check_run/print_format/example_secondary_print_format/example_secondary_print_format.json index 94eb9531..88bbf036 100644 --- a/check_run/check_run/print_format/example_secondary_print_format/example_secondary_print_format.json +++ b/check_run/check_run/print_format/example_secondary_print_format/example_secondary_print_format.json @@ -11,14 +11,14 @@ "doctype": "Print Format", "font": "Default", "font_size": 0, - "html": "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {% for reference in doc.references %}\r\n \r\n {% if reference.reference_doctype == 'Purchase Invoice' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Sales Invoice' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Expense Claim' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Journal Entry' %}\r\n \r\n \r\n {% endif %}\r\n \r\n \r\n \r\n {% endfor %}\r\n
\r\n Cheque Number: {{ doc.reference_no or '' }}\r\n
{{doc.party_name}} {{ frappe.utils.formatdate(doc.reference_date) or '' }} {{doc.get_formatted(\"base_paid_amount\")}}
Date Reference Amount Payment
{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.fmt_money(reference.get_formatted('total_amount'), 2, 'USD')}} {{ reference.get_formatted('allocated_amount')}}
", + "html": "{% set checkrun = frappe.get_doc(\"Check Run\", doc.check_run) %}\n{% set checkrunsetting = frappe.db.get_list(\"Check Run Settings\", {\"bank_account\": checkrun.bank_account, \"pay_to_account\": checkrun.pay_to_account , \"company\": checkrun.company}) %}\n{% set crs_doc = frappe.get_doc(\"Check Run Settings\", checkrunsetting[0]) %}\n{% set printable_mop = frappe.db.get_list(\"Check Run Printable MOP\", {\"parent\": crs_doc.name}, \"mode_of_payment\", pluck=\"mode_of_payment\") %}\n\n{% if doc.mode_of_payment in printable_mop %}\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {% for reference in doc.references %}\n \n {% if reference.reference_doctype == 'Purchase Invoice' %}\n \n \n {% elif reference.reference_doctype == 'Sales Invoice' %}\n \n \n {% elif reference.reference_doctype == 'Expense Claim' %}\n \n \n {% elif reference.reference_doctype == 'Journal Entry' %}\n \n \n {% endif %}\n \n \n \n {% endfor %}\n
\n Check Number: {{ doc.reference_no or '' }}\n
{{doc.party_name}} {{ frappe.utils.formatdate(doc.reference_date) or '' }} {{doc.get_formatted(\"base_paid_amount\")}}
Date Reference Amount Payment
{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.fmt_money(reference.get_formatted('total_amount'), 2, 'USD')}} {{ frappe.utils.fmt_money(reference.get_formatted('allocated_amount'), 2, 'USD')}}
\n{% endif %}", "idx": 0, "line_breaks": 0, "margin_bottom": 0.0, "margin_left": 0.0, "margin_right": 0.0, "margin_top": 0.0, - "modified": "2024-03-28 01:38:34.472380", + "modified": "2024-07-31 14:30:07.415365", "modified_by": "Administrator", "module": "Check Run", "name": "Example Secondary Print Format", diff --git a/check_run/check_run/print_format/example_voucher/example_voucher.json b/check_run/check_run/print_format/example_voucher/example_voucher.json index 65e12199..1489a555 100644 --- a/check_run/check_run/print_format/example_voucher/example_voucher.json +++ b/check_run/check_run/print_format/example_voucher/example_voucher.json @@ -2,7 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2022-08-30 12:27:45.736571", - "css": "@font-face {\n font-family: 'EntezareZohoor2';\n src: url('fonts/EntezareZohoor2.eot'), url('fonts/EntezareZohoor2.ttf') format('truetype'), url('fonts/EntezareZohoor2.svg') format('svg');\n font-weight: normal;\n font-style: normal;\n}\n\n.print-format {\n\tpadding: 0px;\n}\n@media screen {\n\t.print-format {\n\t\tpadding: 0in;\n\t}\n}\n#payer_check_window_block {\n top: 0.7cm; \n left: 0.7cm;\n height: 2.2cm;\n width: 8.8cm;\n position: absolute;\n \n}\n\n#payer_name_block{\n top: 0.1cm; \n left: 1cm;\n position: absolute;\n}\n\n#payee_address_window_block {\n top:4.9cm;\n left: 1.9cm;\n position: absolute; \n width: 8.8cm; \n height:2.2cm;\n}\n\n#address_block{\n top: 0.2cm; \n left: 0.2cm;\n position: relative;\n}\n\n\n\n#memo_block {\n top:7.1cm;\n left: 2cm;\n position: absolute; \n width: 6cm;\n}\n\n\n#check_section_1 {\n font-size: 15px;\n width:20.0cm;\n height:8.9cm;\n}\n\n#check_section_2 {\n height:8.9cm;\n}\n\n#check_section_3 {\n height:8.9cm;\n}\n\n\n#payer_name_block {\ntext-align: center; \n\n}\n\n#payer_name_block {\nwidth:4cm;\ntext-align: center; \nposition: absolute;\n}\n\n#bank_info_block {\nfont-size: 10px;\nwidth:2.5cm;\nheight:1.8cm;\ntext-align: center; \nposition: absolute;\n}\n\n\n#payment_in_words_block {\n font-size: 13px;\n}\n\n#memo_block {\nfont-size: 10px;\n}\n\n#signature_block {\ncolor: blue;\nfont-family: cursive;\n\n}\n#payment_amount_block{\n top:3.3cm;\n left: 17.6cm;\n\tposition: absolute; \n\tmin-width: 4cm;\n\n}\n\n.payment_reference_block {\npadding-left:1cm; \npadding-right:1cm; \n}\n\n.payment_name_cell {\ntext-align: right; \n}\n\n#payment_amount_number_block {\n top:3.3cm;\n left: 17.6cm;\n\tposition: absolute; \n\tmin-width: 4cm;\n}\n\n\n.right_stamp {\n top:2.8cm;\n left: 16.6cm;\n width: 3cm;\n\theight: 1.5cm;\n\tfont-size: 40px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n.sig_stamp {\n top:6.3cm;\n left: 13.8cm;\n width: 3cm;\n\theight: 1.5cm;\n\tfont-size: 40px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n.big_stamp {\n top:2.8cm;\n left: 6.1cm;\n width: 7cm;\n\theight: 3cm;\n\tfont-size: 80px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n\n.stamp {\n\tmargin: 0px;\n\toverflow: hidden;\n display: flex;\n justify-content: space-around;\n align-items: center;\n vertical-align: middle;\n text-align: center;\n\n flex-direction: row;\n color: #555;\n\tfont-weight: 700;\n\tborder: 0.25rem solid #555;\n\tdisplay: inline-block;\n\t\n\ttext-transform: uppercase;\n\tborder-radius: 1rem;\n\tfont-family: 'Courier';\n\t-webkit-mask-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/8399/grunge.png');\n -webkit-mask-size: 944px 604px;\n mix-blend-mode: multiply;\n}\n\n\n.is-nope {\n color: #ff5858;\n border: 0.5rem double #D23;\n \t-webkit-mask-position: 2rem 3rem;\n \t\n \n}\n\n.is-draft {\n\tcolor: #ff5858;\n\tborder: 1rem double #ff5858;\n font-size: 6rem;\n font-family: \"Open sans\", Helvetica, Arial, sans-serif;\n border-radius: 0;\n padding: 0.5rem;\n} ", + "css": "@font-face {\n font-family: 'EntezareZohoor2';\n src: url('fonts/EntezareZohoor2.eot'), url('fonts/EntezareZohoor2.ttf') format('truetype'), url('fonts/EntezareZohoor2.svg') format('svg');\n font-weight: normal;\n font-style: normal;\n}\n\n.print-format {\n\tpadding: 0px;\n}\n\n@media screen {\n\t.print-format {\n\t\tpadding: 0in;\n\t}\n}\n\n#payer_check_window_block {\n top: 0.7cm; \n left: 0.7cm;\n height: 2.2cm;\n width: 8.8cm;\n position: absolute;\n \n}\n\n#payer_name_block{\n top: 0.1cm; \n left: 1cm;\n position: absolute;\n}\n\n#payee_address_window_block {\n top:4.9cm;\n left: 1.9cm;\n position: absolute; \n width: 8.8cm; \n height:2.2cm;\n}\n\n#address_block{\n top: 0.2cm; \n left: 0.2cm;\n position: relative;\n}\n\n\n\n#memo_block {\n top:7.1cm;\n left: 2cm;\n position: absolute; \n width: 6cm;\n}\n\n\n#check_section_1 {\n font-size: 15px;\n width:20.0cm;\n height:8.9cm;\n}\n\n#check_section_2 {\n height:8.9cm;\n}\n\n#check_section_3 {\n height:8.9cm;\n}\n\n\n#payer_name_block {\ntext-align: center; \n\n}\n\n#payer_name_block {\nwidth:4cm;\ntext-align: center; \nposition: absolute;\n}\n\n#bank_info_block {\nfont-size: 10px;\nwidth:2.5cm;\nheight:1.8cm;\ntext-align: center; \nposition: absolute;\n}\n\n\n#payment_in_words_block {\n font-size: 13px;\n}\n\n#memo_block {\nfont-size: 10px;\n}\n\n#signature_block {\ncolor: blue;\nfont-family: cursive;\n\n}\n#payment_amount_block{\n top:3.3cm;\n left: 17.6cm;\n\tposition: absolute; \n\tmin-width: 4cm;\n\n}\n\n.payment_reference_block {\npadding-left:1cm; \npadding-right:1cm; \n}\n\n.payment_name_cell {\ntext-align: right; \n}\n\n#payment_amount_number_block {\n top:3.3cm;\n left: 16cm;\n\tposition: absolute; \n\tmin-width: 4cm;\n}\n\n\n.right_stamp {\n top:2.8cm;\n left: 16.6cm;\n width: 3cm;\n\theight: 1.5cm;\n\tfont-size: 40px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n.sig_stamp {\n top:6.3cm;\n left: 13.8cm;\n width: 3cm;\n\theight: 1.5cm;\n\tfont-size: 40px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n.big_stamp {\n top:2.8cm;\n left: 6.1cm;\n width: 7cm;\n\theight: 3cm;\n\tfont-size: 80px;\n \tfort-weight: bold;\n position: absolute; \n}\n\n\n.stamp {\n\tmargin: 0px;\n\toverflow: hidden;\n display: flex;\n justify-content: space-around;\n align-items: center;\n vertical-align: middle;\n text-align: center;\n\n flex-direction: row;\n color: #555;\n\tfont-weight: 700;\n\tborder: 0.25rem solid #555;\n\tdisplay: inline-block;\n\t\n\ttext-transform: uppercase;\n\tborder-radius: 1rem;\n\tfont-family: 'Courier';\n\t-webkit-mask-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/8399/grunge.png');\n -webkit-mask-size: 944px 604px;\n mix-blend-mode: multiply;\n}\n\n\n.is-nope {\n color: #ff5858;\n border: 0.5rem double #D23;\n \t-webkit-mask-position: 2rem 3rem;\n \t\n \n}\n\n.is-draft {\n\tcolor: #ff5858;\n\tborder: 1rem double #ff5858;\n font-size: 6rem;\n font-family: \"Open sans\", Helvetica, Arial, sans-serif;\n border-radius: 0;\n padding: 0.5rem;\n} ", "custom_format": 1, "default_print_language": "en", "disabled": 0, @@ -11,14 +11,14 @@ "doctype": "Print Format", "font": "Default", "font_size": 0, - "html": "
\r\n
\r\n {% if doc.docstatus == 0 %}\r\n VOID DRAFT\r\n VOID DRAFT\r\n {% elif doc.docstatus == 2 or overwrite_void %}\r\n VOID\r\n VOID\r\n VOID\r\n {% endif %}\r\n
\r\n \r\n {{ doc.company }}\r\n \r\n
\r\n \r\n {{ doc.get_formatted('posting_date') }} \r\n \r\n \r\n {{ doc.party_name }}\r\n \r\n \r\n
\r\n {{ doc.party_name }}
\r\n {% set address = get_default_address(doc.party_type, doc.party) %}\r\n {% if address %}\r\n {{ frappe.get_doc('Address', address).get_display() }}\r\n {% endif %}\r\n
\r\n
\r\n \r\n {% set money_number = doc.get_formatted('paid_amount')[1:].strip() %} \r\n {% if money_number|length < 18 %}\r\n {% set money_number = ( money_number + '***************************')[:18] %}\r\n {% endif %}\r\n {{ money_number }}\r\n \r\n \r\n {% set money_in_words = frappe.utils.money_in_words(doc.paid_amount)[:-5] %}\r\n {% if money_in_words|length < 90 %}\r\n {% set money_in_words = (money_in_words + '************************************************************************')[:100] %}\r\n {% endif %}\r\n {{ money_in_words }}\r\n \r\n \r\n {{ doc.check_memo or '' }} {% if test_lines %} MEMO {% endif %}\r\n \r\n \r\n SIGNATURE\r\n \r\n \r\n CHECK#\r\n \r\n \r\n ACCOUNT NUMBER {{ doc.account_no or '' }}\r\n \r\n \r\n ROUTING_NUMBER\r\n \r\n
\r\n
\r\n{% set number_of_invoice = frappe.db.get_value(\"Check Run Settings\", {'bank_account':doc.bank_account,'company':doc.company}, ['number_of_invoices_per_voucher']) %}\r\n\r\n{% for i in range(0,2) %}\r\n{% if i == 0 %}\r\n
\r\n{% endif %}\r\n{% if i == 1 and not doc.references|length >= number_of_invoice %}\r\n
\r\n {% endif %}\r\n{% if i == 1 and doc.references|length >= number_of_invoice %}\r\n
\r\n {% endif %}\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n {% if doc.references|length >= number_of_invoice %}\r\n \r\n \r\n \r\n {% else %}\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {% for reference in doc.references %}\r\n \r\n {% if reference.reference_doctype == 'Purchase Invoice' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Sales Invoice' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Expense Claim' %}\r\n \r\n \r\n {% elif reference.reference_doctype == 'Journal Entry' %}\r\n \r\n \r\n {% endif %}\r\n \r\n \r\n \r\n {% endfor %}\r\n {% endif %}\r\n
{{doc.party_name}} {{ frappe.utils.formatdate(doc.reference_date) or '' }} {{doc.get_formatted(\"base_paid_amount\")}}
\r\n {% for reference in doc.references %}\r\n {% if reference.reference_doctype == 'Purchase Invoice' %}\r\n {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_no\") or \"\"}}, \r\n {% elif reference.reference_doctype == 'Sales Invoice' %}\r\n {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_no\") or \"\" }},\r\n {% elif reference.reference_doctype == 'Expense Claim' %}\r\n {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }}, \r\n {% elif reference.reference_doctype == 'Journal Entry' %}\r\n {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }},\r\n {% endif %}\r\n {% endfor %}\r\n
Date Reference Amount Payment
{{i }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"bill_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_date\")) or \"\"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"po_no\") or \"\" }}{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"posting_date\")) or \" \"}} {{ frappe.db.get_value(reference.reference_doctype, reference.reference_name, \"name\") or \" \" }} {{ frappe.utils.fmt_money(reference.get_formatted('total_amount'), 2, 'USD')}} {{ frappe.utils.fmt_money(reference.get_formatted('allocated_amount'), 2, doc.paid_from_account_currency) }}
\r\n
\r\n
\r\n{% endfor %}", + "html": "{% set checkrun = frappe.get_doc(\"Check Run\", doc.check_run) %}\n{% set checkrunsetting = frappe.db.get_list(\"Check Run Settings\", {\"bank_account\": checkrun.bank_account,\n\"pay_to_account\": checkrun.pay_to_account , \"company\": checkrun.company}) %}\n{% set crs_doc = frappe.get_doc(\"Check Run Settings\", checkrunsetting[0]) %}\n{% set printable_mop = frappe.db.get_list(\"Check Run Printable MOP\", {\"parent\": crs_doc.name}, \"mode_of_payment\",\npluck=\"mode_of_payment\") %}\n{% set pdf_page_size = frappe.db.get_value(\"Print Settings\", \"Print Settings\", \"pdf_page_size\") %}\n\n{% if doc.mode_of_payment in printable_mop %}\n\n\t.print-format {\n\t\tbackground-image: url({{crs_doc.background_image or ''}});\n\t}\n\n\n
\n\t
\n\t\t
\n\t\t\t{% if doc.docstatus == 0 %}\n\t\t\tVOID DRAFT\n\t\t\tVOID DRAFT\n\t\t\t{% elif doc.docstatus == 2 or overwrite_void %}\n\t\t\tVOID\n\t\t\tVOID\n\t\t\tVOID\n\t\t\t{% endif %}\n\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t{{ doc.company }}\n\t\t\t\t\n\t\t\t
\n\t\t\t\n\t\t\t\t{{ doc.get_formatted('posting_date') }}\n\t\t\t\n\n\t\t\t\n\t\t\t\t{{ doc.party_name }}\n\t\t\t\n\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{{ doc.party_name }}
\n\t\t\t\t\t{% set address = get_default_address(doc.party_type, doc.party) %}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t{{ frappe.get_doc('Address', address).get_display() }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t\n\t\t\t\t{% set money_number = doc.get_formatted('paid_amount')[1:].strip() %}\n\n\t\t\t\t{% if money_number|length < 18 %} {% set money_number=( money_number + '***************************'\n\t\t\t\t\t)[:18] %} {% endif %} {{ money_number }} \n\n\t\t\t\t\t\n\t\t\t\t\t\t{% set money_in_words = frappe.utils.money_in_words(doc.paid_amount)[:-5] %}\n\t\t\t\t\t\t{% if money_in_words|length < 90 %} {% set money_in_words=(money_in_words\n\t\t\t\t\t\t\t+ '************************************************************************' )[:100] %} {%\n\t\t\t\t\t\t\tendif %} {{ money_in_words }} \n\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{{ doc.check_memo or '' }} {% if test_lines %} MEMO {% endif %}\n\t\t\t\t\t\t\t\n\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tSIGNATURE\n\t\t\t\t\t\t\t\n\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tCHECK#\n\t\t\t\t\t\t\t\n\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tACCOUNT NUMBER {{ doc.account_no or '' }}\n\t\t\t\t\t\t\t\n\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tROUTING_NUMBER\n\t\t\t\t\t\t\t\n\t\t
\n\t
\n\n\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t{% if crs_doc.secondary_print_format or (not crs_doc.secondary_print_format and doc.references|length > 6) %}\n\t\t\t\t\n\t\t\t\t
{{doc.party_name}} {{ frappe.utils.formatdate(doc.reference_date) or '' }} {{doc.get_formatted(\"base_paid_amount\")}}
\n\t\t\t\t
\n\t\t\t\t{% for reference in doc.references %}\n\t\t\t\t\t{{ reference.reference_name }}\n\t\t\t\t\t{{ \", \" if not loop.last else \"\" }}\n\t\t\t\t{% endfor %}\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t Date \n\t\t\t\t\t Reference \n\t\t\t\t\t Amount \n\t\t\t\t\t Payment \n\t\t\t\t\n\t\t\t\t{% for reference in doc.references %}\n\t\t\t\t\n\t\t\t\t\t{% if reference.reference_doctype == 'Purchase Invoice' %}\n\t\t\t\t\t{{ frappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"bill_date\")) or \"\"}}\n\t\t\t\t\t {{ frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"bill_no\") or \"\" }}\n\t\t\t\t\t{% elif reference.reference_doctype == 'Sales Invoice' %}\n\t\t\t\t\t{{\n\t\t\t\t\t\tfrappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"po_date\")) or \"\"}}\n\t\t\t\t\t {{ frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"po_no\") or \"\" }}\n\t\t\t\t\t{% elif reference.reference_doctype == 'Expense Claim' %}\n\t\t\t\t\t{{\n\t\t\t\t\t\tfrappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"posting_date\")) or \" \"}}\n\t\t\t\t\t {{ frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"name\") or \" \" }}\n\t\t\t\t\t{% elif reference.reference_doctype == 'Journal Entry' %}\n\t\t\t\t\t {{\n\t\t\t\t\t\tfrappe.utils.formatdate(frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"posting_date\")) or \" \"}}\n\t\t\t\t\t {{ frappe.db.get_value(reference.reference_doctype,\n\t\t\t\t\t\treference.reference_name, \"name\") or \" \" }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t {{ frappe.utils.fmt_money(reference.get_formatted('total_amount'),\n\t\t\t\t\t\t2, 'USD')}}\n\t\t\t\t\t {{\n\t\t\t\t\t\tfrappe.utils.fmt_money(reference.get_formatted('allocated_amount'), 2, 'USD')}}\n\t\t\t\t\n\t\t\t\t{% endfor %}\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t
\n\t
\n
\n
\n{% endif %}", "idx": 0, "line_breaks": 0, "margin_bottom": 0.0, "margin_left": 0.0, "margin_right": 0.0, "margin_top": 0.0, - "modified": "2024-03-28 01:37:54.409479", + "modified": "2025-06-11 06:04:46.728006", "modified_by": "Administrator", "module": "Check Run", "name": "Example Voucher", diff --git a/check_run/hooks.py b/check_run/hooks.py index aaafd6c1..7e2c4e0a 100644 --- a/check_run/hooks.py +++ b/check_run/hooks.py @@ -19,7 +19,11 @@ # ------------------ # include js, css files in header of desk.html -app_include_css = ["/assets/check_run/js/style.css", "/assets/check_run/css/file_preview.css"] +app_include_css = [ + "/assets/check_run/js/style.css", + "/assets/check_run/css/file_preview.css", + "/assets/check_run/css/print.css", +] app_include_js = [ "check_run.bundle.js", # "/assets/check_run/dist/js/check_run.js", diff --git a/check_run/overrides/payment_entry.py b/check_run/overrides/payment_entry.py index 1ebd87f3..fe55522d 100644 --- a/check_run/overrides/payment_entry.py +++ b/check_run/overrides/payment_entry.py @@ -1,357 +1,357 @@ -# Copyright (c) 2023, AgriTheory and contributors -# For license information, please see license.txt - -import json -import base64 - -import frappe -from frappe.utils import get_link_to_form, flt -from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map -from frappe.utils.data import getdate -from frappe.core.doctype.file.utils import get_local_image -from erpnext.accounts.doctype.payment_entry.payment_entry import ( - PaymentEntry, - get_outstanding_reference_documents, -) -from frappe import _, safe_decode - - -class CheckRunPaymentEntry(PaymentEntry): - def make_gl_entries(self, cancel=0, adv_adj=0): - """ - HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a - REPO: https://github.com/frappe/erpnext/ - PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py - METHOD: make_gl_entries - """ - if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"): - self.setup_party_account_field() - - if self.status == "Voided": - original_posting_date = self.posting_date - self.voided_date = self.posting_date = getdate() - - gl_entries = [] - self.add_party_gl_entries(gl_entries) - self.add_bank_gl_entries(gl_entries) - self.add_deductions_gl_entries(gl_entries) - self.add_tax_gl_entries(gl_entries) - - gl_entries = process_gl_map(gl_entries) - make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) - - if self.status == "Voided": - self.posting_date = original_posting_date - - def set_status(self): - """ - HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a - REPO: https://github.com/frappe/erpnext/ - PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py - METHOD: set_status - """ - if self.status == "Voided": - pass - elif self.docstatus == 2: - self.status = "Cancelled" - elif self.docstatus == 1: - self.status = "Submitted" - else: - self.status = "Draft" - - self.db_set("status", self.status, update_modified=True) - - # Bug Fix - def get_valid_reference_doctypes(self): - """ - HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a - REPO: https://github.com/frappe/erpnext/ - PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py - METHOD: get_valid_reference_doctypes - """ - if self.party_type == "Customer": - return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") - elif self.party_type == "Supplier": - return ("Purchase Order", "Purchase Invoice", "Journal Entry") - elif self.party_type == "Shareholder": - return ("Journal Entry",) - elif self.party_type == "Employee": - return ("Journal Entry", "Expense Claim") # Expense Claim - - """ - Because Check Run processes multiple payment entries in a background queue, errors generally do not include - enough data to identify the problem since there were written and remain appropriate for the context of an individual - Payment Entry. This code is copied from: - - https://github.com/frappe/erpnext/blob/version-14/erpnext/accounts/doctype/payment_entry/payment_entry.py#L164 - - https://github.com/frappe/erpnext/blob/version-14/erpnext/accounts/doctype/payment_entry/payment_entry.py#L194 - """ - - def validate_allocated_amount(self): - """ - HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a - REPO: https://github.com/frappe/erpnext/ - PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py - METHOD: validate_allocated_amount - """ - if self.payment_type == "Internal Transfer": - return - - if self.party_type in ("Customer", "Supplier"): - self.validate_allocated_amount_with_latest_data() - else: - fail_message = _( - "{0} Row {1} / {2}: Allocated Amount of {3} cannot be greater than outstanding amount of {4}." - ) - for d in self.get("references"): - if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw( - fail_message.format( - self.party_name, - d.idx, - get_link_to_form(d.reference_doctype, d.reference_name), - d.allocated_amount, - d.outstanding_amount, - ) - ) - - # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw( - fail_message.format( - self.party_name, - d.idx, - get_link_to_form(d.reference_doctype, d.reference_name), - d.allocated_amount, - d.outstanding_amount, - ) - ) - - def validate_allocated_amount_with_latest_data(self): - """ - HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a - REPO: https://github.com/frappe/erpnext/ - PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py - METHOD: validate_allocated_amount_with_latest_data - """ - if self.references: - unique_vouchers = {(x.reference_doctype, x.reference_name) for x in self.references} - vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in unique_vouchers] - latest_references = get_outstanding_reference_documents( - { - "posting_date": self.posting_date, - "company": self.company, - "party_type": self.party_type, - "payment_type": self.payment_type, - "party": self.party, - "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, - "get_outstanding_invoices": True, - "get_orders_to_be_billed": True, - "vouchers": vouchers, - } - ) - - # Group latest_references by (voucher_type, voucher_no) - latest_lookup = {} - for d in latest_references: - d = frappe._dict(d) - latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d - - for idx, d in enumerate(self.get("references"), start=1): - latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() - - # If term based allocation is enabled, throw - if ( - d.payment_term is None or d.payment_term == "" - ) and self.term_based_allocation_enabled_for_reference( - d.reference_doctype, d.reference_name - ): - frappe.throw( - _( - "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" - ).format(frappe.bold(d.reference_name), frappe.bold(idx)) - ) - - # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key - latest = latest.get(d.payment_term) or latest.get(None) - # The reference has already been fully paid - if not latest: - frappe.throw( - _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) - ) - # The reference has already been partly paid - elif ( - latest.outstanding_amount < latest.invoice_amount - and flt(d.outstanding_amount, d.precision("outstanding_amount")) - != flt(latest.outstanding_amount, d.precision("outstanding_amount")) - and d.payment_term == "" - ): - frappe.throw( - _( - "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." - ).format(_(d.reference_doctype), d.reference_name) - ) - - fail_message = _( - "Row #{1} {0} / {2}: Allocated Amount of {3} cannot be greater than outstanding amount of {4}." - ) - - if ( - d.payment_term - and ( - (flt(d.allocated_amount)) > 0 - and latest.payment_term_outstanding - and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding)) - ) - and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name) - ): - frappe.throw( - _( - "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" - ).format( - d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term - ) - ) - - if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): - frappe.throw( - fail_message.format( - self.party_name, - d.idx, - get_link_to_form(d.reference_doctype, d.reference_name), - d.allocated_amount, - d.outstanding_amount, - ) - ) - - # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): - frappe.throw( - fail_message.format( - self.party_name, - d.idx, - get_link_to_form(d.reference_doctype, d.reference_name), - d.allocated_amount, - d.outstanding_amount, - ) - ) - - -@frappe.whitelist() -def update_check_number(doc: PaymentEntry, method: str | None = None) -> None: - mode_of_payment_type = frappe.db.get_value("Mode of Payment", doc.mode_of_payment, "type") - if doc.bank_account and mode_of_payment_type == "Bank" and str(doc.reference_no).isdigit(): - frappe.db.set_value("Bank Account", doc.bank_account, "check_number", doc.reference_no) - - -@frappe.whitelist() -def validate_duplicate_check_number(doc: PaymentEntry, method: str | None = None) -> None: - check_run_settings = frappe.db.exists( - "Check Run Settings", {"bank_account": doc.bank_account, "pay_to_account": doc.paid_to} - ) - if not check_run_settings or not frappe.db.get_value( - "Check Run Settings", check_run_settings, "validate_unique_check_number" - ): - return - - mode_of_payment_type = frappe.db.get_value("Mode of Payment", doc.mode_of_payment, "type") - if mode_of_payment_type != "Bank" or not str(doc.reference_no).isdigit(): - return - - pe_names = frappe.get_all( - "Payment Entry", - { - "payment_type": "Pay", - "name": ["!=", doc.name], - "docstatus": ["!=", 2], - "reference_no": doc.reference_no, - }, - ) - if not pe_names: - return - - error_message = "
  • ".join([get_link_to_form("Payment Entry", p.name) for p in pe_names]) - frappe.throw( - msg=frappe._( - f"Check Number {doc.reference_no} is already set in:

    • {error_message}
    " - ), - title="Check Number already exists", - ) - - -@frappe.whitelist() -def update_outstanding_amount(doc: PaymentEntry, method: str | None = None): - paid_amount = doc.paid_amount if method == "on_submit" else 0.0 - for r in doc.get("references"): - if r.reference_doctype != "Purchase Invoice": - continue - payment_schedules = frappe.get_all( - "Payment Schedule", - {"parent": r.reference_name}, - ["name", "outstanding", "payment_term", "payment_amount"], - order_by="due_date ASC", - ) - if not payment_schedules: - continue - - payment_schedule = frappe.get_doc("Payment Schedule", payment_schedules[0]["name"]) - precision = payment_schedule.precision("outstanding") - payment_schedules = payment_schedules if method == "on_submit" else reversed(payment_schedules) - - for term in payment_schedules: - if r.payment_term and term.payment_term != r.payment_term: - continue - - if method == "on_submit": - if term.outstanding > 0.0 and paid_amount > 0.0: - if term.outstanding > paid_amount: - frappe.db.set_value( - "Payment Schedule", - term.name, - "outstanding", - flt(term.outstanding - paid_amount, precision), - ) - break - else: - paid_amount = flt(paid_amount - term.outstanding, precision) - frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) - if paid_amount <= 0.0: - break - - if method == "on_cancel": - if term.outstanding != term.payment_amount: - # if this payment term had previously been allocated against - paid_amount += flt(paid_amount + (term.payment_amount - term.outstanding), precision) - reverse = ( - flt(paid_amount + term.outstanding, precision) - if paid_amount < term.payment_amount - else term.payment_amount - ) - frappe.db.set_value("Payment Schedule", term.name, "outstanding", reverse) - if paid_amount >= doc.paid_amount: - break - - -@frappe.whitelist() -def remove_from_check_run(check_run, payment_entry): - cr = frappe.get_doc("Check Run", check_run) - transactions = json.loads(cr.transactions) if cr.transactions else [] - new_transactions = [] - for transaction in transactions: - if transaction.get("payment_entry") != payment_entry: - new_transactions.append(transaction) - cr.db_set("transactions", json.dumps(new_transactions)) - frappe.db.set_value("Payment Entry", payment_entry, "check_run", "") - frappe.msgprint(_("Removed from Check Run"), alert=True) - return "removed" - - -@frappe.whitelist() -def get_image_base64_data(file_url): - file_doc = frappe.get_doc("File", {"file_url": file_url}) - if not file_doc.has_permission(ptype="read"): - return "" - image, unused_filename, extn = get_local_image(file_url) - file_content = file_doc.get_content() - return f"data:image/{extn};base64,{safe_decode(base64.b64encode(file_content).decode('utf-8'))}" +# Copyright (c) 2023, AgriTheory and contributors +# For license information, please see license.txt + +import json +import base64 + +import frappe +from frappe.utils import get_link_to_form, flt +from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map +from frappe.utils.data import getdate +from frappe.core.doctype.file.utils import get_local_image +from erpnext.accounts.doctype.payment_entry.payment_entry import ( + PaymentEntry, + get_outstanding_reference_documents, +) +from frappe import _, safe_decode + + +class CheckRunPaymentEntry(PaymentEntry): + def make_gl_entries(self, cancel=0, adv_adj=0): + """ + HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py + METHOD: make_gl_entries + """ + if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"): + self.setup_party_account_field() + + if self.status == "Voided": + original_posting_date = self.posting_date + self.voided_date = self.posting_date = getdate() + + gl_entries = [] + self.add_party_gl_entries(gl_entries) + self.add_bank_gl_entries(gl_entries) + self.add_deductions_gl_entries(gl_entries) + self.add_tax_gl_entries(gl_entries) + + gl_entries = process_gl_map(gl_entries) + make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) + + if self.status == "Voided": + self.posting_date = original_posting_date + + def set_status(self): + """ + HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py + METHOD: set_status + """ + if self.status == "Voided": + pass + elif self.docstatus == 2: + self.status = "Cancelled" + elif self.docstatus == 1: + self.status = "Submitted" + else: + self.status = "Draft" + + self.db_set("status", self.status, update_modified=True) + + # Bug Fix + def get_valid_reference_doctypes(self): + """ + HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py + METHOD: get_valid_reference_doctypes + """ + if self.party_type == "Customer": + return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning") + elif self.party_type == "Supplier": + return ("Purchase Order", "Purchase Invoice", "Journal Entry") + elif self.party_type == "Shareholder": + return ("Journal Entry",) + elif self.party_type == "Employee": + return ("Journal Entry", "Expense Claim") # Expense Claim + + """ + Because Check Run processes multiple payment entries in a background queue, errors generally do not include + enough data to identify the problem since there were written and remain appropriate for the context of an individual + Payment Entry. This code is copied from: + + https://github.com/frappe/erpnext/blob/version-14/erpnext/accounts/doctype/payment_entry/payment_entry.py#L164 + + https://github.com/frappe/erpnext/blob/version-14/erpnext/accounts/doctype/payment_entry/payment_entry.py#L194 + """ + + def validate_allocated_amount(self): + """ + HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py + METHOD: validate_allocated_amount + """ + if self.payment_type == "Internal Transfer": + return + + if self.party_type in ("Customer", "Supplier"): + self.validate_allocated_amount_with_latest_data() + else: + fail_message = _( + "{0} Row {1} / {2}: Allocated Amount of {3} cannot be greater than outstanding amount of {4}." + ) + for d in self.get("references"): + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): + frappe.throw( + fail_message.format( + self.party_name, + d.idx, + get_link_to_form(d.reference_doctype, d.reference_name), + d.allocated_amount, + d.outstanding_amount, + ) + ) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw( + fail_message.format( + self.party_name, + d.idx, + get_link_to_form(d.reference_doctype, d.reference_name), + d.allocated_amount, + d.outstanding_amount, + ) + ) + + def validate_allocated_amount_with_latest_data(self): + """ + HASH: 7e847f27ddf6b2c198b535a81cb51ad909ddcd5a + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/payment_entry/payment_entry.py + METHOD: validate_allocated_amount_with_latest_data + """ + if self.references: + unique_vouchers = {(x.reference_doctype, x.reference_name) for x in self.references} + vouchers = [frappe._dict({"voucher_type": x[0], "voucher_no": x[1]}) for x in unique_vouchers] + latest_references = get_outstanding_reference_documents( + { + "posting_date": self.posting_date, + "company": self.company, + "party_type": self.party_type, + "payment_type": self.payment_type, + "party": self.party, + "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + "get_outstanding_invoices": True, + "get_orders_to_be_billed": True, + "vouchers": vouchers, + } + ) + + # Group latest_references by (voucher_type, voucher_no) + latest_lookup = {} + for d in latest_references: + d = frappe._dict(d) + latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d + + for idx, d in enumerate(self.get("references"), start=1): + latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict() + + # If term based allocation is enabled, throw + if ( + d.payment_term is None or d.payment_term == "" + ) and self.term_based_allocation_enabled_for_reference( + d.reference_doctype, d.reference_name + ): + frappe.throw( + _( + "{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section" + ).format(frappe.bold(d.reference_name), frappe.bold(idx)) + ) + + # if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key + latest = latest.get(d.payment_term) or latest.get(None) + # The reference has already been fully paid + if not latest: + frappe.throw( + _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) + ) + # The reference has already been partly paid + elif ( + latest.outstanding_amount < latest.invoice_amount + and flt(d.outstanding_amount, d.precision("outstanding_amount")) + != flt(latest.outstanding_amount, d.precision("outstanding_amount")) + and d.payment_term == "" + ): + frappe.throw( + _( + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." + ).format(_(d.reference_doctype), d.reference_name) + ) + + fail_message = _( + "Row #{1} {0} / {2}: Allocated Amount of {3} cannot be greater than outstanding amount of {4}." + ) + + if ( + d.payment_term + and ( + (flt(d.allocated_amount)) > 0 + and latest.payment_term_outstanding + and (flt(d.allocated_amount) > flt(latest.payment_term_outstanding)) + ) + and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name) + ): + frappe.throw( + _( + "Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}" + ).format( + d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term + ) + ) + + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): + frappe.throw( + fail_message.format( + self.party_name, + d.idx, + get_link_to_form(d.reference_doctype, d.reference_name), + d.allocated_amount, + d.outstanding_amount, + ) + ) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): + frappe.throw( + fail_message.format( + self.party_name, + d.idx, + get_link_to_form(d.reference_doctype, d.reference_name), + d.allocated_amount, + d.outstanding_amount, + ) + ) + + +@frappe.whitelist() +def update_check_number(doc: PaymentEntry, method: str | None = None) -> None: + mode_of_payment_type = frappe.db.get_value("Mode of Payment", doc.mode_of_payment, "type") + if doc.bank_account and mode_of_payment_type == "Bank" and str(doc.reference_no).isdigit(): + frappe.db.set_value("Bank Account", doc.bank_account, "check_number", doc.reference_no) + + +@frappe.whitelist() +def validate_duplicate_check_number(doc: PaymentEntry, method: str | None = None) -> None: + check_run_settings = frappe.db.exists( + "Check Run Settings", {"bank_account": doc.bank_account, "pay_to_account": doc.paid_to} + ) + if not check_run_settings or not frappe.db.get_value( + "Check Run Settings", check_run_settings, "validate_unique_check_number" + ): + return + + mode_of_payment_type = frappe.db.get_value("Mode of Payment", doc.mode_of_payment, "type") + if mode_of_payment_type != "Bank" or not str(doc.reference_no).isdigit(): + return + + pe_names = frappe.get_all( + "Payment Entry", + { + "payment_type": "Pay", + "name": ["!=", doc.name], + "docstatus": ["!=", 2], + "reference_no": doc.reference_no, + }, + ) + if not pe_names: + return + + error_message = "
  • ".join([get_link_to_form("Payment Entry", p.name) for p in pe_names]) + frappe.throw( + msg=frappe._( + f"Check Number {doc.reference_no} is already set in:

    • {error_message}
    " + ), + title="Check Number already exists", + ) + + +@frappe.whitelist() +def update_outstanding_amount(doc: PaymentEntry, method: str | None = None): + paid_amount = doc.paid_amount if method == "on_submit" else 0.0 + for r in doc.get("references"): + if r.reference_doctype != "Purchase Invoice": + continue + payment_schedules = frappe.get_all( + "Payment Schedule", + {"parent": r.reference_name}, + ["name", "outstanding", "payment_term", "payment_amount"], + order_by="due_date ASC", + ) + if not payment_schedules: + continue + + payment_schedule = frappe.get_doc("Payment Schedule", payment_schedules[0]["name"]) + precision = payment_schedule.precision("outstanding") + payment_schedules = payment_schedules if method == "on_submit" else reversed(payment_schedules) + + for term in payment_schedules: + if r.payment_term and term.payment_term != r.payment_term: + continue + + if method == "on_submit": + if term.outstanding > 0.0 and paid_amount > 0.0: + if term.outstanding > paid_amount: + frappe.db.set_value( + "Payment Schedule", + term.name, + "outstanding", + flt(term.outstanding - paid_amount, precision), + ) + break + else: + paid_amount = flt(paid_amount - term.outstanding, precision) + frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) + if paid_amount <= 0.0: + break + + if method == "on_cancel": + if term.outstanding != term.payment_amount: + # if this payment term had previously been allocated against + paid_amount += flt(paid_amount + (term.payment_amount - term.outstanding), precision) + reverse = ( + flt(paid_amount + term.outstanding, precision) + if paid_amount < term.payment_amount + else term.payment_amount + ) + frappe.db.set_value("Payment Schedule", term.name, "outstanding", reverse) + if paid_amount >= doc.paid_amount: + break + + +@frappe.whitelist() +def remove_from_check_run(check_run, payment_entry): + cr = frappe.get_doc("Check Run", check_run) + transactions = json.loads(cr.transactions) if cr.transactions else [] + new_transactions = [] + for transaction in transactions: + if transaction.get("payment_entry") != payment_entry: + new_transactions.append(transaction) + cr.db_set("transactions", json.dumps(new_transactions)) + frappe.db.set_value("Payment Entry", payment_entry, "check_run", "") + frappe.msgprint(_("Removed from Check Run"), alert=True) + return "removed" + + +@frappe.whitelist() +def get_image_base64_data(file_url): + file_doc = frappe.get_doc("File", {"file_url": file_url}) + if not file_doc.has_permission(ptype="read"): + return "" + image, unused_filename, extn = get_local_image(file_url) + file_content = file_doc.get_content() + return f"data:image/{extn};base64,{safe_decode(base64.b64encode(file_content).decode('utf-8'))}" diff --git a/check_run/patches.txt b/check_run/patches.txt index d2e8badd..2628fb7d 100644 --- a/check_run/patches.txt +++ b/check_run/patches.txt @@ -1,2 +1,3 @@ check_run.patches.patch_payment_schedule # 12/19/23 -check_run.patches.patch_voided_check_workflow # 01/05/24 \ No newline at end of file +check_run.patches.patch_voided_check_workflow # 01/05/24 +check_run.patches.patch_printable_mop_table # 07/31/24 \ No newline at end of file diff --git a/check_run/patches/patch_printable_mop_table.py b/check_run/patches/patch_printable_mop_table.py new file mode 100644 index 00000000..6bbd85d9 --- /dev/null +++ b/check_run/patches/patch_printable_mop_table.py @@ -0,0 +1,19 @@ +import frappe + + +def execute(): + cr_settings = frappe.get_all("Check Run Settings", pluck="name") + bank_mop = frappe.get_all("Mode of Payment", {"type": "Bank"}, pluck="name") + + if not cr_settings or not bank_mop: + return + + for crs in cr_settings: + crs = frappe.get_doc("Check Run Settings", crs) + crs_mops = frappe.get_all( + "Check Run Printable MOP", {"parent": crs}, "mode_of_payment", pluck="mode_of_payment" + ) + for mop in bank_mop: + if mop not in crs_mops: + crs.append("printable_mop_in_check_run", {"mode_of_payment": mop}) + crs.save() diff --git a/check_run/public/css/print.css b/check_run/public/css/print.css new file mode 100644 index 00000000..facf9650 --- /dev/null +++ b/check_run/public/css/print.css @@ -0,0 +1,6 @@ +/* remove any attached background image from the print */ +@media print { + .print-format { + background-image: none !important; + } +} diff --git a/check_run/tests/setup.py b/check_run/tests/setup.py index 151deae7..867e58e6 100644 --- a/check_run/tests/setup.py +++ b/check_run/tests/setup.py @@ -16,6 +16,9 @@ def before_test(): + # avoid call to _ensure_idle_system in ERPNext's account controller + frappe.flags.in_test = True + frappe.clear_cache() today = frappe.utils.getdate() setup_complete( @@ -46,6 +49,8 @@ def before_test(): frappe.set_value("Website Settings", "Website Settings", "home_page", "login") frappe.db.commit() + frappe.flags.in_test = False + def create_test_data(): today = frappe.utils.getdate() @@ -73,8 +78,8 @@ def create_test_data(): create_employees(settings) create_expense_claim(settings) for month in range(1, 13): - create_payroll_journal_entry(settings) settings.day = settings.day.replace(month=month) + create_payroll_journal_entry(settings) create_manual_payment_entry(settings) @@ -156,7 +161,6 @@ def create_bank_and_bank_account(settings): def setup_accounts(): - frappe.flags.in_test = True frappe.rename_doc( "Account", "1000 - Application of Funds (Assets) - CFC", "1000 - Assets - CFC", force=True ) @@ -318,7 +322,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = "Tireless Equipment Rental, Inc" pi.append( "items", @@ -339,7 +343,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = supplier[0] pi.append( "items", @@ -355,7 +359,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = suppliers[0][0] pi.append( "items", @@ -372,7 +376,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = suppliers[4][0] pi.append( "items", @@ -395,7 +399,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = next_day + pi.bill_date = pi.posting_date = next_day pi.supplier = supplier[0] pi.append( "items", @@ -413,7 +417,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = next_day + pi.bill_date = pi.posting_date = next_day pi.supplier = suppliers[0][0] pi.append( "items", @@ -430,7 +434,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = suppliers[4][0] pi.append( "items", @@ -448,7 +452,7 @@ def create_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = settings.day + pi.bill_date = pi.posting_date = settings.day pi.supplier = suppliers[1][0] pi.append( "items", @@ -725,7 +729,7 @@ def create_extra_invoices(settings): pi = frappe.new_doc("Purchase Invoice") pi.company = settings.company pi.set_posting_time = 1 - pi.posting_date = _day + pi.bill_date = pi.posting_date = _day pi.supplier = supplier[0] pi.append( "items", diff --git a/check_run/www/__init__.py b/check_run/www/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/check_run/www/print_check_run.html b/check_run/www/print_check_run.html new file mode 100644 index 00000000..6c57940d --- /dev/null +++ b/check_run/www/print_check_run.html @@ -0,0 +1,37 @@ + + + + + + {{ title }} + + {{ include_style('print.bundle.css') }} {% if print_style %} + + {% endif %} + + + + + + + {%- if comment -%} + + {%- endif -%} + diff --git a/check_run/www/print_check_run.py b/check_run/www/print_check_run.py new file mode 100644 index 00000000..d03f6e8c --- /dev/null +++ b/check_run/www/print_check_run.py @@ -0,0 +1,177 @@ +import json + +import frappe +from frappe.www.printview import ( + get_html_and_style as frappe_get_html_and_style, + get_print_style, + escape_html, +) +from frappe.www.printview import set_link_titles, get_rendered_template, get_print_format_doc + +from check_run.check_run.doctype.check_run.check_run import get_check_run_settings + + +def get_context(context): + """Build context for print""" + if not ((frappe.form_dict.doctype and frappe.form_dict.name) or frappe.form_dict.doc): + return { + "body": f""" +

    Error

    +

    Parameters doctype and name required

    +
    {escape_html(frappe.as_json(frappe.form_dict, indent=2))}
    + """ + } + if frappe.form_dict.doc: + doc = frappe.form_dict.doc + else: + doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name) + + +@frappe.whitelist() +def get_html_and_style( + doc, + name=None, + doctype_to_print=None, + print_format=None, + meta=None, + no_letterhead=None, + letterhead=None, + trigger_print=False, + style=None, + settings=None, + templates=None, +): + if isinstance(doc, str) and isinstance(name, str): + doc = frappe.get_doc(doc, name) + + if isinstance(doc, str): + doc = frappe.get_doc(json.loads(doc)) + if doc.doctype == "Check Run": + return get_check_run_format( + doc, + name, + doctype_to_print, + print_format, + meta, + no_letterhead, + letterhead, + trigger_print, + style, + settings, + templates, + ) + + return frappe_get_html_and_style( + doc, + name, + print_format, + meta, + no_letterhead, + letterhead, + trigger_print, + style, + settings, + templates, + ) + + +def get_check_run_format( + doc, + name=None, + doctype_to_print=None, + print_format=None, + meta=None, + no_letterhead=None, + letterhead=None, + trigger_print=False, + style=None, + settings=None, + templates=None, +): + check_run_settings = get_check_run_settings(doc) + if not settings: + settings = {} + settings["payment_entry_format"] = check_run_settings.print_format + settings["secondary_print_format"] = check_run_settings.secondary_print_format + + if doctype_to_print == "Check Run": + print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta("Check Run")) + set_link_titles(doc) + try: + html = get_rendered_template( + doc, + name=name, + print_format=print_format, + meta=meta, + no_letterhead=no_letterhead, + letterhead=letterhead, + trigger_print=trigger_print, + settings=frappe.parse_json(settings), + ) + except frappe.TemplateNotFoundError: + frappe.clear_last_message() + html = None + + return {"html": html, "style": get_print_style(style=style, print_format=print_format)} + + transaction = json.loads(doc.transactions) if isinstance(doc.transactions, str) else None + html = [] + pe = [] + if transaction and doc.docstatus == 1: + for row in transaction: + pe.append(row.get("payment_entry")) + + payment_entry = list(set(pe)) + for row in payment_entry: + pe_doc = frappe.get_doc("Payment Entry", row) + + settings = json.loads(settings) if isinstance(doc, str) else settings + + print_formats = get_print_format_doc( + print_format, meta=meta or frappe.get_meta("Payment Entry") + ) + set_link_titles(pe_doc) + + try: + html_ = [] + html_code = get_rendered_template( + pe_doc, + name=name, + print_format=print_formats, + meta=meta, + no_letterhead=no_letterhead, + letterhead=letterhead, + trigger_print=trigger_print, + settings=frappe.parse_json(settings), + ) + # html_code += "
    " + html_.append(html_code) + html.append(html_) + except frappe.TemplateNotFoundError: + frappe.clear_last_message() + html = None + + return {"html": html, "style": get_print_style(style=style, print_format=print_formats)} + return { + "html": [ + "

    Please Process Check Run First And Create Payment Entry

    " + ], + "style": None, + } + + +@frappe.whitelist() +def get_formats(doctype): + if doctype == "Check Run": + print_format = frappe.db.get_list("Print Format", filters={"doc_type": doctype}, pluck="name") + + return print_format + elif doctype == "Payment Entry": + print_format = frappe.db.get_list( + "Print Format", filters={"doc_type": "Payment Entry"}, pluck="name" + ) + return print_format + else: + doc = frappe.get_doc("Check Run", doctype) + check_run_settings = get_check_run_settings(doc) + return [check_run_settings.secondary_print_format] diff --git a/docs/version-14/en/assets/Settings_Main.png b/docs/version-14/en/assets/Settings_Main.png index 79db16ac..18a1b562 100644 Binary files a/docs/version-14/en/assets/Settings_Main.png and b/docs/version-14/en/assets/Settings_Main.png differ diff --git a/docs/version-14/en/assets/print_format_example_voucher.png b/docs/version-14/en/assets/print_format_example_voucher.png new file mode 100644 index 00000000..24d0431b Binary files /dev/null and b/docs/version-14/en/assets/print_format_example_voucher.png differ diff --git a/docs/version-14/en/assets/print_format_secondary.png b/docs/version-14/en/assets/print_format_secondary.png new file mode 100644 index 00000000..5819c944 Binary files /dev/null and b/docs/version-14/en/assets/print_format_secondary.png differ diff --git a/docs/version-14/en/assets/settings_print_section.png b/docs/version-14/en/assets/settings_print_section.png new file mode 100644 index 00000000..59884c4c Binary files /dev/null and b/docs/version-14/en/assets/settings_print_section.png differ diff --git a/docs/version-14/en/exampleprint.md b/docs/version-14/en/exampleprint.md index f70f0cf0..1042613d 100644 --- a/docs/version-14/en/exampleprint.md +++ b/docs/version-14/en/exampleprint.md @@ -1,8 +1,16 @@ -# Example Print Format: Voucher Check +# Example Print Formats: Voucher Check and Summary of References -To take advantage of Check Run's check printing functionality, you'll need to set up a print format in ERPNext. Print formats are as unique as the organizations using ERPNext, so a voucher check print format serves as an example template. It can be a starting point from which to customize to suit your needs. +To take advantage of Check Run's check printing functionality, you'll need to set up a print format in ERPNext. An Example Voucher check print format is provided with the application to serve as a starting point. Print formats are as unique as the organizations using ERPNext, so the example template should be customized to suit your needs. It's enabled by default and can be found in the Print Format list. -The example print format included with this application can be found in the Print Format List under Example Voucher. It is disabled by default. +![Screen shot showing the print preview screen of a Check Run that applies the Example Voucher print format. The top half of the format includes the actual check data and the bottom half includes the references associated with the payment.](./assets/print_format_example_voucher.png) + +A second print format called Example Secondary Print Format is also provided under Print Formats. It's not meant to be used to print checks, but will display a summary of the references associated with each Check. + +![Screen shot showing the print preview of a Check Run that applies the Example Secondary Print Format. It shows a table of references and amounts associated with the payment.](./assets/print_format_secondary.png) + +Both example print formats are set to only display transactions where the Mode of Payment is included in the Printable Modes of Payment in Check Run multiselect field found in Check Run Settings. + +One consideration to be aware of if you include references in the print format (like the examples) is that if there are a lot of references associated with a payment, the list may exceed the length of the paper and not print correctly. The value for Number of Invoices per Voucher in Check Run Settings will limit the number of references associated with a payment and can be adjusted as-needed. Additional resources: diff --git a/docs/version-14/en/settings.md b/docs/version-14/en/settings.md index 30da5e92..325691a1 100644 --- a/docs/version-14/en/settings.md +++ b/docs/version-14/en/settings.md @@ -62,6 +62,21 @@ graph TD ![Check Run output table showing a row for eight invoices paid (two for AgriTheory, two for Cooperative Ag Finance, and four for Exceptional Grid). The first two Exceptional Grid invoices have Check Reference Number ACC-PAY-2022-00003 and the next set of two invoices have Check Reference Number ACC-PAY-2022-00004. They were split into different vouchers because the setting limited two invoices per voucher.](./assets/VoucherGroup.png) +The Print Settings section covers the print-related options. + +![Screen shot showing the Print Settings section.](./assets/settings_print_section.png) + +- **Print Preview:** + - The default value is to Automatically Render PDF After Submit, however the Print from Print Preview option allows for viewing and printing using different Print Formats +- **Print Format:** + - Links to a preferred default Print Format +- **Secondary Print Format:** + - Links to a backup Print Format +- **Printable Modes of Payment in Check Run:** + - A collection of the Mode of Payments that are meant to be printed. As detailed in the [configuration page](./configuration.md), this should include the Modes of Payment with the Type "Bank". To see an application of how this is used in a Print Format, see the code in the [Example Voucher](./exampleprint.md) included in Check Run, which only renders payments that have a Mode of Payment included in this list +- **Check formats with background image:** + - Default is unselected, if this is checked, it allows the user to upload a background image to apply in print preview (see the code in the [Example Voucher](./exampleprint.md) for how this is used) + The next section of settings allow for an optional default Mode of Payment for Purchase Invoices, Expense Claims, and Journal Entries. If there isn't a Mode of Payment specified in the Purchase Invoice, Expense Claim, or Journal Entry itself, and there isn't a default set for the party (see the [Configuration page](./configuration.md) for more details), then this field is used to populate the Mode of Payment column in the Check Run. ![Screen shot showing the Default Mode of Payment section in settings.](./assets/Settings_MOP.png)