diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 4dfb5b18c46771327ded4b17925d21951b5ba859..62165a0dceb17fac0fa9f1f46a2b66f3a42f02e3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -948,7 +948,7 @@ frappe.ui.form.on('Sales Invoice', { frm.trigger('reset_posting_time'); }, - redeem_loyalty_points: function(frm) { + redeem_loyalty_points: function(frm) { frm.events.get_loyalty_details(frm); }, @@ -1043,7 +1043,10 @@ frappe.ui.form.on('Sales Invoice', { frappe.db.get_list("Item", {filters: {is_down_payment_item: 1}, order_by: "down_payment_percentage DESC"}) .then(r => { if (!r.exc && r.length) { - frappe.model.set_value(frm.doc.items[0].doctype, frm.doc.items[0].name, "item_code", r[0].name) + frappe.model.set_value(frm.doc.items[0].doctype, frm.doc.items[0].name, "item_code", r[0].name); + const line = locals[frm.doc.items[0].doctype][frm.doc.items[0].name]; + calculate_down_payment(line); + adjust_discount_amount(frm, r[0].down_payment_percentage); } }) } @@ -1082,7 +1085,18 @@ const calculate_down_payment = line => { } } -var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) { +const adjust_discount_amount = (frm, down_payment_percentage) => { + if (frm.doc.is_down_payment_invoice) { + if (frm.doc.additional_discount_percentage === 0.0 && frm.doc.discount_amount > 0.0) { + frm.set_value( + "discount_amount", + (flt(down_payment_percentage) / 100.0) * flt(frm.doc.discount_amount) + ); + } + } +}; + +var set_timesheet_detail_rate = function (cdt, cdn, currency, timelog) { frappe.call({ method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_detail_rate", args: { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index ef20364d7a7b0d1a75fea50b4f9101aec372062e..a17cc5392f0697244826123ec23a637346967dc6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1640,7 +1640,8 @@ "fieldtype": "Check", "label": "Is Down Payment Invoice", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "set_only_once": 1 }, { "default": "0", @@ -1926,7 +1927,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2025-02-06 15:59:54.636202", + "modified": "2025-09-28 11:28:35.262629", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 72bb04feb0a2daad640ee988e051e79e8bc01a95..59ff912e5d780051db8642843b3aa482f499e25e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -298,7 +298,8 @@ class SalesInvoice(SellingController): self.clear_unallocated_advances("Sales Invoice Advance", "advances") self.validate_fixed_asset() self.set_income_account_for_fixed_assets() - self.set_income_account_for_down_payments() + self.set_income_account_for_down_payments() # @dokos + self.set_print_heading() # @dokos self.validate_item_cost_centers() self.check_conversion_rate() self.validate_accounts() @@ -1225,6 +1226,10 @@ class SalesInvoice(SellingController): for d in self.get("items"): d.income_account = debit_to + def set_print_heading(self): + if self.get("is_down_payment_invoice"): + self.select_print_heading = frappe.get_cached_value("Print Heading", _("Down Payment Invoice")) + def check_prev_docstatus(self): for d in self.get("items"): if ( diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1d3aa0b3d38e0abe82736342cf416884528cb9b1..5ddb141a362d17d4507a833c6e026f60295e5dad 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -393,6 +393,7 @@ erpnext.patches.dokos.v4_0.check_enable_pappers erpnext.patches.dokos.v4_0.suspense_accounts_for_vat_on_payments erpnext.patches.dokos.v4_0.move_bundle_price_determination_to_bundle execute:frappe.delete_doc_if_exists("Page", "booking-credits") +erpnext.patches.dokos.v4_0.add_down_payment_invoice_heading # @dokos diff --git a/erpnext/patches/dokos/v4_0/add_down_payment_invoice_heading.py b/erpnext/patches/dokos/v4_0/add_down_payment_invoice_heading.py new file mode 100644 index 0000000000000000000000000000000000000000..09c1127fea502938a2cc1b2e58ecd5a244e166f7 --- /dev/null +++ b/erpnext/patches/dokos/v4_0/add_down_payment_invoice_heading.py @@ -0,0 +1,9 @@ +import frappe +from frappe.printing.doctype.print_heading.print_heading import PrintHeading + + +def execute(): + lang = frappe.get_system_settings("language") + print_heading: PrintHeading = frappe.new_doc("Print Heading") # type: ignore + print_heading.print_heading = frappe._("Down Payment Invoice", lang=lang) + print_heading.insert(ignore_if_duplicate=True) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 0164e8da3c5068f6cf48e7a72ab7915e7dec5a9e..33b35843a4c37d77ce1b05cc7744b31de97559fc 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -99,14 +99,22 @@ erpnext.buying = { return{ query: "erpnext.controllers.queries.item_query", - filters: filters + filters: filters, + }; + } else { + const filters = { + supplier: me.frm.doc.supplier, + is_purchase_item: 1, + has_variants: 0, + }; // @dokos + if (me.frm.doc.is_down_payment_invoice) { + // @dokos + filters["is_down_payment_item"] = 1; } - } - else { - return{ + return { query: "erpnext.controllers.queries.item_query", - filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1, 'has_variants': 0} - } + filters: filters, // @dokos + }; } }); diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index b72083c8606a97f4a07926a53e3d0b9e65602c02..e6446b8f80317d079f7c286d4044d57e3492140f 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -75,9 +75,14 @@ erpnext.sales_common = { if (me.frm.doc.doctype == "Quotation" && me.frm.doc.quotation_to == "Customer") { customer = me.frm.doc.party_name; } + const filters = { is_sales_item: 1, customer: customer, has_variants: 0 }; // @dokos + if (me.frm.doc.is_down_payment_invoice) { + // @dokos + filters["is_down_payment_item"] = 1; + } return { query: "erpnext.controllers.queries.item_query", - filters: { is_sales_item: 1, customer: customer, has_variants: 0 }, + filters: filters, // @dokos }; }); } diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e27507488ce2b7925de1776b99d158d27f78bda1..709360f8ef3a7395421c9a406bd145e0f22cb2c9 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -3,7 +3,7 @@ import json -from typing import Literal +from typing import TYPE_CHECKING, Literal import frappe import frappe.utils @@ -42,6 +42,9 @@ from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} +if TYPE_CHECKING: + from erpnext.stock.doctype.item.item import Item + class WarehouseRequired(frappe.ValidationError): pass @@ -1197,6 +1200,32 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): def postprocess(source, target): + if "is_down_payment_invoice" in (frappe.flags.args or {}): + target.is_down_payment_invoice = True + down_payment_item: Item = frappe.db.get_value( + "Item", + filters={"is_down_payment_item": 1}, + order_by="down_payment_percentage DESC", + as_dict=True, + fieldname=["name", "down_payment_percentage"], + ) # type: ignore + target.items = [] + target.append( + "items", + { + "sales_order": source.name, + "item_code": down_payment_item.name, + "price_list_rate": down_payment_item.down_payment_percentage / 100 * source.total, + "base_rate": down_payment_item.down_payment_percentage / 100 * source.base_total, + "rate": down_payment_item.down_payment_percentage / 100 * source.total, + }, + ) + + if not target.additional_discount_percentage and target.discount_amount: + target.discount_amount = ( + target.discount_amount * flt(down_payment_item.down_payment_percentage) / 100.0 + ) + set_missing_values(source, target) # Get the advance paid Journal Entries in Sales Invoice Advance if target.get("allocate_advances_automatically"): diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 0442cb8cd13af5698fca30f9a8cefef42eaabf03..76ca460c87f79a1c0fbadfd70bec10e88fe6f262 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -288,14 +288,15 @@ def install(country=None): {"doctype": "Party Type", "party_type": "Supplier", "account_type": "Payable"}, {"doctype": "Party Type", "party_type": "Employee", "account_type": "Payable"}, {"doctype": "Party Type", "party_type": "Shareholder", "account_type": "Payable"}, - {"doctype": "Opportunity Type", "name": _("Sales")}, - {"doctype": "Opportunity Type", "name": _("Support")}, - {"doctype": "Opportunity Type", "name": _("Maintenance")}, - {"doctype": "Project Type", "project_type": "Internal"}, - {"doctype": "Project Type", "project_type": "External"}, - {"doctype": "Project Type", "project_type": "Other"}, - {"doctype": "Print Heading", "print_heading": _("Credit Note")}, - {"doctype": "Print Heading", "print_heading": _("Debit Note")}, + {"doctype": "Opportunity Type", "name": frappe._("Sales")}, + {"doctype": "Opportunity Type", "name": frappe._("Support")}, + {"doctype": "Opportunity Type", "name": frappe._("Maintenance")}, + {"doctype": "Project Type", "project_type": _("Internal")}, + {"doctype": "Project Type", "project_type": _("External")}, + {"doctype": "Project Type", "project_type": _("Other")}, + {"doctype": "Print Heading", "print_heading": frappe._("Credit Note")}, + {"doctype": "Print Heading", "print_heading": frappe._("Debit Note")}, + {"doctype": "Print Heading", "print_heading": frappe._("Down Payment Invoice")}, # Share Management {"doctype": "Share Type", "title": _("Equity")}, {"doctype": "Share Type", "title": _("Preference")},