Merge branch 'develop' into version-16-beta

This commit is contained in:
Rohit Waghchaure
2026-01-01 18:07:13 +05:30
187 changed files with 25139 additions and 20055 deletions

View File

@@ -1,7 +1,7 @@
{ {
"chart_name": "Profit and Loss", "chart_name": "Profit and Loss",
"chart_type": "Report", "chart_type": "Report",
"creation": "2020-07-17 11:25:34.448572", "creation": "2025-04-01 20:38:16.986176",
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
@@ -9,7 +9,7 @@
"idx": 0, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2023-07-19 13:08:56.470390", "modified": "2025-12-19 12:37:31.673782",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Profit and Loss", "name": "Profit and Loss",
@@ -17,8 +17,9 @@
"owner": "Administrator", "owner": "Administrator",
"report_name": "Profit and Loss Statement", "report_name": "Profit and Loss Statement",
"roles": [], "roles": [],
"show_values_over_chart": 1,
"timeseries": 0, "timeseries": 0,
"type": "Bar", "type": "Line",
"use_report_chart": 1, "use_report_chart": 1,
"y_axis": [] "y_axis": []
} }

View File

@@ -450,14 +450,12 @@ def process_deferred_accounting(posting_date=None):
for company in companies: for company in companies:
for record_type in ("Income", "Expense"): for record_type in ("Income", "Expense"):
doc = frappe.get_doc( doc = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", company=company.name,
company=company.name, posting_date=posting_date,
posting_date=posting_date, start_date=start_date,
start_date=start_date, end_date=end_date,
end_date=end_date, type=record_type,
type=record_type,
)
) )
doc.insert() doc.insert()

View File

@@ -415,15 +415,13 @@ def create_account(**kwargs):
return account.name return account.name
else: else:
account = frappe.get_doc( account = frappe.get_doc(
dict( doctype="Account",
doctype="Account", is_group=kwargs.get("is_group", 0),
is_group=kwargs.get("is_group", 0), account_name=kwargs.get("account_name"),
account_name=kwargs.get("account_name"), account_type=kwargs.get("account_type"),
account_type=kwargs.get("account_type"), parent_account=kwargs.get("parent_account"),
parent_account=kwargs.get("parent_account"), company=kwargs.get("company"),
company=kwargs.get("company"), account_currency=kwargs.get("account_currency"),
account_currency=kwargs.get("account_currency"),
)
) )
account.save() account.save()

View File

@@ -125,7 +125,7 @@ class BankClearance(Document):
) )
msg += "</ul>" msg += "</ul>"
frappe.throw(_(msg)) msgprint(_(msg))
return return
if not entries_to_update: if not entries_to_update:

View File

@@ -30,8 +30,7 @@
"label": "Payment Entry", "label": "Payment Entry",
"oldfieldname": "voucher_id", "oldfieldname": "voucher_id",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "payment_document", "options": "payment_document"
"width": "50"
}, },
{ {
"columns": 2, "columns": 2,
@@ -69,7 +68,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"columns": 2, "columns": 1,
"fieldname": "cheque_number", "fieldname": "cheque_number",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
@@ -79,8 +78,10 @@
"read_only": 1 "read_only": 1
}, },
{ {
"columns": 2,
"fieldname": "cheque_date", "fieldname": "cheque_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Cheque Date", "label": "Cheque Date",
"oldfieldname": "cheque_date", "oldfieldname": "cheque_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
@@ -96,17 +97,19 @@
"oldfieldtype": "Date" "oldfieldtype": "Date"
} }
], ],
"grid_page_length": 50,
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:06:37.609319", "modified": "2025-12-17 14:33:45.913311",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Clearance Detail", "name": "Bank Clearance Detail",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "ASC", "sort_order": "ASC",
"states": [] "states": []
} }

View File

@@ -304,6 +304,7 @@ def create_payment_entry_bts(
project=None, project=None,
cost_center=None, cost_center=None,
allow_edit=None, allow_edit=None,
company_bank_account=None,
): ):
# Create a new payment entry based on the bank transaction # Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values( bank_transaction = frappe.db.get_values(
@@ -345,6 +346,9 @@ def create_payment_entry_bts(
pe.project = project pe.project = project
pe.cost_center = cost_center pe.cost_center = cost_center
if company_bank_account:
pe.bank_account = company_bank_account
pe.validate() pe.validate()
if allow_edit: if allow_edit:

View File

@@ -50,6 +50,9 @@ class BankTransaction(Document):
self.handle_excluded_fee() self.handle_excluded_fee()
self.update_allocated_amount() self.update_allocated_amount()
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
self.validate_included_fee() self.validate_included_fee()
self.validate_duplicate_references() self.validate_duplicate_references()

View File

@@ -9,8 +9,8 @@ frappe.listview_settings["Invoice Discounting"] = {
return [__("Disbursed"), "blue", "status,=,Disbursed"]; return [__("Disbursed"), "blue", "status,=,Disbursed"];
} else if (doc.status == "Settled") { } else if (doc.status == "Settled") {
return [__("Settled"), "orange", "status,=,Settled"]; return [__("Settled"), "orange", "status,=,Settled"];
} else if (doc.status == "Canceled") { } else if (doc.status == "Cancelled") {
return [__("Canceled"), "red", "status,=,Canceled"]; return [__("Cancelled"), "red", "status,=,Cancelled"];
} }
}, },
}; };

View File

@@ -182,7 +182,7 @@ frappe.ui.form.on("Payment Entry", {
"Dunning", "Dunning",
]; ];
if (in_list(party_type_doctypes, child.reference_doctype)) { if (party_type_doctypes.includes(child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party; filters[doc.party_type.toLowerCase()] = doc.party;
} }
@@ -427,7 +427,15 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.payment_type == "Internal Transfer") { if (frm.doc.payment_type == "Internal Transfer") {
$.each( $.each(
["party", "party_type", "paid_from", "paid_to", "references", "total_allocated_amount"], [
"party",
"party_type",
"paid_from",
"paid_to",
"references",
"total_allocated_amount",
"party_name",
],
function (i, field) { function (i, field) {
frm.set_value(field, null); frm.set_value(field, null);
} }
@@ -1033,7 +1041,7 @@ frappe.ui.form.on("Payment Entry", {
c.allocated_amount = d.allocated_amount; c.allocated_amount = d.allocated_amount;
c.account = d.account; c.account = d.account;
if (!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) { if (!frm.events.get_order_doctypes(frm).includes(d.voucher_type)) {
if (flt(d.outstanding_amount) > 0) if (flt(d.outstanding_amount) > 0)
total_positive_outstanding += flt(d.outstanding_amount); total_positive_outstanding += flt(d.outstanding_amount);
else total_negative_outstanding += Math.abs(flt(d.outstanding_amount)); else total_negative_outstanding += Math.abs(flt(d.outstanding_amount));
@@ -1049,7 +1057,7 @@ frappe.ui.form.on("Payment Entry", {
} else { } else {
c.exchange_rate = 1; c.exchange_rate = 1;
} }
if (in_list(frm.events.get_invoice_doctypes(frm), d.reference_doctype)) { if (frm.events.get_invoice_doctypes(frm).includes(d.reference_doctype)) {
c.due_date = d.due_date; c.due_date = d.due_date;
} }
}); });

View File

@@ -50,12 +50,10 @@ class TestPaymentOrder(IntegrationTestCase):
def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account): def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
payment_order = frappe.get_doc( payment_order = frappe.get_doc(
dict( doctype="Payment Order",
doctype="Payment Order", company="_Test Company",
company="_Test Company", payment_order_type=order_type,
payment_order_type=order_type, company_bank_account=bank_account,
company_bank_account=bank_account,
)
) )
doc = make_payment_order(ref_doc.name, payment_order) doc = make_payment_order(ref_doc.name, payment_order)
doc.save() doc.save()

View File

@@ -100,7 +100,10 @@ class PaymentRequest(Document):
subscription_plans: DF.Table[SubscriptionPlanDetail] subscription_plans: DF.Table[SubscriptionPlanDetail]
swift_number: DF.ReadOnly | None swift_number: DF.ReadOnly | None
transaction_date: DF.Date | None transaction_date: DF.Date | None
# end: auto-generated types # end: auto-generated types
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
if self.get("__islocal"): if self.get("__islocal"):

View File

@@ -171,7 +171,7 @@ frappe.ui.form.on("Pricing Rule", {
set_field_options("applicable_for", options.join("\n")); set_field_options("applicable_for", options.join("\n"));
if (!in_list(options, applicable_for)) applicable_for = null; if (!options.includes(applicable_for)) applicable_for = null;
frm.set_value("applicable_for", applicable_for); frm.set_value("applicable_for", applicable_for);
}, },
}); });

