mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 00:44:45 +00:00
Merge pull request #52103 from frappe/version-16-hotfix
chore: release v16
This commit is contained in:
50
erpnext/accounts/accounts_dashboard/payments/payments.json
Normal file
50
erpnext/accounts/accounts_dashboard/payments/payments.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"card": "Total Outgoing Bills"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card": "Total Incoming Bills"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card": "Total Incoming Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"card": "Total Outgoing Payment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"charts": [
|
||||||
|
{
|
||||||
|
"chart": "Incoming Bills (Purchase Invoice)",
|
||||||
|
"width": "Half"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chart": "Outgoing Bills (Sales Invoice)",
|
||||||
|
"width": "Half"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chart": "Accounts Receivable Ageing",
|
||||||
|
"width": "Half"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chart": "Accounts Payable Ageing",
|
||||||
|
"width": "Half"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chart": "Bank Balance",
|
||||||
|
"width": "Full"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"creation": "2026-01-26 21:25:12.793893",
|
||||||
|
"dashboard_name": "Payments",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Dashboard",
|
||||||
|
"idx": 0,
|
||||||
|
"is_default": 0,
|
||||||
|
"is_standard": 1,
|
||||||
|
"modified": "2026-01-26 21:25:12.793893",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Payments",
|
||||||
|
"owner": "Administrator"
|
||||||
|
}
|
||||||
@@ -3,9 +3,6 @@
|
|||||||
frappe.provide("erpnext.integrations");
|
frappe.provide("erpnext.integrations");
|
||||||
|
|
||||||
frappe.ui.form.on("Bank", {
|
frappe.ui.form.on("Bank", {
|
||||||
onload: function (frm) {
|
|
||||||
add_fields_to_mapping_table(frm);
|
|
||||||
},
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
add_fields_to_mapping_table(frm);
|
add_fields_to_mapping_table(frm);
|
||||||
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
|
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
|
||||||
@@ -37,11 +34,11 @@ let add_fields_to_mapping_table = function (frm) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
|
||||||
"bank_transaction_field",
|
|
||||||
"options",
|
if (grid) {
|
||||||
options
|
grid.update_docfield_property("bank_transaction_field", "options", options);
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||||
@@ -116,7 +113,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
|||||||
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
|
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
plaid_success(token, response) {
|
plaid_success(token, response) {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class JournalEntry(AccountsController):
|
|||||||
validate_docs_for_deferred_accounting([self.name], [])
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def submit(self):
|
def submit(self):
|
||||||
if len(self.accounts) > 100:
|
if len(self.accounts) > 100 and not self.meta.queue_in_background:
|
||||||
queue_submission(self, "_submit")
|
queue_submission(self, "_submit")
|
||||||
else:
|
else:
|
||||||
return self._submit()
|
return self._submit()
|
||||||
|
|||||||
@@ -400,6 +400,16 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
);
|
);
|
||||||
|
|
||||||
frm.refresh_fields();
|
frm.refresh_fields();
|
||||||
|
|
||||||
|
const party_currency =
|
||||||
|
frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency";
|
||||||
|
|
||||||
|
var reference_grid = frm.fields_dict["references"].grid;
|
||||||
|
["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => {
|
||||||
|
reference_grid.update_docfield_property(fieldname, "options", party_currency);
|
||||||
|
});
|
||||||
|
|
||||||
|
reference_grid.refresh();
|
||||||
},
|
},
|
||||||
|
|
||||||
show_general_ledger: function (frm) {
|
show_general_ledger: function (frm) {
|
||||||
@@ -1104,7 +1114,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
|
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
|
||||||
await frm.call("allocate_amount_to_references", {
|
await frm.call("allocate_amount_to_references", {
|
||||||
paid_amount: paid_amount,
|
paid_amount: flt(paid_amount),
|
||||||
paid_amount_change: paid_amount_change,
|
paid_amount_change: paid_amount_change,
|
||||||
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
|
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -132,6 +132,12 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "due_date",
|
"fieldname": "due_date",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class PaymentLedgerEntry(Document):
|
|||||||
amount_in_account_currency: DF.Currency
|
amount_in_account_currency: DF.Currency
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
cost_center: DF.Link | None
|
cost_center: DF.Link | None
|
||||||
|
project: DF.Link | None
|
||||||
delinked: DF.Check
|
delinked: DF.Check
|
||||||
due_date: DF.Date | None
|
due_date: DF.Date | None
|
||||||
finance_book: DF.Link | None
|
finance_book: DF.Link | None
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ class PaymentReconciliation(Document):
|
|||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
for x in self.dimensions:
|
for x in self.dimensions:
|
||||||
dimension = x.fieldname
|
dimension = x.fieldname
|
||||||
if self.get(dimension):
|
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
|
||||||
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
|
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
|
||||||
|
|
||||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||||
|
|||||||
@@ -116,11 +116,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
|||||||
|
|
||||||
if (cint(doc.update_stock) != 1) {
|
if (cint(doc.update_stock) != 1) {
|
||||||
if (!is_delivered_by_supplier) {
|
if (!is_delivered_by_supplier) {
|
||||||
this.frm.add_custom_button(
|
const should_create_delivery_note = doc.items.some(
|
||||||
__("Delivery Note"),
|
(item) =>
|
||||||
this.frm.cscript["Make Delivery Note"],
|
item.qty - item.delivered_qty > 0 &&
|
||||||
__("Create")
|
!item.scio_detail &&
|
||||||
|
!item.dn_detail &&
|
||||||
|
!item.delivered_by_supplier
|
||||||
);
|
);
|
||||||
|
if (should_create_delivery_note) {
|
||||||
|
this.frm.add_custom_button(
|
||||||
|
__("Delivery Note"),
|
||||||
|
this.frm.cscript["Make Delivery Note"],
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2424,7 +2424,8 @@ def make_delivery_note(source_name, target_doc=None):
|
|||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: doc.delivered_by_supplier != 1
|
"condition": lambda doc: doc.delivered_by_supplier != 1
|
||||||
and not doc.scio_detail
|
and not doc.scio_detail
|
||||||
and not doc.dn_detail,
|
and not doc.dn_detail
|
||||||
|
and doc.qty - doc.delivered_qty > 0,
|
||||||
},
|
},
|
||||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||||
"Sales Team": {
|
"Sales Team": {
|
||||||
|
|||||||
@@ -1947,6 +1947,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
|||||||
account=gle.account,
|
account=gle.account,
|
||||||
party_type=gle.party_type,
|
party_type=gle.party_type,
|
||||||
party=gle.party,
|
party=gle.party,
|
||||||
|
project=gle.project,
|
||||||
cost_center=gle.cost_center,
|
cost_center=gle.cost_center,
|
||||||
finance_book=gle.finance_book,
|
finance_book=gle.finance_book,
|
||||||
due_date=gle.due_date,
|
due_date=gle.due_date,
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
"for_user": "",
|
"for_user": "",
|
||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
"icon": "accounting",
|
"icon": "accounting",
|
||||||
"idx": 3,
|
"idx": 4,
|
||||||
"indicator_color": "",
|
"indicator_color": "",
|
||||||
"is_hidden": 0,
|
"is_hidden": 0,
|
||||||
"label": "Accounting",
|
"label": "Invoicing",
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -587,10 +587,10 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-12-24 13:20:34.857205",
|
"modified": "2026-01-23 11:05:47.246213",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Invoicing",
|
||||||
"number_cards": [
|
"number_cards": [
|
||||||
{
|
{
|
||||||
"label": "Outgoing Bills",
|
"label": "Outgoing Bills",
|
||||||
@@ -617,6 +617,6 @@
|
|||||||
"roles": [],
|
"roles": [],
|
||||||
"sequence_id": 2.0,
|
"sequence_id": 2.0,
|
||||||
"shortcuts": [],
|
"shortcuts": [],
|
||||||
"title": "Accounting",
|
"title": "Invoicing",
|
||||||
"type": "Workspace"
|
"type": "Workspace"
|
||||||
}
|
}
|
||||||
@@ -116,14 +116,6 @@ frappe.ui.form.on("Asset", {
|
|||||||
__("Manage")
|
__("Manage")
|
||||||
);
|
);
|
||||||
|
|
||||||
frm.add_custom_button(
|
|
||||||
__("Repair Asset"),
|
|
||||||
function () {
|
|
||||||
frm.trigger("create_asset_repair");
|
|
||||||
},
|
|
||||||
__("Manage")
|
|
||||||
);
|
|
||||||
|
|
||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Split Asset"),
|
__("Split Asset"),
|
||||||
function () {
|
function () {
|
||||||
@@ -155,6 +147,14 @@ frappe.ui.form.on("Asset", {
|
|||||||
},
|
},
|
||||||
__("Manage")
|
__("Manage")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frm.add_custom_button(
|
||||||
|
__("Repair Asset"),
|
||||||
|
function () {
|
||||||
|
frm.trigger("create_asset_repair");
|
||||||
|
},
|
||||||
|
__("Manage")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frm.doc.calculate_depreciation) {
|
if (!frm.doc.calculate_depreciation) {
|
||||||
|
|||||||
@@ -244,6 +244,8 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
if self.is_composite_asset and not has_active_capitalization(self.name):
|
if self.is_composite_asset and not has_active_capitalization(self.name):
|
||||||
|
if self.split_from and has_active_capitalization(self.split_from):
|
||||||
|
return
|
||||||
frappe.throw(_("Please capitalize this asset before submitting."))
|
frappe.throw(_("Please capitalize this asset before submitting."))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
|||||||
@@ -1691,6 +1691,71 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))
|
self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))
|
||||||
|
|
||||||
|
def test_split_asset_created_via_capitalization(self):
|
||||||
|
"""Test that assets created via Asset Capitalization can be split without capitalization error"""
|
||||||
|
from erpnext.assets.doctype.asset_capitalization.test_asset_capitalization import (
|
||||||
|
create_asset_capitalization,
|
||||||
|
create_asset_capitalization_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure test data exists
|
||||||
|
create_asset_capitalization_data()
|
||||||
|
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
set_depreciation_settings_in_company(company=company)
|
||||||
|
name = frappe.db.get_value(
|
||||||
|
"Asset Category Account",
|
||||||
|
filters={"parent": "Computers", "company_name": company},
|
||||||
|
fieldname=["name"],
|
||||||
|
)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||||
|
|
||||||
|
stock_rate = 1000
|
||||||
|
stock_qty = 2
|
||||||
|
total_amount = 2000
|
||||||
|
|
||||||
|
# Create composite asset
|
||||||
|
wip_composite_asset = create_asset(
|
||||||
|
asset_name="Asset Capitalization WIP Composite Asset for Split",
|
||||||
|
is_composite_asset=1,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
company=company,
|
||||||
|
asset_quantity=2, # Set quantity > 1 to allow splitting
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create and submit Asset Capitalization
|
||||||
|
asset_capitalization = create_asset_capitalization(
|
||||||
|
target_asset=wip_composite_asset.name,
|
||||||
|
stock_qty=stock_qty,
|
||||||
|
stock_rate=stock_rate,
|
||||||
|
company=company,
|
||||||
|
submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify asset was capitalized
|
||||||
|
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||||
|
self.assertEqual(target_asset.net_purchase_amount, total_amount)
|
||||||
|
self.assertEqual(target_asset.status, "Work In Progress")
|
||||||
|
|
||||||
|
# Submit the capitalized asset
|
||||||
|
target_asset.submit()
|
||||||
|
self.assertEqual(target_asset.status, "Submitted")
|
||||||
|
|
||||||
|
# Split the asset - this should work without capitalization error
|
||||||
|
split_qty = 1
|
||||||
|
splitted_asset = split_asset(target_asset.name, split_qty)
|
||||||
|
|
||||||
|
# Verify split asset was created and submitted successfully
|
||||||
|
self.assertIsNotNone(splitted_asset)
|
||||||
|
self.assertEqual(splitted_asset.asset_quantity, split_qty)
|
||||||
|
self.assertEqual(splitted_asset.split_from, target_asset.name)
|
||||||
|
self.assertEqual(splitted_asset.docstatus, 1) # Should be submitted
|
||||||
|
self.assertEqual(splitted_asset.status, "Submitted")
|
||||||
|
|
||||||
|
# Verify original asset was updated
|
||||||
|
target_asset.reload()
|
||||||
|
self.assertEqual(target_asset.asset_quantity, 1) # Remaining quantity
|
||||||
|
|
||||||
|
|
||||||
def get_gl_entries(doctype, docname):
|
def get_gl_entries(doctype, docname):
|
||||||
gl_entry = frappe.qb.DocType("GL Entry")
|
gl_entry = frappe.qb.DocType("GL Entry")
|
||||||
|
|||||||
@@ -574,13 +574,19 @@ class AssetCapitalization(StockController):
|
|||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
net_purchase_amount = asset_doc.net_purchase_amount - total_target_asset_value
|
net_purchase_amount = asset_doc.net_purchase_amount - total_target_asset_value
|
||||||
purchase_amount = asset_doc.purchase_amount - total_target_asset_value
|
purchase_amount = asset_doc.purchase_amount - total_target_asset_value
|
||||||
asset_doc.db_set("total_asset_cost", asset_doc.total_asset_cost - total_target_asset_value)
|
total_asset_cost = asset_doc.total_asset_cost - total_target_asset_value
|
||||||
else:
|
else:
|
||||||
net_purchase_amount = asset_doc.net_purchase_amount + total_target_asset_value
|
net_purchase_amount = asset_doc.net_purchase_amount + total_target_asset_value
|
||||||
purchase_amount = asset_doc.purchase_amount + total_target_asset_value
|
purchase_amount = asset_doc.purchase_amount + total_target_asset_value
|
||||||
|
total_asset_cost = asset_doc.total_asset_cost + total_target_asset_value
|
||||||
|
|
||||||
asset_doc.db_set("net_purchase_amount", net_purchase_amount)
|
asset_doc.db_set(
|
||||||
asset_doc.db_set("purchase_amount", purchase_amount)
|
{
|
||||||
|
"net_purchase_amount": net_purchase_amount,
|
||||||
|
"purchase_amount": purchase_amount,
|
||||||
|
"total_asset_cost": total_asset_cost,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("Asset {0} has been updated. Please set the depreciation details if any and submit it.").format(
|
_("Asset {0} has been updated. Please set the depreciation details if any and submit it.").format(
|
||||||
|
|||||||
@@ -212,7 +212,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
party = filters.get("customer") or filters.get("supplier")
|
party = filters.get("customer") or filters.get("supplier")
|
||||||
item_rules_list = frappe.get_all(
|
item_rules_list = frappe.get_all(
|
||||||
"Party Specific Item",
|
"Party Specific Item",
|
||||||
filters={"party": party},
|
filters={
|
||||||
|
"party": ["!=", party],
|
||||||
|
"party_type": "Customer" if filters.get("customer") else "Supplier",
|
||||||
|
},
|
||||||
fields=["restrict_based_on", "based_on_value"],
|
fields=["restrict_based_on", "based_on_value"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -226,7 +229,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
||||||
|
|
||||||
for filter in filters_dict:
|
for filter in filters_dict:
|
||||||
filters[scrub(filter)] = ["in", filters_dict[filter]]
|
filters[scrub(filter)] = ["not in", filters_dict[filter]]
|
||||||
|
|
||||||
if filters.get("customer"):
|
if filters.get("customer"):
|
||||||
del filters["customer"]
|
del filters["customer"]
|
||||||
|
|||||||
@@ -552,7 +552,7 @@ class StockController(AccountsController):
|
|||||||
if is_rejected:
|
if is_rejected:
|
||||||
serial_nos = row.get("rejected_serial_no")
|
serial_nos = row.get("rejected_serial_no")
|
||||||
type_of_transaction = "Inward" if not self.is_return else "Outward"
|
type_of_transaction = "Inward" if not self.is_return else "Outward"
|
||||||
qty = row.get("rejected_qty")
|
qty = row.get("rejected_qty") * row.get("conversion_factor", 1.0)
|
||||||
warehouse = row.get("rejected_warehouse")
|
warehouse = row.get("rejected_warehouse")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -166,29 +166,46 @@ class SubcontractingController(StockController):
|
|||||||
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
|
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.doctype != "Subcontracting Receipt" and item.qty > flt(
|
if self.doctype != "Subcontracting Receipt":
|
||||||
get_pending_subcontracted_quantity(
|
order_item_doctype = (
|
||||||
self.doctype,
|
|
||||||
self.purchase_order if self.doctype == "Subcontracting Order" else self.sales_order,
|
|
||||||
).get(
|
|
||||||
item.purchase_order_item
|
|
||||||
if self.doctype == "Subcontracting Order"
|
|
||||||
else item.sales_order_item
|
|
||||||
)
|
|
||||||
/ item.subcontracting_conversion_factor,
|
|
||||||
frappe.get_precision(
|
|
||||||
"Purchase Order Item"
|
"Purchase Order Item"
|
||||||
if self.doctype == "Subcontracting Order"
|
if self.doctype == "Subcontracting Order"
|
||||||
else "Sales Order Item",
|
else "Sales Order Item"
|
||||||
"qty",
|
|
||||||
),
|
|
||||||
):
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
|
|
||||||
).format(item.idx, item.item_name)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
order_name = (
|
||||||
|
self.purchase_order if self.doctype == "Subcontracting Order" else self.sales_order
|
||||||
|
)
|
||||||
|
order_item_field = frappe.scrub(order_item_doctype)
|
||||||
|
|
||||||
|
if not item.get(order_item_field):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: Item {1} must be linked to a {2}.").format(
|
||||||
|
item.idx, item.item_name, order_item_doctype
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pending_qty = flt(
|
||||||
|
flt(
|
||||||
|
get_pending_subcontracted_quantity(
|
||||||
|
order_item_doctype,
|
||||||
|
order_name,
|
||||||
|
).get(item.get(order_item_field))
|
||||||
|
)
|
||||||
|
/ item.subcontracting_conversion_factor,
|
||||||
|
frappe.get_precision(
|
||||||
|
order_item_doctype,
|
||||||
|
"qty",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.qty > pending_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Row {0}: Item {1}'s quantity cannot be higher than the available quantity."
|
||||||
|
).format(item.idx, item.item_name)
|
||||||
|
)
|
||||||
|
|
||||||
if self.doctype != "Subcontracting Inward Order":
|
if self.doctype != "Subcontracting Inward Order":
|
||||||
item.amount = item.qty * item.rate
|
item.amount = item.qty * item.rate
|
||||||
|
|
||||||
@@ -1333,9 +1350,7 @@ def get_item_details(items):
|
|||||||
|
|
||||||
|
|
||||||
def get_pending_subcontracted_quantity(doctype, name):
|
def get_pending_subcontracted_quantity(doctype, name):
|
||||||
table = frappe.qb.DocType(
|
table = frappe.qb.DocType(doctype)
|
||||||
"Purchase Order Item" if doctype == "Subcontracting Order" else "Sales Order Item"
|
|
||||||
)
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.select(table.name, table.stock_qty, table.subcontracted_qty)
|
.select(table.name, table.stock_qty, table.subcontracted_qty)
|
||||||
|
|||||||
@@ -720,6 +720,7 @@ class SubcontractingInwardController:
|
|||||||
item.db_set("scio_detail", scio_rm.name)
|
item.db_set("scio_detail", scio_rm.name)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
precision = self.precision("customer_provided_item_cost", "items")
|
||||||
result = frappe.get_all(
|
result = frappe.get_all(
|
||||||
"Subcontracting Inward Order Received Item",
|
"Subcontracting Inward Order Received Item",
|
||||||
filters={
|
filters={
|
||||||
@@ -734,10 +735,17 @@ class SubcontractingInwardController:
|
|||||||
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
|
table = frappe.qb.DocType("Subcontracting Inward Order Received Item")
|
||||||
case_expr_qty, case_expr_rate = Case(), Case()
|
case_expr_qty, case_expr_rate = Case(), Case()
|
||||||
for d in result:
|
for d in result:
|
||||||
d.received_qty += (
|
current_qty = flt(data[d.name].transfer_qty) * (1 if self._action == "submit" else -1)
|
||||||
data[d.name].transfer_qty if self._action == "submit" else -data[d.name].transfer_qty
|
current_rate = flt(data[d.name].rate)
|
||||||
|
|
||||||
|
# Calculate weighted average rate
|
||||||
|
old_total = d.rate * d.received_qty
|
||||||
|
current_total = current_rate * current_qty
|
||||||
|
|
||||||
|
d.received_qty = d.received_qty + current_qty
|
||||||
|
d.rate = (
|
||||||
|
flt((old_total + current_total) / d.received_qty, precision) if d.received_qty else 0.0
|
||||||
)
|
)
|
||||||
d.rate += data[d.name].rate if self._action == "submit" else -data[d.name].rate
|
|
||||||
|
|
||||||
if not d.required_qty and not d.received_qty:
|
if not d.required_qty and not d.received_qty:
|
||||||
deleted_docs.append(d.name)
|
deleted_docs.append(d.name)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2025-11-17 20:55:11.854086",
|
"creation": "2026-01-27 17:02:43.440221",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desktop Icon",
|
"doctype": "Desktop Icon",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"icon": "accounting",
|
"icon": "accounting",
|
||||||
"icon_type": "Link",
|
"icon_type": "Folder",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"label": "Accounting",
|
"label": "Accounting",
|
||||||
"link_to": "Accounting",
|
"link_to": "",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-01 20:07:01.203651",
|
"modified": "2026-01-27 17:04:04.351402",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"standard": 1
|
"standard": 1
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"app": "erpnext",
|
|
||||||
"creation": "2025-11-12 13:07:51.988728",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Desktop Icon",
|
|
||||||
"hidden": 0,
|
|
||||||
"icon_type": "Folder",
|
|
||||||
"idx": 1,
|
|
||||||
"label": "Accounts",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"logo_url": "",
|
|
||||||
"modified": "2025-11-17 17:39:36.915358",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"name": "Accounts",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"roles": [],
|
|
||||||
"standard": 1
|
|
||||||
}
|
|
||||||
21
erpnext/desktop_icon/accounts_setup.json
Normal file
21
erpnext/desktop_icon/accounts_setup.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"app": "erpnext",
|
||||||
|
"bg_color": "blue",
|
||||||
|
"creation": "2026-01-27 17:37:55.824821",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Desktop Icon",
|
||||||
|
"hidden": 0,
|
||||||
|
"icon_type": "Link",
|
||||||
|
"idx": 3,
|
||||||
|
"label": "Accounts Setup",
|
||||||
|
"link_to": "Accounts Setup",
|
||||||
|
"link_type": "Workspace Sidebar",
|
||||||
|
"modified": "2026-01-27 18:34:57.092350",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Accounts Setup",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent_icon": "Accounting",
|
||||||
|
"restrict_removal": 0,
|
||||||
|
"roles": [],
|
||||||
|
"standard": 1
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Banking",
|
"name": "Banking",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"standard": 1
|
"standard": 1
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2025-11-10 16:54:04.780644",
|
"creation": "2026-01-23 11:00:23.272751",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desktop Icon",
|
"doctype": "Desktop Icon",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"icon": "expenses",
|
"icon": "expenses",
|
||||||
"icon_type": "Link",
|
"icon_type": "Link",
|
||||||
"idx": 4,
|
"idx": 6,
|
||||||
"label": "Budget",
|
"label": "Budget",
|
||||||
"link_to": "Budget",
|
"link_to": "Budget",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-01 20:07:01.449176",
|
"modified": "2026-01-23 14:39:30.839274",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Budget",
|
"name": "Budget",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"standard": 1
|
"standard": 1
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2025-11-17 20:55:11.772622",
|
"creation": "2026-01-23 11:00:23.250819",
|
||||||
"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": 0,
|
"idx": 2,
|
||||||
"label": "Financial Reports",
|
"label": "Financial Reports",
|
||||||
"link_to": "Financial Reports",
|
"link_to": "Financial Reports",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-01 20:07:01.253367",
|
"modified": "2026-01-23 14:38:46.479759",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Financial Reports",
|
"name": "Financial Reports",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"sidebar": "",
|
"sidebar": "",
|
||||||
|
|||||||
21
erpnext/desktop_icon/invoicing.json
Normal file
21
erpnext/desktop_icon/invoicing.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"app": "erpnext",
|
||||||
|
"creation": "2026-01-23 10:51:05.799725",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Desktop Icon",
|
||||||
|
"hidden": 0,
|
||||||
|
"icon": "accounting",
|
||||||
|
"icon_type": "Link",
|
||||||
|
"idx": 0,
|
||||||
|
"label": "Invoicing",
|
||||||
|
"link_to": "Invoicing",
|
||||||
|
"link_type": "Workspace Sidebar",
|
||||||
|
"modified": "2026-01-23 15:17:23.564795",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Invoicing",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent_icon": "Accounting",
|
||||||
|
"restrict_removal": 0,
|
||||||
|
"roles": [],
|
||||||
|
"standard": 1
|
||||||
|
}
|
||||||
22
erpnext/desktop_icon/payments.json
Normal file
22
erpnext/desktop_icon/payments.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"app": "erpnext",
|
||||||
|
"bg_color": "blue",
|
||||||
|
"creation": "2026-01-27 17:37:55.866525",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Desktop Icon",
|
||||||
|
"hidden": 0,
|
||||||
|
"icon": "receipt-text",
|
||||||
|
"icon_type": "Link",
|
||||||
|
"idx": 1,
|
||||||
|
"label": "Payments",
|
||||||
|
"link_to": "Payments",
|
||||||
|
"link_type": "Workspace Sidebar",
|
||||||
|
"modified": "2026-01-27 18:31:59.617181",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Payments",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent_icon": "Accounting",
|
||||||
|
"restrict_removal": 0,
|
||||||
|
"roles": [],
|
||||||
|
"standard": 1
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2026-01-12 12:31:53.444807",
|
"creation": "2026-01-23 11:00:23.303554",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desktop Icon",
|
"doctype": "Desktop Icon",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"icon_type": "Link",
|
"icon_type": "Link",
|
||||||
"idx": 8,
|
"idx": 7,
|
||||||
"label": "Share Management",
|
"label": "Share Management",
|
||||||
"link_to": "Share Management",
|
"link_to": "Share Management",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-12 12:31:53.444807",
|
"modified": "2026-01-23 14:39:34.128991",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Share Management",
|
"name": "Share Management",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"standard": 1
|
"standard": 1
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2025-11-10 16:14:25.976756",
|
"creation": "2026-01-23 11:00:23.344237",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desktop Icon",
|
"doctype": "Desktop Icon",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"icon": "monitor-check",
|
"icon": "monitor-check",
|
||||||
"icon_type": "Link",
|
"icon_type": "Link",
|
||||||
"idx": 99,
|
"idx": 8,
|
||||||
"label": "Subscription",
|
"label": "Subscription",
|
||||||
"link_to": "Subscription",
|
"link_to": "Subscription",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-01 20:07:01.548581",
|
"modified": "2026-01-23 14:39:37.830722",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Subscription",
|
"name": "Subscription",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"standard": 1
|
"standard": 1
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": "erpnext",
|
"app": "erpnext",
|
||||||
"creation": "2025-11-12 15:05:54.474218",
|
"creation": "2026-01-23 11:00:23.262357",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Desktop Icon",
|
"doctype": "Desktop Icon",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"icon": "book-text",
|
"icon": "book-text",
|
||||||
"icon_type": "Link",
|
"icon_type": "Link",
|
||||||
"idx": 3,
|
"idx": 4,
|
||||||
"label": "Taxes",
|
"label": "Taxes",
|
||||||
"link_to": "Taxes",
|
"link_to": "Taxes",
|
||||||
"link_type": "Workspace Sidebar",
|
"link_type": "Workspace Sidebar",
|
||||||
"modified": "2026-01-01 20:07:01.356333",
|
"modified": "2026-01-23 14:39:25.636166",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Taxes",
|
"name": "Taxes",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"parent_icon": "Accounts",
|
"parent_icon": "Accounting",
|
||||||
"restrict_removal": 0,
|
"restrict_removal": 0,
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"sidebar": "",
|
"sidebar": "",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -697,8 +697,6 @@ var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) {
|
|||||||
do_not_explode: d.do_not_explode,
|
do_not_explode: d.do_not_explode,
|
||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
d = locals[cdt][cdn];
|
|
||||||
|
|
||||||
$.extend(d, r.message);
|
$.extend(d, r.message);
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
refresh_field("scrap_items");
|
refresh_field("scrap_items");
|
||||||
|
|||||||
@@ -1547,6 +1547,9 @@ def add_operating_cost_component_wise(
|
|||||||
if job_card and job_card.operation_id != row.name:
|
if job_card and job_card.operation_id != row.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not row.actual_operation_time:
|
||||||
|
continue
|
||||||
|
|
||||||
workstation_cost = frappe.get_all(
|
workstation_cost = frappe.get_all(
|
||||||
"Workstation Cost",
|
"Workstation Cost",
|
||||||
fields=["operating_component", "operating_cost"],
|
fields=["operating_component", "operating_cost"],
|
||||||
@@ -1609,7 +1612,7 @@ def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_
|
|||||||
job_card=job_card,
|
job_card=job_card,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not cost_added:
|
if not cost_added and not job_card:
|
||||||
stock_entry.append(
|
stock_entry.append(
|
||||||
"additional_costs",
|
"additional_costs",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3725,6 +3725,53 @@ class TestWorkOrder(IntegrationTestCase):
|
|||||||
wo = make_wo_order_test_record(item="Top Level Parent")
|
wo = make_wo_order_test_record(item="Top Level Parent")
|
||||||
self.assertEqual([item.item_code for item in wo.required_items], expected)
|
self.assertEqual([item.item_code for item in wo.required_items], expected)
|
||||||
|
|
||||||
|
def test_reserved_qty_for_pp_with_extra_material_transfer(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
|
||||||
|
make_stock_entry as make_stock_entry_test_record,
|
||||||
|
)
|
||||||
|
|
||||||
|
rm_item_code = make_item(
|
||||||
|
"_Test Reserved Qty PP Item",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
fg_item_code = make_item(
|
||||||
|
"_Test Reserved Qty PP FG Item",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry_test_record(
|
||||||
|
item_code=rm_item_code, target="_Test Warehouse - _TC", qty=10, basic_rate=100
|
||||||
|
)
|
||||||
|
|
||||||
|
make_bom(
|
||||||
|
item=fg_item_code,
|
||||||
|
raw_materials=[rm_item_code],
|
||||||
|
)
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
item=fg_item_code,
|
||||||
|
qty=1,
|
||||||
|
source_warehouse="_Test Warehouse - _TC",
|
||||||
|
skip_transfer=0,
|
||||||
|
target_warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
bin1_at_completion = get_bin(rm_item_code, "_Test Warehouse - _TC")
|
||||||
|
self.assertEqual(bin1_at_completion.reserved_qty_for_production, 1)
|
||||||
|
|
||||||
|
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 1))
|
||||||
|
s.items[0].qty += 2 # extra material transfer
|
||||||
|
s.submit()
|
||||||
|
|
||||||
|
bin1_at_completion = get_bin(rm_item_code, "_Test Warehouse - _TC")
|
||||||
|
|
||||||
|
self.assertEqual(bin1_at_completion.reserved_qty_for_production, 0)
|
||||||
|
|
||||||
|
|
||||||
def get_reserved_entries(voucher_no, warehouse=None):
|
def get_reserved_entries(voucher_no, warehouse=None):
|
||||||
doctype = frappe.qb.DocType("Stock Reservation Entry")
|
doctype = frappe.qb.DocType("Stock Reservation Entry")
|
||||||
|
|||||||
@@ -770,6 +770,7 @@ class WorkOrder(Document):
|
|||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
|
|
||||||
self.on_close_or_cancel()
|
self.on_close_or_cancel()
|
||||||
|
self.delete_job_card()
|
||||||
|
|
||||||
def on_close_or_cancel(self):
|
def on_close_or_cancel(self):
|
||||||
if self.production_plan and frappe.db.exists(
|
if self.production_plan and frappe.db.exists(
|
||||||
@@ -779,7 +780,6 @@ class WorkOrder(Document):
|
|||||||
else:
|
else:
|
||||||
self.update_work_order_qty_in_so()
|
self.update_work_order_qty_in_so()
|
||||||
|
|
||||||
self.delete_job_card()
|
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
@@ -2654,6 +2654,9 @@ def get_reserved_qty_for_production(
|
|||||||
qty_field = wo_item.required_qty
|
qty_field = wo_item.required_qty
|
||||||
else:
|
else:
|
||||||
qty_field = Case()
|
qty_field = Case()
|
||||||
|
qty_field = qty_field.when(
|
||||||
|
((wo.skip_transfer == 0) & (wo_item.transferred_qty > wo_item.required_qty)), 0.0
|
||||||
|
)
|
||||||
qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
|
qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
|
||||||
qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty)
|
qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty)
|
||||||
|
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ def send_project_update_email_to_users(project):
|
|||||||
"sent": 0,
|
"sent": 0,
|
||||||
"date": today(),
|
"date": today(),
|
||||||
"time": nowtime(),
|
"time": nowtime(),
|
||||||
"naming_series": "UPDATE-.project.-.YY.MM.DD.-",
|
"naming_series": "UPDATE-.project.-.YY.MM.DD.-.####",
|
||||||
}
|
}
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 928 B After Width: | Height: | Size: 928 B |
@@ -637,6 +637,12 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
tax_count ? this.frm.doc["taxes"][tax_count - 1].total + grand_total_diff : this.frm.doc.net_total
|
tax_count ? this.frm.doc["taxes"][tax_count - 1].total + grand_total_diff : this.frm.doc.net_total
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// total taxes and charges is calculated before adjusting base grand total
|
||||||
|
this.frm.doc.total_taxes_and_charges = flt(
|
||||||
|
this.frm.doc.grand_total - this.frm.doc.net_total - grand_total_diff,
|
||||||
|
precision("total_taxes_and_charges")
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(
|
["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(
|
||||||
this.frm.doc.doctype
|
this.frm.doc.doctype
|
||||||
@@ -679,11 +685,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frm.doc.total_taxes_and_charges = flt(
|
|
||||||
this.frm.doc.grand_total - this.frm.doc.net_total - grand_total_diff,
|
|
||||||
precision("total_taxes_and_charges")
|
|
||||||
);
|
|
||||||
|
|
||||||
this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges"]);
|
this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges"]);
|
||||||
|
|
||||||
// Round grand total as per precision
|
// Round grand total as per precision
|
||||||
|
|||||||
@@ -3131,10 +3131,16 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
|
|
||||||
set_warehouse() {
|
set_warehouse() {
|
||||||
this.autofill_warehouse(this.frm.doc.items, "warehouse", this.frm.doc.set_warehouse);
|
this.autofill_warehouse(this.frm.doc.items, "warehouse", this.frm.doc.set_warehouse);
|
||||||
|
this.autofill_warehouse(this.frm.doc.packed_items, "warehouse", this.frm.doc.set_warehouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_target_warehouse() {
|
set_target_warehouse() {
|
||||||
this.autofill_warehouse(this.frm.doc.items, "target_warehouse", this.frm.doc.set_target_warehouse);
|
this.autofill_warehouse(this.frm.doc.items, "target_warehouse", this.frm.doc.set_target_warehouse);
|
||||||
|
this.autofill_warehouse(
|
||||||
|
this.frm.doc.packed_items,
|
||||||
|
"target_warehouse",
|
||||||
|
this.frm.doc.set_target_warehouse
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_from_warehouse() {
|
set_from_warehouse() {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
frappe.provide("erpnext.financial_statements");
|
frappe.provide("erpnext.financial_statements");
|
||||||
|
|
||||||
|
function get_filter_value(filter_name) {
|
||||||
|
// not warn when the filter is missing
|
||||||
|
return frappe.query_report.get_filter_value(filter_name, false);
|
||||||
|
}
|
||||||
|
|
||||||
erpnext.financial_statements = {
|
erpnext.financial_statements = {
|
||||||
filters: get_filters(),
|
filters: get_filters(),
|
||||||
baseData: null,
|
baseData: null,
|
||||||
|
|
||||||
get_pdf_format: function (report, custom_format) {
|
get_pdf_format: function (report, custom_format) {
|
||||||
// If report template is selected, use default pdf formatting
|
// If report template is selected, use default pdf formatting
|
||||||
return report.get_filter_value("report_template") ? null : custom_format;
|
return get_filter_value("report_template") ? null : custom_format;
|
||||||
},
|
},
|
||||||
|
|
||||||
formatter: function (value, row, column, data, default_formatter, filter) {
|
formatter: function (value, row, column, data, default_formatter, filter) {
|
||||||
@@ -15,14 +20,14 @@ erpnext.financial_statements = {
|
|||||||
if (erpnext.financial_statements._is_special_view(column, data))
|
if (erpnext.financial_statements._is_special_view(column, data))
|
||||||
return erpnext.financial_statements._format_special_view(...report_params);
|
return erpnext.financial_statements._format_special_view(...report_params);
|
||||||
|
|
||||||
if (frappe.query_report.get_filter_value("report_template"))
|
if (get_filter_value("report_template"))
|
||||||
return erpnext.financial_statements._format_custom_report(...report_params);
|
return erpnext.financial_statements._format_custom_report(...report_params);
|
||||||
else return erpnext.financial_statements._format_standard_report(...report_params);
|
else return erpnext.financial_statements._format_standard_report(...report_params);
|
||||||
},
|
},
|
||||||
|
|
||||||
_is_special_view: function (column, data) {
|
_is_special_view: function (column, data) {
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
const view = frappe.query_report.get_filter_value("selected_view");
|
const view = get_filter_value("selected_view");
|
||||||
return (view === "Growth" && column.colIndex >= 3) || (view === "Margin" && column.colIndex >= 2);
|
return (view === "Growth" && column.colIndex >= 3) || (view === "Margin" && column.colIndex >= 2);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,7 +105,7 @@ erpnext.financial_statements = {
|
|||||||
from_date: formatting.from_date || formatting.period_start_date,
|
from_date: formatting.from_date || formatting.period_start_date,
|
||||||
to_date: formatting.to_date || formatting.period_end_date,
|
to_date: formatting.to_date || formatting.period_end_date,
|
||||||
account_type: formatting.account_type,
|
account_type: formatting.account_type,
|
||||||
company: frappe.query_report.get_filter_value("company"),
|
company: get_filter_value("company"),
|
||||||
};
|
};
|
||||||
|
|
||||||
column.link_onclick =
|
column.link_onclick =
|
||||||
@@ -177,7 +182,7 @@ erpnext.financial_statements = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_format_special_view: function (value, row, column, data, default_formatter) {
|
_format_special_view: function (value, row, column, data, default_formatter) {
|
||||||
const selectedView = frappe.query_report.get_filter_value("selected_view");
|
const selectedView = get_filter_value("selected_view");
|
||||||
|
|
||||||
if (selectedView === "Growth") {
|
if (selectedView === "Growth") {
|
||||||
const growthPercent = data[column.fieldname];
|
const growthPercent = data[column.fieldname];
|
||||||
@@ -252,7 +257,7 @@ erpnext.financial_statements = {
|
|||||||
|
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
account: data.account || data.accounts,
|
account: data.account || data.accounts,
|
||||||
company: frappe.query_report.get_filter_value("company"),
|
company: get_filter_value("company"),
|
||||||
from_date: data.from_date || data.year_start_date,
|
from_date: data.from_date || data.year_start_date,
|
||||||
to_date: data.to_date || data.year_end_date,
|
to_date: data.to_date || data.year_end_date,
|
||||||
project: project && project.length > 0 ? project[0].get_value() : "",
|
project: project && project.length > 0 ? project[0].get_value() : "",
|
||||||
@@ -305,17 +310,49 @@ erpnext.financial_statements = {
|
|||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Balance Sheet"), function () {
|
report.page.add_custom_menu_item(views_menu, __("Balance Sheet"), function () {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Balance Sheet", { company: filters.company });
|
frappe.set_route("query-report", "Balance Sheet", {
|
||||||
|
company: filters.company,
|
||||||
|
filter_based_on: filters.filter_based_on,
|
||||||
|
period_start_date: filters.period_start_date,
|
||||||
|
period_end_date: filters.period_end_date,
|
||||||
|
from_fiscal_year: filters.from_fiscal_year,
|
||||||
|
to_fiscal_year: filters.to_fiscal_year,
|
||||||
|
periodicity: filters.periodicity,
|
||||||
|
presentation_currency: filters.presentation_currency,
|
||||||
|
cost_center: filters.cost_center,
|
||||||
|
project: filters.project,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Profit and Loss"), function () {
|
report.page.add_custom_menu_item(views_menu, __("Profit and Loss"), function () {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Profit and Loss Statement", { company: filters.company });
|
frappe.set_route("query-report", "Profit and Loss Statement", {
|
||||||
|
company: filters.company,
|
||||||
|
filter_based_on: filters.filter_based_on,
|
||||||
|
period_start_date: filters.period_start_date,
|
||||||
|
period_end_date: filters.period_end_date,
|
||||||
|
from_fiscal_year: filters.from_fiscal_year,
|
||||||
|
to_fiscal_year: filters.to_fiscal_year,
|
||||||
|
periodicity: filters.periodicity,
|
||||||
|
presentation_currency: filters.presentation_currency,
|
||||||
|
cost_center: filters.cost_center,
|
||||||
|
project: filters.project,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
report.page.add_custom_menu_item(views_menu, __("Cash Flow Statement"), function () {
|
report.page.add_custom_menu_item(views_menu, __("Cash Flow Statement"), function () {
|
||||||
var filters = report.get_values();
|
var filters = report.get_values();
|
||||||
frappe.set_route("query-report", "Cash Flow", { company: filters.company });
|
frappe.set_route("query-report", "Cash Flow", {
|
||||||
|
company: filters.company,
|
||||||
|
filter_based_on: filters.filter_based_on,
|
||||||
|
period_start_date: filters.period_start_date,
|
||||||
|
period_end_date: filters.period_end_date,
|
||||||
|
from_fiscal_year: filters.from_fiscal_year,
|
||||||
|
to_fiscal_year: filters.to_fiscal_year,
|
||||||
|
periodicity: filters.periodicity,
|
||||||
|
cost_center: filters.cost_center,
|
||||||
|
project: filters.project,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -345,7 +382,7 @@ function get_filters() {
|
|||||||
default: ["Fiscal Year"],
|
default: ["Fiscal Year"],
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
on_change: function () {
|
on_change: function () {
|
||||||
let filter_based_on = frappe.query_report.get_filter_value("filter_based_on");
|
let filter_based_on = get_filter_value("filter_based_on");
|
||||||
frappe.query_report.toggle_filter_display(
|
frappe.query_report.toggle_filter_display(
|
||||||
"from_fiscal_year",
|
"from_fiscal_year",
|
||||||
filter_based_on === "Date Range"
|
filter_based_on === "Date Range"
|
||||||
@@ -422,7 +459,7 @@ function get_filters() {
|
|||||||
fieldtype: "MultiSelectList",
|
fieldtype: "MultiSelectList",
|
||||||
get_data: function (txt) {
|
get_data: function (txt) {
|
||||||
return frappe.db.get_link_options("Cost Center", txt, {
|
return frappe.db.get_link_options("Cost Center", txt, {
|
||||||
company: frappe.query_report.get_filter_value("company"),
|
company: get_filter_value("company"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
options: "Cost Center",
|
options: "Cost Center",
|
||||||
@@ -433,7 +470,7 @@ function get_filters() {
|
|||||||
fieldtype: "MultiSelectList",
|
fieldtype: "MultiSelectList",
|
||||||
get_data: function (txt) {
|
get_data: function (txt) {
|
||||||
return frappe.db.get_link_options("Project", txt, {
|
return frappe.db.get_link_options("Project", txt, {
|
||||||
company: frappe.query_report.get_filter_value("company"),
|
company: get_filter_value("company"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
options: "Project",
|
options: "Project",
|
||||||
|
|||||||
@@ -495,7 +495,30 @@ erpnext.sales_common = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project() {
|
project(doc, cdt, cdn) {
|
||||||
|
if (!cdt || !cdn) {
|
||||||
|
if (this.frm.doc.project) {
|
||||||
|
$.each(this.frm.doc["items"] || [], function (i, item) {
|
||||||
|
if (!item.project) {
|
||||||
|
frappe.model.set_value(item.doctype, item.name, "project", doc.project);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const item = frappe.get_doc(cdt, cdn);
|
||||||
|
if (item.project) {
|
||||||
|
$.each(this.frm.doc["items"] || [], function (i, other_item) {
|
||||||
|
if (!other_item.project) {
|
||||||
|
frappe.model.set_value(
|
||||||
|
other_item.doctype,
|
||||||
|
other_item.name,
|
||||||
|
"project",
|
||||||
|
item.project
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
let me = this;
|
let me = this;
|
||||||
if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) {
|
if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) {
|
||||||
if (this.frm.doc.project) {
|
if (this.frm.doc.project) {
|
||||||
|
|||||||
4
erpnext/regional/address_template/templates/sweden.html
Normal file
4
erpnext/regional/address_template/templates/sweden.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{{ address_line1 }}<br>
|
||||||
|
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
|
||||||
|
{{ pincode }} {{ city | upper }}<br>
|
||||||
|
{{ country | upper }}
|
||||||
@@ -183,6 +183,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Customer Group",
|
"label": "Customer Group",
|
||||||
|
"link_filters": "[[\"Customer Group\", \"is_group\", \"=\", 0]]",
|
||||||
"oldfieldname": "customer_group",
|
"oldfieldname": "customer_group",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Customer Group",
|
"options": "Customer Group",
|
||||||
@@ -625,7 +626,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2026-01-16 15:56:05.967663",
|
"modified": "2026-01-21 17:23:42.151114",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class Customer(TransactionBase):
|
|||||||
set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||||
|
|
||||||
def get_customer_name(self):
|
def get_customer_name(self):
|
||||||
|
self.customer_name = self.customer_name.strip()
|
||||||
if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
|
if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
|
||||||
count = frappe.db.sql(
|
count = frappe.db.sql(
|
||||||
"""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
|
"""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ class TestPartySpecificItem(IntegrationTestCase):
|
|||||||
items = item_query(
|
items = item_query(
|
||||||
doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters=filters, as_dict=False
|
doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters=filters, as_dict=False
|
||||||
)
|
)
|
||||||
for item in items:
|
self.assertTrue(self.item.name in flatten(items))
|
||||||
self.assertEqual(item[0], self.item.name)
|
|
||||||
|
|
||||||
def test_item_query_for_supplier(self):
|
def test_item_query_for_supplier(self):
|
||||||
create_party_specific_item(
|
create_party_specific_item(
|
||||||
@@ -49,5 +48,14 @@ class TestPartySpecificItem(IntegrationTestCase):
|
|||||||
items = item_query(
|
items = item_query(
|
||||||
doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters=filters, as_dict=False
|
doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters=filters, as_dict=False
|
||||||
)
|
)
|
||||||
for item in items:
|
self.assertTrue(self.item.item_group in flatten(items))
|
||||||
self.assertEqual(item[2], self.item.item_group)
|
|
||||||
|
|
||||||
|
def flatten(lst):
|
||||||
|
result = []
|
||||||
|
for item in lst:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
result.extend(flatten(item))
|
||||||
|
else:
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Customer Group",
|
"label": "Default Customer Group",
|
||||||
|
"link_filters": "[[\"Customer Group\", \"is_group\", \"=\", 0]]",
|
||||||
"options": "Customer Group"
|
"options": "Customer Group"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -297,7 +298,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-01-02 18:17:05.734945",
|
"modified": "2026-01-21 17:28:37.027837",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Algeria": {
|
"Algeria": {
|
||||||
"Algeria VAT 17%": {
|
"Algeria TVA 19%": {
|
||||||
"account_name": "VAT 17%",
|
"account_name": "TVA 19%",
|
||||||
"tax_rate": 17.00,
|
"tax_rate": 19.00,
|
||||||
"default": 1
|
"default": 1
|
||||||
},
|
},
|
||||||
"Algeria VAT 7%": {
|
"Algeria TVA 9%": {
|
||||||
"account_name": "VAT 7%",
|
"account_name": "TVA 9%",
|
||||||
"tax_rate": 7.00
|
"tax_rate": 9.00
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
for ledger in entries:
|
for ledger in entries:
|
||||||
self.stock_value_differece[ledger.batch_no] += flt(ledger.batch_value)
|
self.stock_value_differece[ledger.batch_no] += flt(ledger.batch_value)
|
||||||
self.available_qty[ledger.batch_no] += flt(ledger.batch_qty)
|
self.available_qty[ledger.batch_no] += flt(ledger.batch_qty)
|
||||||
self.total_qty[ledger.batch_no] += flt(ledger.batch_qty)
|
|
||||||
|
|
||||||
@deprecated(
|
@deprecated(
|
||||||
"erpnext.stock.serial_batch_bundle.BatchNoValuation.get_sle_for_batches",
|
"erpnext.stock.serial_batch_bundle.BatchNoValuation.get_sle_for_batches",
|
||||||
@@ -271,7 +270,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
batch_data = query.run(as_dict=True)
|
batch_data = query.run(as_dict=True)
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
self.total_qty[d.batch_no] += flt(d.batch_qty)
|
|
||||||
|
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
if self.available_qty.get(d.batch_no):
|
if self.available_qty.get(d.batch_no):
|
||||||
@@ -383,7 +381,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
batch_data = query.run(as_dict=True)
|
batch_data = query.run(as_dict=True)
|
||||||
for d in batch_data:
|
for d in batch_data:
|
||||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||||
self.total_qty[d.batch_no] += flt(d.batch_qty)
|
|
||||||
|
|
||||||
if not self.last_sle:
|
if not self.last_sle:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -232,8 +232,25 @@ class Item(Document):
|
|||||||
cint(frappe.get_single_value("Stock Settings", "clean_description_html"))
|
cint(frappe.get_single_value("Stock Settings", "clean_description_html"))
|
||||||
and self.description != self.item_name # perf: Avoid cleaning up a fallback
|
and self.description != self.item_name # perf: Avoid cleaning up a fallback
|
||||||
):
|
):
|
||||||
|
old_desc = self.description
|
||||||
self.description = clean_html(self.description)
|
self.description = clean_html(self.description)
|
||||||
|
|
||||||
|
if (
|
||||||
|
old_desc
|
||||||
|
and self.description
|
||||||
|
and "<img src" in old_desc
|
||||||
|
and "<img src" not in self.description
|
||||||
|
):
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
'Image in the description has been removed. To disable this behavior, uncheck "{0}" in {1}.'
|
||||||
|
).format(
|
||||||
|
frappe.get_meta("Stock Settings").get_label("clean_description_html"),
|
||||||
|
get_link_to_form("Stock Settings"),
|
||||||
|
),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
|
||||||
def validate_customer_provided_part(self):
|
def validate_customer_provided_part(self):
|
||||||
if self.is_customer_provided_item:
|
if self.is_customer_provided_item:
|
||||||
if self.is_purchase_item:
|
if self.is_purchase_item:
|
||||||
|
|||||||
@@ -282,7 +282,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "set_warehouse",
|
"fieldname": "set_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Set Target Warehouse",
|
"label": "Set Target Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
@@ -378,7 +377,7 @@
|
|||||||
"idx": 70,
|
"idx": 70,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-01-10 15:34:59.000603",
|
"modified": "2026-01-21 12:48:40.792323",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Material Request",
|
"name": "Material Request",
|
||||||
|
|||||||
@@ -4997,6 +4997,45 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
|||||||
|
|
||||||
self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])
|
self.assertEqual(frappe.parse_json(stock_queue), [[20, 0.0]])
|
||||||
|
|
||||||
|
def test_negative_stock_error_for_purchase_return(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Negative Stock for Purchase Return Item",
|
||||||
|
{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TNSFPRI.#####"},
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
posting_date=add_days(today(), -3),
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
posting_date=add_days(today(), -4),
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
source="_Test Warehouse - _TC",
|
||||||
|
target="_Test Warehouse 1 - _TC",
|
||||||
|
batch_no=batch_no,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return_pr = make_return_doc("Purchase Receipt", pr.name)
|
||||||
|
self.assertRaises(frappe.ValidationError, return_pr.submit)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -576,14 +576,12 @@ class SerialandBatchBundle(Document):
|
|||||||
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
|
d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no)))
|
||||||
|
|
||||||
precision = d.precision("qty")
|
precision = d.precision("qty")
|
||||||
for field in ["available_qty", "total_qty"]:
|
available_qty = flt(sn_obj.available_qty.get(d.batch_no), precision)
|
||||||
value = getattr(sn_obj, field)
|
if self.docstatus == 1:
|
||||||
available_qty = flt(value.get(d.batch_no), precision)
|
available_qty += flt(d.qty, precision)
|
||||||
if self.docstatus == 1:
|
|
||||||
available_qty += flt(d.qty, precision)
|
|
||||||
|
|
||||||
if not allow_negative_stock:
|
if not allow_negative_stock:
|
||||||
self.validate_negative_batch(d.batch_no, available_qty, field)
|
self.validate_negative_batch(d.batch_no, available_qty)
|
||||||
|
|
||||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||||
|
|
||||||
@@ -596,8 +594,8 @@ class SerialandBatchBundle(Document):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_negative_batch(self, batch_no, available_qty, field=None):
|
def validate_negative_batch(self, batch_no, available_qty):
|
||||||
if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty, field=field):
|
if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty):
|
||||||
msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)}
|
msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)}
|
||||||
has negative stock
|
has negative stock
|
||||||
of quantity {bold(available_qty)} in the
|
of quantity {bold(available_qty)} in the
|
||||||
@@ -605,7 +603,7 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
frappe.throw(_(msg), BatchNegativeStockError)
|
frappe.throw(_(msg), BatchNegativeStockError)
|
||||||
|
|
||||||
def is_stock_reco_for_valuation_adjustment(self, available_qty, field=None):
|
def is_stock_reco_for_valuation_adjustment(self, available_qty):
|
||||||
if (
|
if (
|
||||||
self.voucher_type == "Stock Reconciliation"
|
self.voucher_type == "Stock Reconciliation"
|
||||||
and self.type_of_transaction == "Outward"
|
and self.type_of_transaction == "Outward"
|
||||||
@@ -613,7 +611,6 @@ class SerialandBatchBundle(Document):
|
|||||||
and (
|
and (
|
||||||
abs(frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty"))
|
abs(frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty"))
|
||||||
== abs(available_qty)
|
== abs(available_qty)
|
||||||
or field == "total_qty"
|
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
@@ -1344,6 +1341,7 @@ class SerialandBatchBundle(Document):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_docstatus()
|
self.validate_docstatus()
|
||||||
self.validate_serial_nos_inventory()
|
self.validate_serial_nos_inventory()
|
||||||
|
self.validate_batch_quantity()
|
||||||
|
|
||||||
def validate_docstatus(self):
|
def validate_docstatus(self):
|
||||||
for row in self.entries:
|
for row in self.entries:
|
||||||
@@ -1437,6 +1435,106 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.validate_voucher_no_docstatus()
|
self.validate_voucher_no_docstatus()
|
||||||
|
self.validate_batch_quantity()
|
||||||
|
|
||||||
|
def validate_batch_quantity(self):
|
||||||
|
if not self.has_batch_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.type_of_transaction != "Outward" or (
|
||||||
|
self.voucher_type == "Stock Reconciliation" and self.type_of_transaction == "Outward"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
batch_wise_available_qty = self.get_batchwise_available_qty()
|
||||||
|
precision = frappe.get_precision("Serial and Batch Entry", "qty")
|
||||||
|
|
||||||
|
for d in self.entries:
|
||||||
|
available_qty = batch_wise_available_qty.get(d.batch_no, 0)
|
||||||
|
if flt(available_qty, precision) < 0:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"""
|
||||||
|
The Batch {0} of an item {1} has negative stock in the warehouse {2}. Please add a stock quantity of {3} to proceed with this entry."""
|
||||||
|
).format(
|
||||||
|
bold(d.batch_no),
|
||||||
|
bold(self.item_code),
|
||||||
|
bold(self.warehouse),
|
||||||
|
bold(abs(flt(available_qty, precision))),
|
||||||
|
),
|
||||||
|
title=_("Negative Stock Error"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_batchwise_available_qty(self):
|
||||||
|
available_qty = self.get_available_qty_from_sabb()
|
||||||
|
available_qty_from_ledger = self.get_available_qty_from_stock_ledger()
|
||||||
|
|
||||||
|
if not available_qty_from_ledger:
|
||||||
|
return available_qty
|
||||||
|
|
||||||
|
for batch_no, qty in available_qty_from_ledger.items():
|
||||||
|
if batch_no in available_qty:
|
||||||
|
available_qty[batch_no] += qty
|
||||||
|
else:
|
||||||
|
available_qty[batch_no] = qty
|
||||||
|
|
||||||
|
return available_qty
|
||||||
|
|
||||||
|
def get_available_qty_from_stock_ledger(self):
|
||||||
|
batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
|
|
||||||
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(sle)
|
||||||
|
.select(
|
||||||
|
sle.batch_no,
|
||||||
|
Sum(sle.actual_qty).as_("available_qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(sle.item_code == self.item_code)
|
||||||
|
& (sle.warehouse == self.warehouse)
|
||||||
|
& (sle.is_cancelled == 0)
|
||||||
|
& (sle.batch_no.isin(batches))
|
||||||
|
& (sle.docstatus == 1)
|
||||||
|
& (sle.serial_and_batch_bundle.isnull())
|
||||||
|
& (sle.batch_no.isnotnull())
|
||||||
|
)
|
||||||
|
.for_update()
|
||||||
|
.groupby(sle.batch_no)
|
||||||
|
)
|
||||||
|
|
||||||
|
res = query.run(as_list=True)
|
||||||
|
|
||||||
|
return frappe._dict(res) if res else frappe._dict()
|
||||||
|
|
||||||
|
def get_available_qty_from_sabb(self):
|
||||||
|
batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
|
|
||||||
|
child = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(child)
|
||||||
|
.select(
|
||||||
|
child.batch_no,
|
||||||
|
Sum(child.qty).as_("available_qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(child.item_code == self.item_code)
|
||||||
|
& (child.warehouse == self.warehouse)
|
||||||
|
& (child.is_cancelled == 0)
|
||||||
|
& (child.batch_no.isin(batches))
|
||||||
|
& (child.docstatus == 1)
|
||||||
|
& (child.type_of_transaction.isin(["Inward", "Outward"]))
|
||||||
|
)
|
||||||
|
.for_update()
|
||||||
|
.groupby(child.batch_no)
|
||||||
|
)
|
||||||
|
query = query.where(child.voucher_type != "Pick List")
|
||||||
|
|
||||||
|
res = query.run(as_list=True)
|
||||||
|
|
||||||
|
return frappe._dict(res) if res else frappe._dict()
|
||||||
|
|
||||||
def validate_voucher_no_docstatus(self):
|
def validate_voucher_no_docstatus(self):
|
||||||
if self.voucher_type == "POS Invoice":
|
if self.voucher_type == "POS Invoice":
|
||||||
|
|||||||
@@ -261,9 +261,9 @@ frappe.ui.form.on("Shipment", {
|
|||||||
frappe.db.get_value(
|
frappe.db.get_value(
|
||||||
"User",
|
"User",
|
||||||
{ name: frappe.session.user },
|
{ name: frappe.session.user },
|
||||||
["full_name", "last_name", "email", "phone", "mobile_no"],
|
["full_name", "email", "phone", "mobile_no"],
|
||||||
(r) => {
|
(r) => {
|
||||||
if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
|
if (!(r.full_name && r.email && (r.phone || r.mobile_no))) {
|
||||||
if (delivery_type == "Delivery") {
|
if (delivery_type == "Delivery") {
|
||||||
frm.set_value("delivery_company", "");
|
frm.set_value("delivery_company", "");
|
||||||
frm.set_value("delivery_contact", "");
|
frm.set_value("delivery_contact", "");
|
||||||
@@ -272,9 +272,9 @@ frappe.ui.form.on("Shipment", {
|
|||||||
frm.set_value("pickup_contact", "");
|
frm.set_value("pickup_contact", "");
|
||||||
}
|
}
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") +
|
__("Full Name, Email or Phone/Mobile of the user are mandatory to continue.") +
|
||||||
"</br>" +
|
"</br>" +
|
||||||
__("Please first set Last Name, Email and Phone for the user") +
|
__("Please first set Full Name, Email and Phone for the user") +
|
||||||
` <a href="/app/user/${frappe.session.user}">${frappe.session.user}</a>`
|
` <a href="/app/user/${frappe.session.user}">${frappe.session.user}</a>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,9 +278,30 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
|
|
||||||
self.validate_closed_subcontracting_order()
|
self.validate_closed_subcontracting_order()
|
||||||
self.validate_subcontract_order()
|
self.validate_subcontract_order()
|
||||||
|
self.validate_raw_materials_exists()
|
||||||
|
|
||||||
super().validate_subcontracting_inward()
|
super().validate_subcontracting_inward()
|
||||||
|
|
||||||
|
def validate_raw_materials_exists(self):
|
||||||
|
if self.purpose not in ["Manufacture", "Repack", "Disassemble"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
|
||||||
|
return
|
||||||
|
|
||||||
|
raw_materials = []
|
||||||
|
for row in self.items:
|
||||||
|
if row.s_warehouse:
|
||||||
|
raw_materials.append(row.item_code)
|
||||||
|
|
||||||
|
if not raw_materials:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"At least one raw material item must be present in the stock entry for the type {0}"
|
||||||
|
).format(bold(self.purpose)),
|
||||||
|
title=_("Raw Materials Missing"),
|
||||||
|
)
|
||||||
|
|
||||||
def set_serial_batch_for_disassembly(self):
|
def set_serial_batch_for_disassembly(self):
|
||||||
if self.purpose != "Disassemble":
|
if self.purpose != "Disassemble":
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2362,6 +2362,28 @@ class TestStockEntry(IntegrationTestCase):
|
|||||||
self.assertEqual(target_sabb.entries[0].batch_no, batch)
|
self.assertEqual(target_sabb.entries[0].batch_no, batch)
|
||||||
self.assertEqual([entry.serial_no for entry in target_sabb.entries], serial_nos[:2])
|
self.assertEqual([entry.serial_no for entry in target_sabb.entries], serial_nos[:2])
|
||||||
|
|
||||||
|
def test_raw_material_missing_validation(self):
|
||||||
|
original_value = frappe.db.get_single_value("Manufacturing Settings", "material_consumption")
|
||||||
|
frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0)
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(
|
||||||
|
item_code="_Test Item",
|
||||||
|
qty=1,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entry.purpose = "Manufacture"
|
||||||
|
stock_entry.stock_entry_type = "Manufacture"
|
||||||
|
stock_entry.items[0].is_finished_item = 1
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError,
|
||||||
|
stock_entry.save,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Manufacturing Settings", "material_consumption", original_value)
|
||||||
|
|
||||||
@IntegrationTestCase.change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Manufacturing Settings",
|
"Manufacturing Settings",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -666,6 +666,16 @@ class StockReconciliation(StockController):
|
|||||||
validate_is_stock_item,
|
validate_is_stock_item,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_serial_batch_items():
|
||||||
|
has_batch_no, has_serial_no = frappe.get_value(
|
||||||
|
"Item", item_code, ["has_batch_no", "has_serial_no"]
|
||||||
|
)
|
||||||
|
if row.use_serial_batch_fields and self.purpose == "Stock Reconciliation":
|
||||||
|
if has_batch_no and not row.batch_no:
|
||||||
|
raise frappe.ValidationError(_("Please enter Batch No"))
|
||||||
|
if has_serial_no and not row.serial_no:
|
||||||
|
raise frappe.ValidationError(_("Please enter Serial No"))
|
||||||
|
|
||||||
# using try except to catch all validation msgs and display together
|
# using try except to catch all validation msgs and display together
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -674,12 +684,13 @@ class StockReconciliation(StockController):
|
|||||||
# end of life and stock item
|
# end of life and stock item
|
||||||
validate_end_of_life(item_code, item.end_of_life, item.disabled)
|
validate_end_of_life(item_code, item.end_of_life, item.disabled)
|
||||||
validate_is_stock_item(item_code, item.is_stock_item)
|
validate_is_stock_item(item_code, item.is_stock_item)
|
||||||
|
validate_serial_batch_items()
|
||||||
|
|
||||||
# docstatus should be < 2
|
# docstatus should be < 2
|
||||||
validate_cancelled_item(item_code, item.docstatus)
|
validate_cancelled_item(item_code, item.docstatus)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.validation_messages.append(_("Row #") + " " + ("%d: " % (row.idx)) + cstr(e))
|
self.validation_messages.append(_("Row #") + ("%d: " % (row.idx)) + cstr(e))
|
||||||
|
|
||||||
def validate_reserved_stock(self) -> None:
|
def validate_reserved_stock(self) -> None:
|
||||||
"""Raises an exception if there is any reserved stock for the items in the Stock Reconciliation."""
|
"""Raises an exception if there is any reserved stock for the items in the Stock Reconciliation."""
|
||||||
|
|||||||
@@ -1450,6 +1450,7 @@ class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
|||||||
qty=10,
|
qty=10,
|
||||||
rate=100,
|
rate=100,
|
||||||
use_serial_batch_fields=1,
|
use_serial_batch_fields=1,
|
||||||
|
purpose="Opening Stock",
|
||||||
)
|
)
|
||||||
|
|
||||||
sr.reload()
|
sr.reload()
|
||||||
@@ -1592,6 +1593,7 @@ class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
|||||||
qty=10,
|
qty=10,
|
||||||
rate=80,
|
rate=80,
|
||||||
use_serial_batch_fields=1,
|
use_serial_batch_fields=1,
|
||||||
|
purpose="Opening Stock",
|
||||||
)
|
)
|
||||||
|
|
||||||
batch_no = get_batch_from_bundle(reco.items[0].serial_and_batch_bundle)
|
batch_no = get_batch_from_bundle(reco.items[0].serial_and_batch_bundle)
|
||||||
@@ -1676,6 +1678,7 @@ class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
|||||||
qty=10,
|
qty=10,
|
||||||
rate=100,
|
rate=100,
|
||||||
use_serial_batch_fields=1,
|
use_serial_batch_fields=1,
|
||||||
|
purpose="Opening Stock",
|
||||||
)
|
)
|
||||||
|
|
||||||
sr.reload()
|
sr.reload()
|
||||||
|
|||||||
@@ -439,8 +439,10 @@ def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> It
|
|||||||
if not ctx.uom:
|
if not ctx.uom:
|
||||||
if ctx.doctype in sales_doctypes:
|
if ctx.doctype in sales_doctypes:
|
||||||
ctx.uom = item.sales_uom if item.sales_uom else item.stock_uom
|
ctx.uom = item.sales_uom if item.sales_uom else item.stock_uom
|
||||||
elif (ctx.doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]) or (
|
elif (
|
||||||
ctx.doctype == "Material Request" and ctx.material_request_type == "Purchase"
|
(ctx.doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"])
|
||||||
|
or (ctx.doctype == "Material Request" and ctx.material_request_type == "Purchase")
|
||||||
|
or (ctx.doctype == "Supplier Quotation")
|
||||||
):
|
):
|
||||||
ctx.uom = item.purchase_uom if item.purchase_uom else item.stock_uom
|
ctx.uom = item.purchase_uom if item.purchase_uom else item.stock_uom
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -807,62 +807,11 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
for ledger in entries:
|
for ledger in entries:
|
||||||
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
|
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
|
||||||
self.available_qty[ledger.batch_no] += flt(ledger.qty)
|
self.available_qty[ledger.batch_no] += flt(ledger.qty)
|
||||||
self.total_qty[ledger.batch_no] += flt(ledger.qty)
|
|
||||||
|
|
||||||
entries = self.get_batch_stock_after_date()
|
|
||||||
for row in entries:
|
|
||||||
self.total_qty[row.batch_no] += flt(row.total_qty)
|
|
||||||
|
|
||||||
self.calculate_avg_rate_from_deprecarated_ledgers()
|
self.calculate_avg_rate_from_deprecarated_ledgers()
|
||||||
self.calculate_avg_rate_for_non_batchwise_valuation()
|
self.calculate_avg_rate_for_non_batchwise_valuation()
|
||||||
self.set_stock_value_difference()
|
self.set_stock_value_difference()
|
||||||
|
|
||||||
def get_batch_stock_after_date(self) -> list[dict]:
|
|
||||||
# Get total qty of each batch no from Serial and Batch Bundle without checking time condition
|
|
||||||
if not self.batchwise_valuation_batches:
|
|
||||||
return []
|
|
||||||
|
|
||||||
child = frappe.qb.DocType("Serial and Batch Entry")
|
|
||||||
|
|
||||||
timestamp_condition = ""
|
|
||||||
if self.sle.posting_datetime:
|
|
||||||
timestamp_condition = child.posting_datetime > self.sle.posting_datetime
|
|
||||||
|
|
||||||
if self.sle.creation:
|
|
||||||
timestamp_condition |= (child.posting_datetime == self.sle.posting_datetime) & (
|
|
||||||
child.creation > self.sle.creation
|
|
||||||
)
|
|
||||||
|
|
||||||
query = (
|
|
||||||
frappe.qb.from_(child)
|
|
||||||
.select(
|
|
||||||
child.batch_no,
|
|
||||||
Sum(child.qty).as_("total_qty"),
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
(child.item_code == self.sle.item_code)
|
|
||||||
& (child.warehouse == self.sle.warehouse)
|
|
||||||
& (child.batch_no.isin(self.batchwise_valuation_batches))
|
|
||||||
& (child.docstatus == 1)
|
|
||||||
& (child.type_of_transaction.isin(["Inward", "Outward"]))
|
|
||||||
)
|
|
||||||
.for_update()
|
|
||||||
.groupby(child.batch_no)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
|
|
||||||
if self.sle.voucher_detail_no:
|
|
||||||
query = query.where(child.voucher_detail_no != self.sle.voucher_detail_no)
|
|
||||||
elif self.sle.voucher_no:
|
|
||||||
query = query.where(child.voucher_no != self.sle.voucher_no)
|
|
||||||
|
|
||||||
query = query.where(child.voucher_type != "Pick List")
|
|
||||||
|
|
||||||
if timestamp_condition:
|
|
||||||
query = query.where(timestamp_condition)
|
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
|
||||||
|
|
||||||
def get_batch_stock_before_date(self) -> list[dict]:
|
def get_batch_stock_before_date(self) -> list[dict]:
|
||||||
# Get batch wise stock value difference from Serial and Batch Bundle considering time condition
|
# Get batch wise stock value difference from Serial and Batch Bundle considering time condition
|
||||||
if not self.batchwise_valuation_batches:
|
if not self.batchwise_valuation_batches:
|
||||||
|
|||||||
@@ -51,6 +51,49 @@ class IntegrationTestSubcontractingInwardOrder(IntegrationTestCase):
|
|||||||
for item in rm_in.get("items"):
|
for item in rm_in.get("items"):
|
||||||
self.assertEqual(item.customer_provided_item_cost, 15)
|
self.assertEqual(item.customer_provided_item_cost, 15)
|
||||||
|
|
||||||
|
def test_customer_provided_item_cost_with_multiple_receipts(self):
|
||||||
|
"""
|
||||||
|
Validate that rate is calculated correctly (Weighted Average) when multiple receipts
|
||||||
|
occur for the same SCIO Received Item.
|
||||||
|
"""
|
||||||
|
so, scio = create_so_scio()
|
||||||
|
rm_item = "Basic RM"
|
||||||
|
|
||||||
|
# Receipt 1: 5 Qty @ Unit Cost 10
|
||||||
|
rm_in_1 = frappe.new_doc("Stock Entry").update(scio.make_rm_stock_entry_inward())
|
||||||
|
rm_in_1.items = [item for item in rm_in_1.items if item.item_code == rm_item]
|
||||||
|
rm_in_1.items[0].qty = 5
|
||||||
|
rm_in_1.items[0].basic_rate = 10
|
||||||
|
rm_in_1.items[0].transfer_qty = 5
|
||||||
|
rm_in_1.submit()
|
||||||
|
|
||||||
|
scio.reload()
|
||||||
|
received_item = next(item for item in scio.received_items if item.rm_item_code == rm_item)
|
||||||
|
self.assertEqual(received_item.rate, 10)
|
||||||
|
|
||||||
|
# Receipt 2: 5 Qty @ Unit Cost 20
|
||||||
|
rm_in_2 = frappe.new_doc("Stock Entry").update(scio.make_rm_stock_entry_inward())
|
||||||
|
rm_in_2.items = [item for item in rm_in_2.items if item.item_code == rm_item]
|
||||||
|
rm_in_2.items[0].qty = 5
|
||||||
|
rm_in_2.items[0].basic_rate = 20
|
||||||
|
rm_in_2.items[0].transfer_qty = 5
|
||||||
|
rm_in_2.save()
|
||||||
|
rm_in_2.submit()
|
||||||
|
|
||||||
|
# Check 2: Rate should be Weighted Average
|
||||||
|
# (5 * 10 + 5 * 20) / 10 = 150 / 10 = 15
|
||||||
|
scio.reload()
|
||||||
|
received_item = next(item for item in scio.received_items if item.rm_item_code == rm_item)
|
||||||
|
self.assertEqual(received_item.rate, 15)
|
||||||
|
|
||||||
|
# Cancel Receipt 2: Rate should revert to original
|
||||||
|
# (15 * 10 - 20 * 5) / 5 = 50 / 5 = 10
|
||||||
|
rm_in_2.cancel()
|
||||||
|
scio.reload()
|
||||||
|
received_item = next(item for item in scio.received_items if item.rm_item_code == rm_item)
|
||||||
|
self.assertEqual(received_item.received_qty, 5)
|
||||||
|
self.assertEqual(received_item.rate, 10)
|
||||||
|
|
||||||
def test_add_extra_customer_provided_item(self):
|
def test_add_extra_customer_provided_item(self):
|
||||||
so, scio = create_so_scio()
|
so, scio = create_so_scio()
|
||||||
|
|
||||||
|
|||||||
287
erpnext/workspace_sidebar/accounts_setup.json
Normal file
287
erpnext/workspace_sidebar/accounts_setup.json
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
{
|
||||||
|
"app": "erpnext",
|
||||||
|
"creation": "2026-01-23 14:36:51.659571",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Workspace Sidebar",
|
||||||
|
"header_icon": "database",
|
||||||
|
"idx": 1,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "database",
|
||||||
|
"indent": 1,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Setup",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Chart of Accounts",
|
||||||
|
"link_to": "Account",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Chart of Cost Centers",
|
||||||
|
"link_to": "Cost Center",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Account Category",
|
||||||
|
"link_to": "Account Category",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Accounting Dimension",
|
||||||
|
"link_to": "Accounting Dimension",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Currency",
|
||||||
|
"link_to": "Currency",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Currency Exchange",
|
||||||
|
"link_to": "Currency Exchange",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Finance Book",
|
||||||
|
"link_to": "Finance Book",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Mode of Payment",
|
||||||
|
"link_to": "Mode of Payment",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payment Term",
|
||||||
|
"link_to": "Payment Term",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Journal Entry Template",
|
||||||
|
"link_to": "Journal Entry Template",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Terms and Conditions",
|
||||||
|
"link_to": "Terms and Conditions",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Company",
|
||||||
|
"link_to": "Company",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Fiscal Year",
|
||||||
|
"link_to": "Fiscal Year",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "lock-keyhole-open",
|
||||||
|
"indent": 1,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Opening & Closing",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "COA Importer",
|
||||||
|
"link_to": "Chart of Accounts Importer",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Opening Invoice Tool",
|
||||||
|
"link_to": "Opening Invoice Creation Tool",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Accounting Period",
|
||||||
|
"link_to": "Accounting Period",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "FX Revaluation",
|
||||||
|
"link_to": "Exchange Rate Revaluation",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Period Closing Voucher",
|
||||||
|
"link_to": "Period Closing Voucher",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "settings",
|
||||||
|
"indent": 1,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Settings",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Accounts Settings",
|
||||||
|
"link_to": "Accounts Settings",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Currency Exchange Settings",
|
||||||
|
"link_to": "Currency Exchange Settings",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Repost Accounting Ledger Settings",
|
||||||
|
"link_to": "Repost Accounting Ledger Settings",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2026-01-23 16:43:27.989825",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Accounts Setup",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"standard": 1,
|
||||||
|
"title": "Accounts Setup"
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
"creation": "2025-10-26 21:53:46.303793",
|
"creation": "2025-10-26 21:53:46.303793",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Workspace Sidebar",
|
"doctype": "Workspace Sidebar",
|
||||||
|
"for_user": "",
|
||||||
"header_icon": "accounting",
|
"header_icon": "accounting",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"child": 0,
|
"child": 0,
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
"indent": 0,
|
"indent": 0,
|
||||||
"keep_closed": 0,
|
"keep_closed": 0,
|
||||||
"label": "Home",
|
"label": "Home",
|
||||||
"link_to": "Accounting",
|
"link_to": "Invoicing",
|
||||||
"link_type": "Workspace",
|
"link_type": "Workspace",
|
||||||
"show_arrow": 0,
|
"show_arrow": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
@@ -30,6 +31,18 @@
|
|||||||
"show_arrow": 0,
|
"show_arrow": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "list-tree",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Chart of Accounts",
|
||||||
|
"link_to": "Account",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"child": 0,
|
"child": 0,
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -151,7 +164,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"icon": "money-coins-1",
|
"icon": "money-coins-1",
|
||||||
"indent": 1,
|
"indent": 1,
|
||||||
"keep_closed": 1,
|
"keep_closed": 0,
|
||||||
"label": "Payments",
|
"label": "Payments",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"show_arrow": 0,
|
"show_arrow": 0,
|
||||||
@@ -261,7 +274,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"icon": "sheet",
|
"icon": "sheet",
|
||||||
"indent": 1,
|
"indent": 1,
|
||||||
"keep_closed": 1,
|
"keep_closed": 0,
|
||||||
"label": "Reports",
|
"label": "Reports",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"show_arrow": 0,
|
"show_arrow": 0,
|
||||||
@@ -299,283 +312,13 @@
|
|||||||
"link_type": "Workspace",
|
"link_type": "Workspace",
|
||||||
"show_arrow": 0,
|
"show_arrow": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 0,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "lock-keyhole-open",
|
|
||||||
"indent": 1,
|
|
||||||
"keep_closed": 1,
|
|
||||||
"label": "Opening & Closing",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "COA Importer",
|
|
||||||
"link_to": "Chart of Accounts Importer",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Opening Invoice Tool",
|
|
||||||
"link_to": "Opening Invoice Creation Tool",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Accounting Period",
|
|
||||||
"link_to": "Accounting Period",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "FX Revaluation",
|
|
||||||
"link_to": "Exchange Rate Revaluation",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Period Closing Voucher",
|
|
||||||
"link_to": "Period Closing Voucher",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 0,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "database",
|
|
||||||
"indent": 1,
|
|
||||||
"keep_closed": 1,
|
|
||||||
"label": "Setup",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Chart of Accounts",
|
|
||||||
"link_to": "Account",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Chart of Cost Centers",
|
|
||||||
"link_to": "Cost Center",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Account Category",
|
|
||||||
"link_to": "Account Category",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Accounting Dimension",
|
|
||||||
"link_to": "Accounting Dimension",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Currency",
|
|
||||||
"link_to": "Currency",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Currency Exchange",
|
|
||||||
"link_to": "Currency Exchange",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Finance Book",
|
|
||||||
"link_to": "Finance Book",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Mode of Payment",
|
|
||||||
"link_to": "Mode of Payment",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Payment Term",
|
|
||||||
"link_to": "Payment Term",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Journal Entry Template",
|
|
||||||
"link_to": "Journal Entry Template",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Terms and Conditions",
|
|
||||||
"link_to": "Terms and Conditions",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Company",
|
|
||||||
"link_to": "Company",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Fiscal Year",
|
|
||||||
"link_to": "Fiscal Year",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 0,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "settings",
|
|
||||||
"indent": 1,
|
|
||||||
"keep_closed": 1,
|
|
||||||
"label": "Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"icon": "",
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Accounts Settings",
|
|
||||||
"link_to": "Accounts Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Currency Exchange Settings",
|
|
||||||
"link_to": "Currency Exchange Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"child": 1,
|
|
||||||
"collapsible": 1,
|
|
||||||
"indent": 0,
|
|
||||||
"keep_closed": 0,
|
|
||||||
"label": "Repost Accounting Ledger Settings",
|
|
||||||
"link_to": "Repost Accounting Ledger Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"show_arrow": 0,
|
|
||||||
"type": "Link"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2026-01-10 00:06:13.234927",
|
"modified": "2026-01-26 21:23:15.665712",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Invoicing",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"standard": 1,
|
"standard": 1,
|
||||||
"title": "Accounting"
|
"title": "Invoicing"
|
||||||
}
|
}
|
||||||
205
erpnext/workspace_sidebar/payments.json
Normal file
205
erpnext/workspace_sidebar/payments.json
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
{
|
||||||
|
"app": "erpnext",
|
||||||
|
"creation": "2026-01-23 11:36:55.020705",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Workspace Sidebar",
|
||||||
|
"header_icon": "receipt-text",
|
||||||
|
"idx": 0,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "chart",
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Dashboard",
|
||||||
|
"link_to": "Payments",
|
||||||
|
"link_type": "Dashboard",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "money-coins-1",
|
||||||
|
"indent": 1,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payments",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payment Entry",
|
||||||
|
"link_to": "Payment Entry",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Journal Entry",
|
||||||
|
"link_to": "Journal Entry",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payment Request",
|
||||||
|
"link_to": "Payment Request",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payment Order",
|
||||||
|
"link_to": "Payment Order",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Payment Reconciliaition",
|
||||||
|
"link_to": "Payment Reconciliation",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Unreconcile Payment",
|
||||||
|
"link_to": "Unreconcile Payment",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Process Payment Reconciliation",
|
||||||
|
"link_to": "Process Payment Reconciliation",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Repost Accounting Ledger",
|
||||||
|
"link_to": "Repost Accounting Ledger",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Repost Payment Ledger",
|
||||||
|
"link_to": "Repost Payment Ledger",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"icon": "sheet",
|
||||||
|
"indent": 1,
|
||||||
|
"keep_closed": 1,
|
||||||
|
"label": "Reports",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Accounts Receivable",
|
||||||
|
"link_to": "Accounts Receivable",
|
||||||
|
"link_type": "Report",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Accounts Payable",
|
||||||
|
"link_to": "Accounts Payable",
|
||||||
|
"link_type": "Report",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "General Ledger",
|
||||||
|
"link_to": "General Ledger",
|
||||||
|
"link_type": "Report",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Trial Balance",
|
||||||
|
"link_to": "Trial Balance",
|
||||||
|
"link_type": "Report",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"child": 1,
|
||||||
|
"collapsible": 1,
|
||||||
|
"indent": 0,
|
||||||
|
"keep_closed": 0,
|
||||||
|
"label": "Financial Reports",
|
||||||
|
"link_to": "Financial Reports",
|
||||||
|
"link_type": "Workspace",
|
||||||
|
"show_arrow": 0,
|
||||||
|
"type": "Link"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2026-01-26 21:25:37.877020",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Payments",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"standard": 1,
|
||||||
|
"title": "Payments"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user