Merge pull request #51440 from rohitwaghchaure/rc-2

chore: v16 beta RC 2
This commit is contained in:
rohitwaghchaure
2026-01-01 19:01:53 +05:30
committed by GitHub
150 changed files with 24845 additions and 19682 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

@@ -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,16 +97,18 @@
"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

@@ -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);
} }

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

@@ -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

@@ -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

@@ -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

@@ -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",

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

@@ -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

@@ -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,6 +131,7 @@
"write": 1 "write": 1
} }
], ],
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],

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",

View File

@@ -33,9 +33,12 @@ class SalesForecast(Document):
parent_warehouse: DF.Link parent_warehouse: DF.Link
posting_date: DF.Date | None posting_date: DF.Date | None
selected_items: DF.TableMultiSelect[SalesForecastItem] selected_items: DF.TableMultiSelect[SalesForecastItem]
status: DF.Literal["Planned", "MPS Generated"] status: DF.Literal["Planned", "MPS Generated", "Cancelled"]
# 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_demand_qty() self.validate_demand_qty()

View File

@@ -180,6 +180,9 @@ class WorkOrder(Document):
return False return False
def on_discard(self):
self.db_set("status", "Cancelled")
def validate(self): def validate(self):
self.validate_production_item() self.validate_production_item()
if self.bom_no: if self.bom_no:

View File

@@ -454,4 +454,5 @@ erpnext.patches.v16_0.populate_budget_distribution_total
erpnext.patches.v16_0.set_mr_picked_qty erpnext.patches.v16_0.set_mr_picked_qty
erpnext.patches.v16_0.update_tax_withholding_field_in_payment_entry erpnext.patches.v16_0.update_tax_withholding_field_in_payment_entry
erpnext.patches.v16_0.migrate_tax_withholding_data erpnext.patches.v16_0.migrate_tax_withholding_data
erpnext.patches.v16_0.update_corrected_cancelled_status
erpnext.patches.v16_0.fix_barcode_typo

View File

@@ -0,0 +1,7 @@
import frappe
def execute():
frappe.qb.update("Item Barcode").set("barcode_type", "EAN-13").where(
frappe.qb.Field("barcode_type") == "EAN-12"
).run()

View File