View File

@@ -48,13 +48,11 @@ class TestProcessDeferredAccounting(IntegrationTestCase):
check_gl_entries(self, si.name, original_gle, "2023-07-01") check_gl_entries(self, si.name, original_gle, "2023-07-01")
process_deferred_accounting = frappe.get_doc( process_deferred_accounting = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date="2023-07-01",
posting_date="2023-07-01", start_date="2023-05-01",
start_date="2023-05-01", end_date="2023-06-30",
end_date="2023-06-30", type="Income",
type="Income",
)
) )
process_deferred_accounting.insert() process_deferred_accounting.insert()
@@ -80,13 +78,11 @@ class TestProcessDeferredAccounting(IntegrationTestCase):
def test_pda_submission_and_cancellation(self): def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc( pda = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date="2019-01-01",
posting_date="2019-01-01", start_date="2019-01-01",
start_date="2019-01-01", end_date="2019-01-31",
end_date="2019-01-31", type="Income",
type="Income",
)
) )
pda.submit() pda.submit()
pda.cancel() pda.cancel()

View File

@@ -35,7 +35,10 @@ class ProcessPaymentReconciliation(Document):
] ]
to_invoice_date: DF.Date | None to_invoice_date: DF.Date | None
to_payment_date: DF.Date | None to_payment_date: DF.Date | None
# end: auto-generated types # end: auto-generated types
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
self.validate_receivable_payable_account() self.validate_receivable_payable_account()

View File

@@ -36,7 +36,10 @@ class ProcessPeriodClosingVoucher(Document):
parent_pcv: DF.Link parent_pcv: DF.Link
status: DF.Literal["Queued", "Running", "Paused", "Completed", "Cancelled"] status: DF.Literal["Queued", "Running", "Paused", "Completed", "Cancelled"]
z_opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] z_opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail]
# end: auto-generated types # end: auto-generated types
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
self.status = "Queued" self.status = "Queued"

View File

@@ -46,7 +46,7 @@ frappe.ui.form.on("Promotional Scheme", {
set_field_options("applicable_for", options.join("\n")); set_field_options("applicable_for", options.join("\n"));
if (!in_list(options, applicable_for)) applicable_for = null; if (!options.includes(applicable_for)) applicable_for = null;
frm.set_value("applicable_for", applicable_for); frm.set_value("applicable_for", applicable_for);
}, },

View File

@@ -1249,14 +1249,12 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
pi.submit() pi.submit()
pda1 = frappe.get_doc( pda1 = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2019-01-01",
start_date="2019-01-01", end_date="2019-03-31",
end_date="2019-03-31", type="Expense",
type="Expense", company="_Test Company",
company="_Test Company",
)
) )
pda1.insert() pda1.insert()

View File

@@ -115,6 +115,10 @@ class RepostAccountingLedger(Document):
def generate_preview(self): def generate_preview(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns from erpnext.accounts.report.general_ledger.general_ledger import get_columns as get_gl_columns
if not self.vouchers:
frappe.msgprint(_("Add vouchers to generate preview."))
return
gl_columns = [] gl_columns = []
gl_data = [] gl_data = []
@@ -142,6 +146,7 @@ class RepostAccountingLedger(Document):
account_repost_doc=self.name, account_repost_doc=self.name,
is_async=True, is_async=True,
job_name=job_name, job_name=job_name,
enqueue_after_commit=True,
) )
frappe.msgprint(_("Repost has started in the background")) frappe.msgprint(_("Repost has started in the background"))
else: else:

View File

@@ -2510,14 +2510,12 @@ class TestSalesInvoice(ERPNextTestSuite):
si.submit() si.submit()
pda1 = frappe.get_doc( pda1 = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2019-01-01",
start_date="2019-01-01", end_date="2019-03-31",
end_date="2019-03-31", type="Income",
type="Income", company="_Test Company",
company="_Test Company",
)
) )
pda1.insert() pda1.insert()
@@ -2568,14 +2566,12 @@ class TestSalesInvoice(ERPNextTestSuite):
si.submit() si.submit()
pda1 = frappe.get_doc( pda1 = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date="2019-03-31",
posting_date="2019-03-31", start_date="2019-01-01",
start_date="2019-01-01", end_date="2019-03-31",
end_date="2019-03-31", type="Income",
type="Income", company="_Test Company",
company="_Test Company",
)
) )
pda1.insert() pda1.insert()
@@ -3478,14 +3474,12 @@ class TestSalesInvoice(ERPNextTestSuite):
frappe.db.set_value("Company", "_Test Company", "accounts_frozen_till_date", getdate("2019-01-31")) frappe.db.set_value("Company", "_Test Company", "accounts_frozen_till_date", getdate("2019-01-31"))
pda1 = frappe.get_doc( pda1 = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2019-01-01",
start_date="2019-01-01", end_date="2019-03-31",
end_date="2019-03-31", type="Income",
type="Income", company="_Test Company",
company="_Test Company",
)
) )
pda1.insert() pda1.insert()

View File

@@ -25,7 +25,6 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
) )
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.party import get_party_account_currency
class InvoiceCancelled(frappe.ValidationError): class InvoiceCancelled(frappe.ValidationError):
@@ -432,7 +431,6 @@ class Subscription(Document):
items_list = self.get_items_from_plans(self.plans, is_prorate()) items_list = self.get_items_from_plans(self.plans, is_prorate())
for item in items_list: for item in items_list:
item["cost_center"] = self.cost_center
invoice.append("items", item) invoice.append("items", item)
# Taxes # Taxes

View File

@@ -653,7 +653,7 @@ def reset_settings():
def create_subscription(**kwargs): def create_subscription(**kwargs):
subscription = frappe.new_doc("Subscription") subscription = frappe.new_doc("Subscription")
subscription.party_type = (kwargs.get("party_type") or "Customer",) subscription.party_type = kwargs.get("party_type") or "Customer"
subscription.company = kwargs.get("company") or "_Test Company" subscription.company = kwargs.get("company") or "_Test Company"
subscription.party = kwargs.get("party") or "_Test Customer" subscription.party = kwargs.get("party") or "_Test Customer"
subscription.trial_period_start = kwargs.get("trial_period_start") subscription.trial_period_start = kwargs.get("trial_period_start")

View File

@@ -1214,8 +1214,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
# First invoice - below threshold, should be under withheld # First invoice - below threshold, should be under withheld
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True)
pi.apply_tds = 1 pi.apply_tds = 1
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.save()
pi.submit() pi.submit()
invoices.append(pi) invoices.append(pi)
@@ -1478,7 +1476,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=12000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=12000, do_not_save=True)
pi.apply_tds = 1 pi.apply_tds = 1
pi.tax_withholding_category = "Test Multi Invoice Category"
advances = pi.get_advance_entries() advances = pi.get_advance_entries()
pi.append( pi.append(
"advances", "advances",
@@ -2096,7 +2093,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
# Create purchase invoice that settles the payment entry # Create purchase invoice that settles the payment entry
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=8000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=8000, do_not_save=True)
pi.apply_tds = 1 pi.apply_tds = 1
pi.tax_withholding_category = "Test Multi Invoice Category"
advances = pi.get_advance_entries() advances = pi.get_advance_entries()
pi.append( pi.append(
"advances", "advances",
@@ -2719,7 +2715,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
# Create purchase invoice with manual override # Create purchase invoice with manual override
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=20000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=20000, do_not_save=True)
pi.apply_tds = 1 pi.apply_tds = 1
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.ignore_tax_withholding_threshold = 1 pi.ignore_tax_withholding_threshold = 1
pi.save() pi.save()
@@ -2749,7 +2744,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
# Step 2: Create Purchase Invoice with partial adjustment and manual rate change # Step 2: Create Purchase Invoice with partial adjustment and manual rate change
pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=80000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=80000, do_not_save=True)
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.override_tax_withholding_entries = 1 # Enable manual override pi.override_tax_withholding_entries = 1 # Enable manual override
pi.tax_withholding_entries = [] pi.tax_withholding_entries = []
@@ -2790,6 +2784,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
) )
pi.save() pi.save()
pi.reload()
pi.submit() pi.submit()
# Step 3: Verify the tax withholding entries # Step 3: Verify the tax withholding entries
@@ -2870,7 +2865,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
# Step 2: Create Purchase Invoice with partial adjustment and manual rate change # Step 2: Create Purchase Invoice with partial adjustment and manual rate change
pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=80000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=80000, do_not_save=True)
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.override_tax_withholding_entries = 1 # Enable manual override pi.override_tax_withholding_entries = 1 # Enable manual override
pi.tax_withholding_entries = [] pi.tax_withholding_entries = []
@@ -2911,6 +2905,7 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
) )
pi.save() pi.save()
pi.reload()
pi.submit() pi.submit()
# Step 3: Verify the tax withholding entries # Step 3: Verify the tax withholding entries
@@ -2993,7 +2988,6 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
self.validate_tax_withholding_entries("Payment Entry", pe.name, pe_expected) self.validate_tax_withholding_entries("Payment Entry", pe.name, pe_expected)
pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=50000, do_not_save=True) pi = create_purchase_invoice(supplier="Test TDS Supplier8", rate=50000, do_not_save=True)
pi.tax_withholding_category = "Test Multi Invoice Category"
pi.override_tax_withholding_entries = 1 pi.override_tax_withholding_entries = 1
pi.tax_withholding_entries = [] pi.tax_withholding_entries = []

View File

@@ -377,30 +377,23 @@ class TaxWithholdingController:
return category_names return category_names
def calculate(self): def calculate(self):
# Always get category details first for account mapping
self.category_details = self._get_category_details() self.category_details = self._get_category_details()
self._update_taxable_amounts()
if not self.doc.override_tax_withholding_entries: if not self.doc.override_tax_withholding_entries:
self._generate_withholding_entries() self._generate_withholding_entries()
# Final processing - entry status and tax_update
self._process_withholding_entries() self._process_withholding_entries()
def _generate_withholding_entries(self): def _generate_withholding_entries(self):
# Clear existing entries
self.doc.tax_withholding_entries = [] self.doc.tax_withholding_entries = []
# Calculate taxable amounts for each category
self._update_taxable_amounts()
# Apply threshold rules
self._evaluate_thresholds() self._evaluate_thresholds()
# Generate entries for each category
for category in self.category_details.values(): for category in self.category_details.values():
self.entries += self._create_entries_for_category(category) self.entries += self._create_entries_for_category(category)
# Add all generated entries to the document
self.doc.extend("tax_withholding_entries", self.entries) self.doc.extend("tax_withholding_entries", self.entries)
def _create_entries_for_category(self, category): def _create_entries_for_category(self, category):

View File

@@ -1063,3 +1063,21 @@ def add_party_account(party_type, party, company, account):
def render_address(address, check_permissions=True): def render_address(address, check_permissions=True):
return frappe.call(_render_address, address, check_permissions=check_permissions) return frappe.call(_render_address, address, check_permissions=check_permissions)
def validate_party_currency_before_merging(party_type, old_party, new_party):
for company in frappe.get_all("Company"):
old_party_currency = get_party_gle_currency(party_type, old_party, company.name)
new_party_currency = get_party_gle_currency(party_type, new_party, company.name)
if old_party_currency and new_party_currency and old_party_currency != new_party_currency:
frappe.throw(
_(
"Cannot merge {0} '{1}' into '{2}' as both have existing accounting entries in different currencies for company '{3}'."
).format(
party_type,
old_party,
new_party,
company.name,
)
)

View File

@@ -101,14 +101,12 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin):
si.submit() si.submit()
pda = frappe.get_doc( pda = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2021-05-01",
start_date="2021-05-01", end_date="2021-08-01",
end_date="2021-08-01", type="Income",
type="Income", company=self.company,
company=self.company,
)
) )
pda.insert() pda.insert()
pda.submit() pda.submit()
@@ -173,14 +171,12 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin):
pi.submit() pi.submit()
pda = frappe.get_doc( pda = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2021-05-01",
start_date="2021-05-01", end_date="2021-08-01",
end_date="2021-08-01", type="Expense",
type="Expense", company=self.company,
company=self.company,
)
) )
pda.insert() pda.insert()
pda.submit() pda.submit()
@@ -240,14 +236,12 @@ class TestDeferredRevenueAndExpense(IntegrationTestCase, AccountsTestMixin):
si.submit() si.submit()
pda = frappe.get_doc( pda = frappe.get_doc(
dict( doctype="Process Deferred Accounting",
doctype="Process Deferred Accounting", posting_date=nowdate(),
posting_date=nowdate(), start_date="2021-05-01",
start_date="2021-05-01", end_date="2021-08-01",
end_date="2021-08-01", type="Income",
type="Income", company=self.company,
company=self.company,
)
) )
pda.insert() pda.insert()
pda.submit() pda.submit()

View File

@@ -6,7 +6,7 @@
"label": "Profit and Loss" "label": "Profit and Loss"
} }
], ],
"content": "[{\"id\":\"nDhfcJYbKH\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Tax Masters\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}}]", "content": "[{\"id\":\"nDhfcJYbKH\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Payment\",\"col\":3}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Tax Masters\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192", "creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@@ -14,7 +14,7 @@
"for_user": "", "for_user": "",
"hide_custom": 0, "hide_custom": 0,
"icon": "accounting", "icon": "accounting",
"idx": 0, "idx": 3,
"indicator_color": "", "indicator_color": "",
"is_hidden": 0, "is_hidden": 0,
"label": "Accounting", "label": "Accounting",
@@ -587,25 +587,25 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2025-11-17 14:35:00.910131", "modified": "2025-12-24 13:20:34.857205",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
"number_cards": [ "number_cards": [
{ {
"label": "Total Outgoing Bills", "label": "Outgoing Bills",
"number_card_name": "Total Outgoing Bills" "number_card_name": "Total Outgoing Bills"
}, },
{ {
"label": "Total Incoming Bills", "label": "Incoming Bills",
"number_card_name": "Total Incoming Bills" "number_card_name": "Total Incoming Bills"
}, },
{ {
"label": "Total Incoming Payment", "label": "Incoming Payment",
"number_card_name": "Total Incoming Payment" "number_card_name": "Total Incoming Payment"
}, },
{ {
"label": "Total Outgoing Payment", "label": "Outgoing Payment",
"number_card_name": "Total Outgoing Payment" "number_card_name": "Total Outgoing Payment"
} }
], ],