@@ -1,6 +1,10 @@
import frappe import frappe
from frappe.utils import add_months, flt, get_first_day, get_last_day from frappe.utils import add_months, flt, get_first_day, get_last_day
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
def execute(): def execute():
remove_old_property_setter() remove_old_property_setter()
@@ -79,6 +83,10 @@ def create_new_budget_from_row(budget_doc, fiscal_year, account_row, percentage_
for field in core_fields: for field in core_fields:
new_budget.set(field, budget_doc.get(field)) new_budget.set(field, budget_doc.get(field))
for fieldname in get_accounting_dimensions():
if budget_doc.get(fieldname):
new_budget.set(fieldname, budget_doc.get(fieldname))
new_budget.from_fiscal_year = fiscal_year.name new_budget.from_fiscal_year = fiscal_year.name
new_budget.to_fiscal_year = fiscal_year.name new_budget.to_fiscal_year = fiscal_year.name
new_budget.budget_start_date = fiscal_year.year_start_date new_budget.budget_start_date = fiscal_year.year_start_date

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
stock_closing_entry = frappe.qb.DocType("Stock Closing Entry")
call_log = frappe.qb.DocType("Call Log")
# updating stock closing entry status to cancelled from canceled
(
frappe.qb.update(stock_closing_entry)
.set(stock_closing_entry.status, "Cancelled")
.where(stock_closing_entry.status == "Canceled")
).run()
# updating call log status to cancelled from canceled
(frappe.qb.update(call_log).set(call_log.status, "Cancelled").where(call_log.status == "Canceled")).run()

View File

@@ -8,6 +8,7 @@ from frappe.tests import IntegrationTestCase
from frappe.utils import add_to_date, now_datetime, nowdate from frappe.utils import add_to_date, now_datetime, nowdate
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.projects.doctype.task.test_task import create_task
from erpnext.projects.doctype.timesheet.timesheet import OverlapError, make_sales_invoice from erpnext.projects.doctype.timesheet.timesheet import OverlapError, make_sales_invoice
from erpnext.setup.doctype.employee.test_employee import make_employee from erpnext.setup.doctype.employee.test_employee import make_employee
from erpnext.tests.utils import ERPNextTestSuite from erpnext.tests.utils import ERPNextTestSuite
@@ -22,6 +23,55 @@ class TestTimesheet(ERPNextTestSuite):
def setUp(self): def setUp(self):
frappe.db.delete("Timesheet") frappe.db.delete("Timesheet")
def test_timesheet_post_update(self):
frappe.get_doc(
{
"doctype": "Property Setter",
"doctype_or_field": "DocField",
"doc_type": "Timesheet",
"field_name": "time_logs",
"property": "allow_on_submit",
"property_type": "Check",
"value": "1",
}
).insert(ignore_permissions=True)
task = create_task("Test Task 1")
timesheet = frappe.new_doc("Timesheet")
timesheet.append(
"time_logs",
{
"task": task.name,
"from_time": now_datetime(),
"to_time": now_datetime() + datetime.timedelta(hours=1),
"company": "_Test Company",
},
)
timesheet.save()
timesheet.submit()
task.reload()
self.assertEqual(task.actual_time, 1)
timesheet.append(
"time_logs",
{
"task": task.name,
"from_time": now_datetime(),
"to_time": now_datetime() + datetime.timedelta(hours=2),
"hours": 2,
},
)
timesheet.save()
task.reload()
self.assertEqual(task.actual_time, 3)
frappe.db.delete(
"Property Setter",
{"doc_type": "Timesheet", "field_name": "time_logs", "property": "allow_on_submit"},
)
def test_timesheet_base_amount(self): def test_timesheet_base_amount(self):
emp = make_employee("test_employee_6@salary.com") emp = make_employee("test_employee_6@salary.com")
timesheet = make_timesheet(emp, simulate=True, is_billable=1) timesheet = make_timesheet(emp, simulate=True, is_billable=1)

View File

@@ -73,6 +73,13 @@ class Timesheet(Document):
self.calculate_percentage_billed() self.calculate_percentage_billed()
self.set_dates() self.set_dates()
def on_discard(self):
self.db_set("status", "Cancelled")
def on_update_after_submit(self):
self.validate_mandatory_fields()
self.update_task_and_project()
def calculate_hours(self): def calculate_hours(self):
for row in self.time_logs: for row in self.time_logs:
if row.to_time and row.from_time: if row.to_time and row.from_time:

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -361,6 +361,21 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
mandatory_depends_on: mandatory_depends_on:
"eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'", "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'",
}, },
{
fieldname: "bank_account",
fieldtype: "Link",
label: "Company Bank Account",
options: "Bank Account",
depends_on: "eval:doc.party",
get_query: function () {
return {
filters: {
is_company_account: 1,
company: this.company,
},
};
},
},
{ {
fieldname: "project", fieldname: "project",
fieldtype: "Link", fieldtype: "Link",
@@ -511,6 +526,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
mode_of_payment: values.mode_of_payment, mode_of_payment: values.mode_of_payment,
project: values.project, project: values.project,
cost_center: values.cost_center, cost_center: values.cost_center,
company_bank_account: values?.bank_account || this?.bank_account,
}, },
callback: (response) => { callback: (response) => {
const alert_string = __("Bank Transaction {0} added as Payment Entry", [ const alert_string = __("Bank Transaction {0} added as Payment Entry", [
@@ -582,6 +598,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
project: values.project, project: values.project,
cost_center: values.cost_center, cost_center: values.cost_center,
allow_edit: true, allow_edit: true,
company_bank_account: values?.bank_account || this?.bank_account,
}, },
callback: (r) => { callback: (r) => {
const doc = frappe.model.sync(r.message); const doc = frappe.model.sync(r.message);

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