View File

@@ -1,13 +1,19 @@
{ {
"charts": [], "app": "erpnext",
"content": "[{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Ledgers\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "charts": [
{
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
}
],
"content": "[{\"id\":\"tS7ZWzC24I\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"8Ej2KxPxOt\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Ledgers\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2024-01-05 16:09:16.766939", "creation": "2024-01-05 16:09:16.766939",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
"for_user": "", "for_user": "",
"hide_custom": 0, "hide_custom": 0,
"icon": "file", "icon": "table",
"idx": 0, "idx": 0,
"indicator_color": "", "indicator_color": "",
"is_hidden": 0, "is_hidden": 0,
@@ -260,7 +266,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2024-01-18 22:13:07.596844", "modified": "2025-12-24 12:49:25.266357",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Financial Reports", "name": "Financial Reports",
@@ -273,5 +279,6 @@
"roles": [], "roles": [],
"sequence_id": 5.0, "sequence_id": 5.0,
"shortcuts": [], "shortcuts": [],
"title": "Financial Reports" "title": "Financial Reports",
} "type": "Workspace"
}

View File

@@ -1,204 +0,0 @@
{
"charts": [],
"content": "[{\"id\":\"rMMsfn2eB4\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Shortcuts</b></span>\",\"col\":12}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Payable\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jAcOH-cC-Q\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"7dj93PEUjW\",\"type\":\"card\",\"data\":{\"card_name\":\"Invoicing\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"9yseIkdG50\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2024-01-05 15:29:11.144373",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "arrow-left",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Payables",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Invoicing",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Invoice",
"link_count": 0,
"link_to": "Purchase Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Supplier",
"link_count": 0,
"link_to": "Supplier",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry",
"link_count": 0,
"link_to": "Journal Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Payable",
"link_count": 0,
"link_to": "Accounts Payable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Payable Summary",
"link_count": 0,
"link_to": "Accounts Payable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Register",
"link_count": 0,
"link_to": "Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Purchase Register",
"link_count": 0,
"link_to": "Item-wise Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Received Items To Be Billed",
"link_count": 0,
"link_to": "Received Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2024-01-18 22:09:46.221549",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payables",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 3.0,
"shortcuts": [
{
"doc_view": "",
"label": "Accounts Payable",
"link_to": "Accounts Payable",
"type": "Report"
},
{
"doc_view": "",
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"type": "DocType"
},
{
"doc_view": "",
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Payment Entry",
"link_to": "Payment Entry",
"type": "DocType"
}
],
"title": "Payables"
}

View File

@@ -1,254 +0,0 @@
{
"charts": [],
"content": "[{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Shortcuts</b></span>\",\"col\":12}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"5yHldR0JNk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"POS Invoice\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"ILlIxJuexy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Cost Center\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"jLgv00c6ek\",\"type\":\"card\",\"data\":{\"card_name\":\"Invoicing\",\"col\":4}},{\"id\":\"npwfXlz0u1\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"am70C27Jrb\",\"type\":\"card\",\"data\":{\"card_name\":\"Dunning\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2024-01-05 15:29:21.084241",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "arrow-right",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Receivables",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Invoicing",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Invoice",
"link_count": 0,
"link_to": "Sales Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 4,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Request",
"link_count": 0,
"link_to": "Payment Request",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Gateway Account",
"link_count": 0,
"link_to": "Payment Gateway Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning",
"link_count": 0,
"link_to": "Dunning",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning Type",
"link_count": 0,
"link_to": "Dunning Type",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 6,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable",
"link_count": 0,
"link_to": "Accounts Receivable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable Summary",
"link_count": 0,
"link_to": "Accounts Receivable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Register",
"link_count": 0,
"link_to": "Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Sales Register",
"link_count": 0,
"link_to": "Item-wise Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Order Analysis",
"link_count": 0,
"link_to": "Sales Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Delivered Items To Be Billed",
"link_count": 0,
"link_to": "Delivered Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2024-01-18 22:11:51.474477",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Receivables",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 4.0,
"shortcuts": [
{
"color": "Grey",
"doc_view": "List",
"label": "POS Invoice",
"link_to": "POS Invoice",
"stats_filter": "[]",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "List",
"label": "Cost Center",
"link_to": "Cost Center",
"type": "DocType"
},
{
"doc_view": "",
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"stats_filter": "[]",
"type": "DocType"
},
{
"doc_view": "",
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Payment Entry",
"link_to": "Payment Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"type": "Report"
}
],
"title": "Receivables"
}

View File

@@ -1,11 +1,12 @@
{ {
"app": "erpnext",
"charts": [ "charts": [
{ {
"chart_name": "Asset Value Analytics", "chart_name": "Asset Value Analytics",
"label": "Asset Value Analytics" "label": "Asset Value Analytics"
} }
], ],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "content": "[{\"id\":\"Q-Cl7bMXDm\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"id\":\"gsSQjvl0Tx\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"xRYRq1sW1O\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"Kx2j5N9BKZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"id\":\"jeNsxtLaH3\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"id\":\"EX5e3NvL51\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:27.634865", "creation": "2020-03-02 15:43:27.634865",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@@ -182,6 +183,7 @@
"link_to": "Asset Maintenance", "link_to": "Asset Maintenance",
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"report_ref_doctype": "Asset Maintenance",
"type": "Link" "type": "Link"
}, },
{ {
@@ -193,10 +195,11 @@
"link_to": "Asset Activity", "link_to": "Asset Activity",
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"report_ref_doctype": "Asset Activity",
"type": "Link" "type": "Link"
} }
], ],
"modified": "2024-01-05 17:40:34.570041", "modified": "2025-12-31 16:22:38.132729",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Assets", "name": "Assets",
@@ -208,27 +211,7 @@
"restrict_to_domain": "", "restrict_to_domain": "",
"roles": [], "roles": [],
"sequence_id": 7.0, "sequence_id": 7.0,
"shortcuts": [ "shortcuts": [],
{ "title": "Assets",
"label": "Asset", "type": "Workspace"
"link_to": "Asset", }
"type": "DocType"
},
{
"label": "Asset Category",
"link_to": "Asset Category",
"type": "DocType"
},
{
"label": "Fixed Asset Register",
"link_to": "Fixed Asset Register",
"type": "Report"
},
{
"label": "Dashboard",
"link_to": "Asset",
"type": "Dashboard"
}
],
"title": "Assets"
}

View File

@@ -248,6 +248,23 @@ frappe.ui.form.on("Request for Quotation", {
} }
refresh_field("items"); refresh_field("items");
}, },
email_template(frm) {
if (frm.doc.email_template) {
frappe.db
.get_value("Email Template", frm.doc.email_template, [
"use_html",
"response",
"response_html",
])
.then((r) => {
frm.set_value(
"message_for_supplier",
r.message.use_html ? r.message.response_html : r.message.response
);
});
}
},
preview: (frm) => { preview: (frm) => {
let dialog = new frappe.ui.Dialog({ let dialog = new frappe.ui.Dialog({
title: __("Preview Email"), title: __("Preview Email"),

View File

@@ -139,8 +139,6 @@
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fetch_from": "email_template.response",
"fetch_if_empty": 1,
"fieldname": "message_for_supplier", "fieldname": "message_for_supplier",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_list_view": 1, "in_list_view": 1,
@@ -322,7 +320,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-03-03 16:48:39.856779", "modified": "2025-12-29 14:44:18.934901",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Request for Quotation", "name": "Request for Quotation",
@@ -393,4 +391,4 @@
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -66,6 +66,7 @@ class RequestforQuotation(BuyingController):
def before_validate(self): def before_validate(self):
self.set_has_unit_price_items() self.set_has_unit_price_items()
self.flags.allow_zero_qty = self.has_unit_price_items self.flags.allow_zero_qty = self.has_unit_price_items
self.set_message_for_supplier()
def validate(self): def validate(self):
self.validate_duplicate_supplier() self.validate_duplicate_supplier()
@@ -90,6 +91,13 @@ class RequestforQuotation(BuyingController):
not row.qty for row in self.get("items") if (row.item_code and not row.qty) not row.qty for row in self.get("items") if (row.item_code and not row.qty)
) )
def set_message_for_supplier(self):
if self.email_template and not self.message_for_supplier:
data = frappe.get_value(
"Email Template", self.email_template, ["use_html", "response", "response_html"], as_dict=True
)
self.message_for_supplier = data.response_html if data.use_html else data.response
def validate_duplicate_supplier(self): def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers] supplier_list = [d.supplier for d in self.suppliers]
if len(supplier_list) != len(set(supplier_list)): if len(supplier_list) != len(set(supplier_list)):

View File

@@ -14,6 +14,7 @@ from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_
from erpnext.accounts.party import ( from erpnext.accounts.party import (
get_dashboard_info, get_dashboard_info,
validate_party_accounts, validate_party_accounts,
validate_party_currency_before_merging,
) )
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
@@ -213,6 +214,10 @@ class Supplier(TransactionBase):
delete_contact_and_address("Supplier", self.name) delete_contact_and_address("Supplier", self.name)
def before_rename(self, olddn, newdn, merge=False):
if merge:
validate_party_currency_before_merging("Supplier", olddn, newdn)
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name": if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
self.db_set("supplier_name", newdn) self.db_set("supplier_name", newdn)

View File

@@ -131,16 +131,14 @@ class TestSupplier(IntegrationTestCase):
self.assertEqual(details.tax_category, "_Test Tax Category 1") self.assertEqual(details.tax_category, "_Test Tax Category 1")
address = frappe.get_doc( address = frappe.get_doc(
dict( doctype="Address",
doctype="Address", address_title="_Test Address With Tax Category",
address_title="_Test Address With Tax Category", tax_category="_Test Tax Category 2",
tax_category="_Test Tax Category 2", address_type="Billing",
address_type="Billing", address_line1="Station Road",
address_line1="Station Road", city="_Test City",
city="_Test City", country="India",
country="India", links=[dict(link_doctype="Supplier", link_name="_Test Supplier With Tax Category")],
links=[dict(link_doctype="Supplier", link_name="_Test Supplier With Tax Category")],
)
).insert() ).insert()
# Tax Category with Address # Tax Category with Address

View File

@@ -45,7 +45,7 @@ class SupplierScorecardCriteria(Document):
mylist = re.finditer(regex, test_formula, re.MULTILINE | re.DOTALL) mylist = re.finditer(regex, test_formula, re.MULTILINE | re.DOTALL)
for _dummy1, match in enumerate(mylist): for _dummy1, match in enumerate(mylist):
for _dummy2 in range(0, len(match.groups())): for _dummy2 in range(0, len(match.groups())):
test_formula = test_formula.replace("{" + match.group(1) + "}", "0") test_formula = test_formula.replace("{" + match.group(1) + "}", "1")
try: try:
frappe.safe_eval(test_formula, None, {"max": max, "min": min}) frappe.safe_eval(test_formula, None, {"max": max, "min": min})

View File

@@ -17,7 +17,6 @@ class TestSupplierScorecardCriteria(IntegrationTestCase):
def test_formula_validate(self): def test_formula_validate(self):
delete_test_scorecards() delete_test_scorecards()
self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[1]).insert) self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[1]).insert)
self.assertRaises(frappe.ValidationError, frappe.get_doc(test_bad_criteria[2]).insert)
def delete_test_scorecards(): def delete_test_scorecards():
@@ -68,16 +67,8 @@ test_bad_criteria = [
"name": "Fake Criteria 2", "name": "Fake Criteria 2",
"weight": 40.0, "weight": 40.0,
"doctype": "Supplier Scorecard Criteria", "doctype": "Supplier Scorecard Criteria",
"formula": "(({cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Force 0 divided by 0 "formula": "(({cost_of_on_time_shipments} {cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Two variables beside eachother
"criteria_name": "Fake Criteria 2", "criteria_name": "Fake Criteria 2",
"max_score": 100.0, "max_score": 100.0,
}, },
{
"name": "Fake Criteria 3",
"weight": 40.0,
"doctype": "Supplier Scorecard Criteria",
"formula": "(({cost_of_on_time_shipments} {cost_of_on_time_shipments} / {tot_cost_shipments}))* 100", # Two variables beside eachother
"criteria_name": "Fake Criteria 3",
"max_score": 100.0,
},
] ]

View File

@@ -97,7 +97,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
value = default_formatter(value, row, column, data); value = default_formatter(value, row, column, data);
let format_fields = ["received_qty", "billed_amount"]; let format_fields = ["received_qty", "billed_amount"];
if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) { if (format_fields.includes(column.fieldname) && data && data[column.fieldname] > 0) {
value = "<span style='color:green'>" + value + "</span>"; value = "<span style='color:green'>" + value + "</span>";
} }
return value; return value;

View File

@@ -188,7 +188,7 @@ def find_variant(template, args, variant_item_code=None):
for attribute, value in args.items(): for attribute, value in args.items():
for row in variant.attributes: for row in variant.attributes:
if row.attribute == attribute and row.attribute_value == cstr(value): if row.attribute == _(attribute) and row.attribute_value == cstr(value):
# this row matches # this row matches
match_count += 1 match_count += 1
break break
@@ -209,7 +209,7 @@ def create_variant(item, args, use_template_image=False):
variant_attributes = [] variant_attributes = []
for d in template.attributes: for d in template.attributes:
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)}) variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(_(d.attribute))})
variant.set("attributes", variant_attributes) variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant) copy_attributes_to_variant(template, variant)

View File

@@ -1022,10 +1022,19 @@ class SellingController(StockController):
def set_default_income_account_for_item(obj): def set_default_income_account_for_item(obj):
for d in obj.get("items"): """Set income account as default for items in the transaction.
if d.item_code:
if getattr(d, "income_account", None): Updates the item default income account for each item in the transaction
set_item_default(d.item_code, obj.company, "income_account", d.income_account) if it differs from the company's default income account.
Args:
obj: Transaction document containing items table with income_account field
"""
company_default = frappe.get_cached_value("Company", obj.company, "default_income_account")
for d in obj.get("items", default=[]):
income_account = getattr(d, "income_account", None)
if d.item_code and income_account and income_account != company_default:
set_item_default(d.item_code, obj.company, "income_account", income_account)
def get_serial_and_batch_bundle(child, parent, delivery_note_child=None): def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):

View File

@@ -184,6 +184,9 @@ class StatusUpdater(Document):
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
""" """
def on_discard(self):
self.db_set("status", "Cancelled")
def update_prevdoc_status(self): def update_prevdoc_status(self):
self.update_qty() self.update_qty()
self.validate_qty() self.validate_qty()

View File

@@ -1401,6 +1401,7 @@ def make_rm_stock_entry(
stock_entry.set_stock_entry_type() stock_entry.set_stock_entry_type()
over_transfer_allowance = frappe.get_single_value("Buying Settings", "over_transfer_allowance")
for fg_item_code in fg_item_code_list: for fg_item_code in fg_item_code_list:
for rm_item in rm_items: for rm_item in rm_items:
if ( if (
@@ -1408,14 +1409,27 @@ def make_rm_stock_entry(
or rm_item.get("item_code") == fg_item_code or rm_item.get("item_code") == fg_item_code
): ):
rm_item_code = rm_item.get("rm_item_code") rm_item_code = rm_item.get("rm_item_code")
qty = rm_item.get("qty") or max(
rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0
)
if qty <= 0 and rm_item.get("total_supplied_qty"):
per_transferred = (
flt(
rm_item.get("total_supplied_qty") / rm_item.get("required_qty"),
frappe.db.get_default("float_precision"),
)
* 100
)
if per_transferred >= 100 + over_transfer_allowance:
continue
items_dict = { items_dict = {
rm_item_code: { rm_item_code: {
rm_detail_field: rm_item.get("name"), rm_detail_field: rm_item.get("name"),
"item_name": rm_item.get("item_name") "item_name": rm_item.get("item_name")
or item_wh.get(rm_item_code, {}).get("item_name", ""), or item_wh.get(rm_item_code, {}).get("item_name", ""),
"description": item_wh.get(rm_item_code, {}).get("description", ""), "description": item_wh.get(rm_item_code, {}).get("description", ""),
"qty": rm_item.get("qty") "qty": qty,
or max(rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0),
"from_warehouse": rm_item.get("warehouse") "from_warehouse": rm_item.get("warehouse")
or rm_item.get("reserve_warehouse"), or rm_item.get("reserve_warehouse"),
"to_warehouse": subcontract_order.supplier_warehouse, "to_warehouse": subcontract_order.supplier_warehouse,

View File

@@ -85,7 +85,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"options": "Unsigned\nActive\nInactive" "options": "Unsigned\nActive\nInactive\nCancelled"
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@@ -257,11 +257,11 @@
"grid_page_length": 50, "grid_page_length": 50,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-06-19 17:48:45.049007", "modified": "2025-12-24 21:33:51.240497",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract", "name": "Contract",
"naming_rule": "Expression (old style)", "naming_rule": "Expression",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -43,7 +43,7 @@ class Contract(Document):
signed_on: DF.Datetime | None signed_on: DF.Datetime | None
signee: DF.Data | None signee: DF.Data | None
start_date: DF.Date | None start_date: DF.Date | None
status: DF.Literal["Unsigned", "Active", "Inactive"] status: DF.Literal["Unsigned", "Active", "Inactive", "Cancelled"]
# end: auto-generated types # end: auto-generated types
def validate(self): def validate(self):
@@ -61,6 +61,9 @@ class Contract(Document):
def before_submit(self): def before_submit(self):
self.signed_by_company = frappe.session.user self.signed_by_company = frappe.session.user
def on_discard(self):
self.db_set("status", "Cancelled")
def before_update_after_submit(self): def before_update_after_submit(self):
self.update_contract_status() self.update_contract_status()
self.update_fulfilment_status() self.update_fulfilment_status()

View File

@@ -1,6 +1,6 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.309749", "creation": "2025-11-17 20:55:11.854086",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
@@ -10,7 +10,7 @@
"label": "Accounting", "label": "Accounting",
"link_to": "Accounting", "link_to": "Accounting",
"link_type": "Workspace", "link_type": "Workspace",
"modified": "2025-11-17 13:33:35.788242", "modified": "2025-12-31 16:06:04.678577",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Accounting", "name": "Accounting",
"owner": "Administrator", "owner": "Administrator",

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.299276", "creation": "2025-11-17 20:55:11.845676",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "assets", "icon": "assets",
"icon_type": "Link", "icon_type": "Link",
"idx": 8, "idx": 1,
"label": "Assets", "label": "Assets",
"link_to": "Assets", "link_to": "Assets",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/asset.svg", "modified": "2025-12-22 13:07:02.129228",
"modified": "2025-11-17 17:41:41.635533",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Assets", "name": "Assets",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -8,9 +8,9 @@
"icon_type": "Link", "icon_type": "Link",
"idx": 5, "idx": 5,
"label": "Banking", "label": "Banking",
"link_to": "Bank Reconciliation Tool", "link_to": "Bank Clearance",
"link_type": "DocType", "link_type": "DocType",
"modified": "2025-11-19 15:57:20.139306", "modified": "2025-12-22 16:53:37.020794",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Banking", "name": "Banking",
"owner": "Administrator", "owner": "Administrator",

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.327790", "creation": "2025-11-17 20:55:11.868134",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "buying", "icon": "buying",
"icon_type": "Link", "icon_type": "Link",
"idx": 3, "idx": 1,
"label": "Buying", "label": "Buying",
"link_to": "Buying", "link_to": "Buying",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/buying.svg", "modified": "2025-12-22 13:06:55.022406",
"modified": "2025-11-17 17:38:19.203107",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Buying", "name": "Buying",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.340610", "creation": "2025-11-17 20:55:11.876996",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "crm", "icon": "crm",
"icon_type": "Link", "icon_type": "Link",
"idx": 7, "idx": 1,
"label": "CRM", "label": "CRM",
"link_to": "CRM", "link_to": "CRM",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/crm.svg", "modified": "2025-12-22 13:06:47.900639",
"modified": "2025-11-17 19:39:59.734778",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "CRM", "name": "CRM",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,20 +1,21 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-05 12:11:24.655043", "creation": "2025-11-17 20:55:11.772622",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "file", "icon": "file",
"icon_type": "Link", "icon_type": "Link",
"idx": 7, "idx": 0,
"label": "Financial Reports", "label": "Financial Reports",
"link_to": "Financial Reports", "link_to": "Financial Reports",
"link_type": "Workspace", "link_type": "Workspace",
"modified": "2025-11-17 13:34:48.074533", "modified": "2025-12-31 16:07:27.060176",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Financial Reports", "name": "Financial Reports",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "Accounts", "parent_icon": "Accounts",
"roles": [], "roles": [],
"sidebar": "",
"standard": 1 "standard": 1
} }

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.269805", "creation": "2025-11-17 20:55:11.824443",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "organization", "icon": "organization",
"icon_type": "Link", "icon_type": "Link",
"idx": 5, "idx": 1,
"label": "Manufacturing", "label": "Manufacturing",
"link_to": "Manufacturing", "link_to": "Manufacturing",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/manufacturing.svg", "modified": "2025-12-22 13:07:15.536476",
"modified": "2025-11-17 17:40:14.207766",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Manufacturing", "name": "Manufacturing",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,20 +0,0 @@
{
"app": "erpnext",
"creation": "2025-11-12 15:15:15.824801",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon": "panel-top-open",
"icon_type": "Link",
"idx": 2,
"label": "Opening & Closing",
"link_to": "Period Closing Voucher",
"link_type": "DocType",
"modified": "2025-11-19 15:59:14.805915",
"modified_by": "Administrator",
"name": "Opening & Closing",
"owner": "Administrator",
"parent_icon": "Accounts",
"roles": [],
"standard": 1
}

View File

@@ -1,20 +0,0 @@
{
"app": "erpnext",
"creation": "2025-11-17 13:19:04.255041",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon": "arrow-left",
"icon_type": "Link",
"idx": 0,
"label": "Payables",
"link_to": "Payables",
"link_type": "Workspace",
"modified": "2025-11-17 15:53:14.737052",
"modified_by": "Administrator",
"name": "Payables",
"owner": "Administrator",
"parent_icon": "Accounts",
"roles": [],
"standard": 1
}

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.293376", "creation": "2025-11-17 20:55:11.841426",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "project", "icon": "project",
"icon_type": "Link", "icon_type": "Link",
"idx": 9, "idx": 1,
"label": "Projects", "label": "Projects",
"link_to": "Projects", "link_to": "Projects",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/projects.svg", "modified": "2025-12-22 13:07:07.027692",
"modified": "2025-11-17 17:42:29.470661",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Projects", "name": "Projects",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.281193", "creation": "2025-11-17 20:55:11.828716",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "quality", "icon": "quality",
"icon_type": "Link", "icon_type": "Link",
"idx": 9, "idx": 1,
"label": "Quality", "label": "Quality",
"link_to": "Quality", "link_to": "Quality",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/quality.svg", "modified": "2025-12-22 13:07:13.506034",
"modified": "2025-11-17 17:42:48.371889",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Quality", "name": "Quality",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,20 +0,0 @@
{
"app": "erpnext",
"creation": "2025-11-17 13:19:04.249828",
"docstatus": 0,
"doctype": "Desktop Icon",
"hidden": 0,
"icon": "arrow-right",
"icon_type": "Link",
"idx": 0,
"label": "Receivables",
"link_to": "Receivables",
"link_type": "Workspace",
"modified": "2025-11-17 15:53:24.958812",
"modified_by": "Administrator",
"name": "Receivables",
"owner": "Administrator",
"parent_icon": "Accounts",
"roles": [],
"standard": 1
}

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.335051", "creation": "2025-11-17 20:55:11.872574",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "sell", "icon": "sell",
"icon_type": "Link", "icon_type": "Link",
"idx": 2, "idx": 1,
"label": "Selling", "label": "Selling",
"link_to": "Selling", "link_to": "Selling",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/selling.svg", "modified": "2025-12-22 13:06:51.346923",
"modified": "2025-11-17 17:37:59.847865",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Selling", "name": "Selling",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.304517", "creation": "2025-11-17 20:55:11.849904",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "stock", "icon": "stock",
"icon_type": "Link", "icon_type": "Link",
"idx": 4, "idx": 1,
"label": "Stock", "label": "Stock",
"link_to": "Stock", "link_to": "Stock",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/stock.svg", "modified": "2025-12-22 13:07:00.130510",
"modified": "2025-11-17 17:38:40.957627",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Stock", "name": "Stock",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -6,11 +6,11 @@
"hidden": 0, "hidden": 0,
"icon": "monitor-check", "icon": "monitor-check",
"icon_type": "Link", "icon_type": "Link",
"idx": 6, "idx": 99,
"label": "Subscription", "label": "Subscription",
"link_to": "Subscription", "link_to": "Subscription",
"link_type": "DocType", "link_type": "DocType",
"modified": "2025-11-19 16:02:32.686833", "modified": "2025-12-22 17:04:08.800434",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Subscription", "name": "Subscription",
"owner": "Administrator", "owner": "Administrator",

View File

@@ -1,21 +1,20 @@
{ {
"app": "erpnext", "app": "erpnext",
"creation": "2025-11-17 13:19:04.288298", "creation": "2025-11-17 20:55:11.837227",
"docstatus": 0, "docstatus": 0,
"doctype": "Desktop Icon", "doctype": "Desktop Icon",
"hidden": 0, "hidden": 0,
"icon": "support", "icon": "support",
"icon_type": "Link", "icon_type": "Link",
"idx": 10, "idx": 1,
"label": "Support", "label": "Support",
"link_to": "Support", "link_to": "Support",
"link_type": "Workspace", "link_type": "Workspace",
"logo_url": "/assets/erpnext/desktop_icons/support.svg", "modified": "2025-12-22 13:07:11.812983",
"modified": "2025-11-17 17:43:09.335445",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Support", "name": "Support",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "", "parent_icon": "ERPNext",
"roles": [], "roles": [],
"standard": 1 "standard": 1
} }

View File

@@ -8,13 +8,14 @@
"icon_type": "Link", "icon_type": "Link",
"idx": 3, "idx": 3,
"label": "Taxes", "label": "Taxes",
"link_to": "Item Tax Template", "link_to": "Sales Taxes and Charges Template",
"link_type": "DocType", "link_type": "DocType",
"modified": "2025-11-19 15:58:21.226664", "modified": "2025-12-31 16:59:14.978584",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Taxes", "name": "Taxes",
"owner": "Administrator", "owner": "Administrator",
"parent_icon": "Accounts", "parent_icon": "Accounts",
"roles": [], "roles": [],
"sidebar": "",
"standard": 1 "standard": 1
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,20 @@ frappe.ui.form.on("BOM", {
}; };
}); });
frm.set_query("operation", "items", function () {
if (!frm.doc.operations?.length) {
frappe.throw(__("Please add Operations first."));
}
let operations = frm.doc.operations.map((d) => d.operation);
return {
filters: {
name: ["in", operations],
},
};
});
frm.set_query("bom_no", "operations", function (doc, cdt, cdn) { frm.set_query("bom_no", "operations", function (doc, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
return { return {

View File

@@ -1036,10 +1036,14 @@ class BOM(WebsiteGenerator):
return erpnext.get_company_currency(self.company) return erpnext.get_company_currency(self.company)
def add_to_cur_exploded_items(self, args): def add_to_cur_exploded_items(self, args):
if self.cur_exploded_items.get(args.item_code): key = args.item_code
self.cur_exploded_items[args.item_code]["stock_qty"] += args.stock_qty if args.operation:
key = (args.item_code, args.operation)
if self.cur_exploded_items.get(key):
self.cur_exploded_items[key]["stock_qty"] += args.stock_qty
else: else:
self.cur_exploded_items[args.item_code] = args self.cur_exploded_items[key] = args
def get_child_exploded_items(self, bom_no, stock_qty, operation=None): def get_child_exploded_items(self, bom_no, stock_qty, operation=None):
"""Add all items from Flat BOM of child BOM""" """Add all items from Flat BOM of child BOM"""
@@ -1275,7 +1279,7 @@ def get_bom_items_as_dict(
): ):
item_dict = {} item_dict = {}
group_by_cond = "group by item_code, stock_uom" group_by_cond = "group by item_code, stock_uom, operation"
if frappe.get_cached_value("BOM", bom, "track_semi_finished_goods"): if frappe.get_cached_value("BOM", bom, "track_semi_finished_goods"):
fetch_exploded = 0 fetch_exploded = 0
group_by_cond = "group by item_code, operation_row_id, stock_uom" group_by_cond = "group by item_code, operation_row_id, stock_uom"
@@ -1360,6 +1364,9 @@ def get_bom_items_as_dict(
if item.operation_row_id: if item.operation_row_id:
key = (item.item_code, item.operation_row_id) key = (item.item_code, item.operation_row_id)
if item.operation:
key = (item.item_code, item.operation)
if item.get("is_phantom_item"): if item.get("is_phantom_item"):
data = get_bom_items_as_dict( data = get_bom_items_as_dict(
item.get("bom_no"), item.get("bom_no"),

View File

@@ -4,7 +4,7 @@
from collections import OrderedDict from collections import OrderedDict
import frappe import frappe
from frappe import _ from frappe import _, bold
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, flt, sbool from frappe.utils import cint, flt, sbool
from pypika.terms import ValueWrapper from pypika.terms import ValueWrapper
@@ -78,6 +78,29 @@ class BOMCreator(Document):
def validate(self): def validate(self):
self.validate_items() self.validate_items()
self.validate_duplicate_item()
def validate_duplicate_item(self):
# If same items added multiple times under same parent, raise error
item_map = {}
for row in self.items:
if not row.fg_reference_id:
continue
key = (row.item_code, row.fg_reference_id)
if key in item_map:
parent_item_code = next(
item.item_code for item in self.items if item.name == row.fg_reference_id
)
frappe.throw(
_(
"Item {0} added multiple times under the same parent item {1} at rows {2} and {3}"
).format(bold(row.item_code), bold(parent_item_code), item_map[key], row.idx),
title=_("Duplicate Item Under Same Parent"),
)
else:
item_map[key] = row.idx
def validate_items(self): def validate_items(self):
for row in self.items: for row in self.items:
@@ -182,7 +205,7 @@ class BOMCreator(Document):
else: else:
row.rate = flt(self.get_raw_material_cost(row.item_code) * row.conversion_factor) row.rate = flt(self.get_raw_material_cost(row.item_code) * row.conversion_factor)
row.amount = flt(row.rate * row.qty) row.amount = flt(row.rate) * flt(row.qty)
amount += flt(row.amount) amount += flt(row.amount)
return amount return amount

View File

@@ -147,8 +147,10 @@
}, },
{ {
"depends_on": "eval:doc.parenttype == \"Routing\"", "depends_on": "eval:doc.parenttype == \"Routing\"",
"description": "If you want to run operations in parallel, keep the same sequence ID for them.",
"fieldname": "sequence_id", "fieldname": "sequence_id",
"fieldtype": "Int", "fieldtype": "Int",
"in_list_view": 1,
"label": "Sequence ID" "label": "Sequence ID"
}, },
{ {
@@ -295,7 +297,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2025-08-12 19:27:20.682797", "modified": "2026-01-01 17:15:59.806874",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Operation", "name": "BOM Operation",

View File

@@ -52,7 +52,7 @@
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Status", "label": "Status",
"options": "Queued\nIn Progress\nCompleted\nFailed" "options": "Queued\nIn Progress\nCompleted\nFailed\nCancelled"
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
@@ -98,11 +98,11 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:06:41.658172", "modified": "2025-12-24 12:27:06.478893",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Update Log", "name": "BOM Update Log",
"naming_rule": "Expression (old style)", "naming_rule": "Expression",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -131,8 +131,9 @@
"write": 1 "write": 1
} }
], ],
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -41,7 +41,7 @@ class BOMUpdateLog(Document):
error_log: DF.Link | None error_log: DF.Link | None
new_bom: DF.Link | None new_bom: DF.Link | None
processed_boms: DF.LongText | None processed_boms: DF.LongText | None
status: DF.Literal["Queued", "In Progress", "Completed", "Failed"] status: DF.Literal["Queued", "In Progress", "Completed", "Failed", "Cancelled"]
update_type: DF.Literal["Replace BOM", "Update Cost"] update_type: DF.Literal["Replace BOM", "Update Cost"]
# end: auto-generated types # end: auto-generated types
@@ -64,6 +64,9 @@ class BOMUpdateLog(Document):
self.status = "Queued" self.status = "Queued"
def on_discard(self):
self.db_set("status", "Cancelled")
def validate_boms_are_specified(self): def validate_boms_are_specified(self):
if self.update_type == "Replace BOM" and not (self.current_bom and self.new_bom): if self.update_type == "Replace BOM" and not (self.current_bom and self.new_bom):
frappe.throw( frappe.throw(

View File

@@ -405,7 +405,8 @@
"fieldname": "employee", "fieldname": "employee",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Employee", "label": "Employee",
"options": "Job Card Time Log" "options": "Job Card Time Log",
"read_only": 1
}, },
{ {
"fieldname": "serial_no", "fieldname": "serial_no",
@@ -625,7 +626,7 @@
"grid_page_length": 50, "grid_page_length": 50,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-08-04 15:47:54.514290", "modified": "2025-12-22 14:20:07.817118",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Job Card", "name": "Job Card",

View File

@@ -144,6 +144,9 @@ class JobCard(Document):
self.set_onload("work_order_closed", self.is_work_order_closed()) self.set_onload("work_order_closed", self.is_work_order_closed())
self.set_onload("has_stock_entry", self.has_stock_entry()) self.set_onload("has_stock_entry", self.has_stock_entry())
def on_discard(self):
self.db_set("status", "Cancelled")
def has_stock_entry(self): def has_stock_entry(self):
return frappe.db.exists("Stock Entry", {"job_card": self.name, "docstatus": ["!=", 2]}) return frappe.db.exists("Stock Entry", {"job_card": self.name, "docstatus": ["!=", 2]})
@@ -162,6 +165,7 @@ class JobCard(Document):
self.set_total_completed_qty_from_sub_operations() self.set_total_completed_qty_from_sub_operations()
self.validate_work_order() self.validate_work_order()
self.set_employees()
def on_update(self): def on_update(self):
self.validate_job_card_qty() self.validate_job_card_qty()
@@ -631,11 +635,6 @@ class JobCard(Document):
row = self.append("time_logs", args) row = self.append("time_logs", args)
row.db_update() row.db_update()
def set_employees(self, employees):
for name in employees:
self.append("employee", {"employee": name.get("employee"), "completed_qty": 0.0})
self.save()
def update_sub_operation_status(self): def update_sub_operation_status(self):
if not self.sub_operations: if not self.sub_operations:
return return
@@ -1230,6 +1229,12 @@ class JobCard(Document):
if self.is_work_order_closed(): if self.is_work_order_closed():
frappe.throw(_("You can't make any changes to Job Card since Work Order is closed.")) frappe.throw(_("You can't make any changes to Job Card since Work Order is closed."))
def set_employees(self):
self.employee = []
for item in self.time_logs:
if not any(d.employee == item.employee for d in self.employee):
self.append("employee", {"employee": item.employee, "completed_qty": 0.0})
def is_work_order_closed(self): def is_work_order_closed(self):
if self.work_order: if self.work_order:
status = frappe.get_value("Work Order", self.work_order) status = frappe.get_value("Work Order", self.work_order)
@@ -1285,10 +1290,8 @@ class JobCard(Document):
self.set_status(update_status=update_status) self.set_status(update_status=update_status)
if not self.employee and kwargs.employees:
self.set_employees(kwargs.employees)
self.validate_time_logs(save=True) self.validate_time_logs(save=True)
self.save()
def update_workstation_status(self): def update_workstation_status(self):
status_map = { status_map = {
@@ -1329,17 +1332,15 @@ class JobCard(Document):
kwargs = frappe._dict(kwargs) kwargs = frappe._dict(kwargs)
if kwargs.end_time: if kwargs.end_time:
if kwargs.for_quantity:
self.for_quantity = kwargs.for_quantity
self.add_time_logs( self.add_time_logs(
to_time=kwargs.end_time, to_time=kwargs.end_time,
completed_qty=kwargs.qty, completed_qty=kwargs.qty,
employees=self.employee, employees=self.employee,
sub_operation=kwargs.get("sub_operation"), sub_operation=kwargs.get("sub_operation"),
) )
if kwargs.for_quantity:
self.for_quantity = kwargs.for_quantity
self.save()
else: else:
self.add_time_logs(completed_qty=kwargs.qty, employees=self.employee) self.add_time_logs(completed_qty=kwargs.qty, employees=self.employee)
self.save() self.save()

View File

@@ -119,6 +119,9 @@ class ProductionPlan(Document):
frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"), frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"),
) )
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
self.set_pending_qty_in_row_without_reference() self.set_pending_qty_in_row_without_reference()
self.calculate_total_planned_qty() self.calculate_total_planned_qty()

View File

@@ -143,7 +143,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Status", "label": "Status",
"options": "Planned\nMPS Generated", "options": "Planned\nMPS Generated\nCancelled",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -166,7 +166,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-11-13 16:33:49.682684", "modified": "2025-12-24 11:40:44.263700",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Sales Forecast", "name": "Sales Forecast",

Some files were not shown because too many files have changed in this diff Show More