Merge pull request #33233 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Deepesh Garg
2022-12-06 20:06:17 +05:30
committed by GitHub
34 changed files with 492 additions and 246 deletions

View File

@@ -169,5 +169,6 @@ def auto_create_fiscal_year():
def get_from_and_to_date(fiscal_year): def get_from_and_to_date(fiscal_year):
fields = ["year_start_date as from_date", "year_end_date as to_date"] fields = ["year_start_date", "year_end_date"]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1) cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)

View File

@@ -231,7 +231,9 @@ class PurchaseInvoice(BuyingController):
) )
if ( if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
): ):
self.validate_rate_with_reference_doc( self.validate_rate_with_reference_doc(
[ [

View File

@@ -40,7 +40,6 @@
"discount_amount", "discount_amount",
"base_rate_with_margin", "base_rate_with_margin",
"sec_break2", "sec_break2",
"apply_tds",
"rate", "rate",
"amount", "amount",
"item_tax_template", "item_tax_template",
@@ -50,6 +49,7 @@
"pricing_rules", "pricing_rules",
"stock_uom_rate", "stock_uom_rate",
"is_free_item", "is_free_item",
"apply_tds",
"section_break_22", "section_break_22",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -871,16 +871,16 @@
"read_only": 1 "read_only": 1
}, },
{ {
"default": "1", "default": "1",
"fieldname": "apply_tds", "fieldname": "apply_tds",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Apply TDS" "label": "Apply TDS"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-10-26 16:05:37.304788", "modified": "2022-11-29 13:01:20.438217",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@@ -920,6 +920,7 @@
"fieldtype": "Table", "fieldtype": "Table",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges", "oldfieldname": "other_charges",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Sales Taxes and Charges" "options": "Sales Taxes and Charges"
@@ -2116,7 +2117,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2022-11-17 17:17:10.883487", "modified": "2022-12-05 16:18:14.532114",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -14,9 +14,17 @@ def execute(filters=None):
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name") filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters) columns = get_columns(filters)
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters) (
tds_docs,
tds_accounts,
tax_category_map,
journal_entry_party_map,
invoice_total_map,
) = get_tds_docs(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map) res = get_result(
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
)
final_result = group_by_supplier_and_category(res) final_result = group_by_supplier_and_category(res)
return columns, final_result return columns, final_result

View File

@@ -8,11 +8,19 @@ from frappe import _
def execute(filters=None): def execute(filters=None):
validate_filters(filters) validate_filters(filters)
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters) (
tds_docs,
tds_accounts,
tax_category_map,
journal_entry_party_map,
invoice_net_total_map,
) = get_tds_docs(filters)
columns = get_columns(filters) columns = get_columns(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map) res = get_result(
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
)
return columns, res return columns, res
@@ -22,7 +30,9 @@ def validate_filters(filters):
frappe.throw(_("From Date must be before To Date")) frappe.throw(_("From Date must be before To Date"))
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map): def get_result(
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map
):
supplier_map = get_supplier_pan_map() supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters) tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs) gle_map = get_gle_map(tds_docs)
@@ -50,7 +60,10 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
if entry.account in tds_accounts: if entry.account in tds_accounts:
tds_deducted += entry.credit - entry.debit tds_deducted += entry.credit - entry.debit
total_amount_credited += entry.credit if invoice_net_total_map.get(name):
total_amount_credited = invoice_net_total_map.get(name)
else:
total_amount_credited += entry.credit
if tds_deducted: if tds_deducted:
row = { row = {
@@ -179,9 +192,10 @@ def get_tds_docs(filters):
purchase_invoices = [] purchase_invoices = []
payment_entries = [] payment_entries = []
journal_entries = [] journal_entries = []
tax_category_map = {} tax_category_map = frappe._dict()
or_filters = {} invoice_net_total_map = frappe._dict()
journal_entry_party_map = {} or_filters = frappe._dict()
journal_entry_party_map = frappe._dict()
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name") bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
tds_accounts = frappe.get_all( tds_accounts = frappe.get_all(
@@ -218,16 +232,22 @@ def get_tds_docs(filters):
tds_documents.append(d.voucher_no) tds_documents.append(d.voucher_no)
if purchase_invoices: if purchase_invoices:
get_tax_category_map(purchase_invoices, "Purchase Invoice", tax_category_map) get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map)
if payment_entries: if payment_entries:
get_tax_category_map(payment_entries, "Payment Entry", tax_category_map) get_doc_info(payment_entries, "Payment Entry", tax_category_map)
if journal_entries: if journal_entries:
journal_entry_party_map = get_journal_entry_party_map(journal_entries) journal_entry_party_map = get_journal_entry_party_map(journal_entries)
get_tax_category_map(journal_entries, "Journal Entry", tax_category_map) get_doc_info(journal_entries, "Journal Entry", tax_category_map)
return tds_documents, tds_accounts, tax_category_map, journal_entry_party_map return (
tds_documents,
tds_accounts,
tax_category_map,
journal_entry_party_map,
invoice_net_total_map,
)
def get_journal_entry_party_map(journal_entries): def get_journal_entry_party_map(journal_entries):
@@ -244,17 +264,18 @@ def get_journal_entry_party_map(journal_entries):
return journal_entry_party_map return journal_entry_party_map
def get_tax_category_map(vouchers, doctype, tax_category_map): def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None):
tax_category_map.update( if doctype == "Purchase Invoice":
frappe._dict( fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"]
frappe.get_all( else:
doctype, fields = ["name", "tax_withholding_category"]
filters={"name": ("in", vouchers)},
fields=["name", "tax_withholding_category"], entries = frappe.get_all(doctype, filters={"name": ("in", vouchers)}, fields=fields)
as_list=1,
) for entry in entries:
) tax_category_map.update({entry.name: entry.tax_withholding_category})
) if doctype == "Purchase Invoice":
invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total})
def get_tax_rate_map(filters): def get_tax_rate_map(filters):

View File

@@ -28,7 +28,7 @@ def get_currency(filters):
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
) )
report_date = filters.get("to_date") report_date = filters.get("to_date") or filters.get("period_end_date")
if not report_date: if not report_date:
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"] fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]

View File

@@ -264,7 +264,7 @@ class TestAsset(AssetSetup):
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation, asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"), asset.precision("gross_purchase_amount"),
) )
this_month_depr_amount = 9000.0 if get_last_day(date) == date else 0 this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount) self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
@@ -1576,3 +1576,9 @@ def set_depreciation_settings_in_company(company=None):
def enable_cwip_accounting(asset_category, enable=1): def enable_cwip_accounting(asset_category, enable=1):
frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable) frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable)
def is_last_day_of_the_month(dt):
last_day_of_the_month = get_last_day(dt)
return getdate(dt) == getdate(last_day_of_the_month)

View File

@@ -44,7 +44,6 @@
"discount_amount", "discount_amount",
"base_rate_with_margin", "base_rate_with_margin",
"sec_break2", "sec_break2",
"apply_tds",
"rate", "rate",
"amount", "amount",
"item_tax_template", "item_tax_template",
@@ -54,6 +53,7 @@
"pricing_rules", "pricing_rules",
"stock_uom_rate", "stock_uom_rate",
"is_free_item", "is_free_item",
"apply_tds",
"section_break_29", "section_break_29",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -902,7 +902,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-10-26 16:47:41.364387", "modified": "2022-11-29 16:47:41.364387",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@@ -22,6 +22,13 @@ frappe.ui.form.on("Request for Quotation",{
} }
}; };
} }
frm.set_query('warehouse', 'items', () => ({
filters: {
company: frm.doc.company,
is_group: 0
}
}));
}, },
onload: function(frm) { onload: function(frm) {

View File

@@ -2311,7 +2311,7 @@ def get_due_date(term, posting_date=None, bill_date=None):
elif term.due_date_based_on == "Day(s) after the end of the invoice month": elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = add_days(get_last_day(date), term.credit_days) due_date = add_days(get_last_day(date), term.credit_days)
elif term.due_date_based_on == "Month(s) after the end of the invoice month": elif term.due_date_based_on == "Month(s) after the end of the invoice month":
due_date = add_months(get_last_day(date), term.credit_months) due_date = get_last_day(add_months(date, term.credit_months))
return due_date return due_date
@@ -2323,7 +2323,7 @@ def get_discount_date(term, posting_date=None, bill_date=None):
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month": elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
discount_validity = add_days(get_last_day(date), term.discount_validity) discount_validity = add_days(get_last_day(date), term.discount_validity)
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month": elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
discount_validity = add_months(get_last_day(date), term.discount_validity) discount_validity = get_last_day(add_months(date, term.discount_validity))
return discount_validity return discount_validity

View File

@@ -322,17 +322,18 @@ class BuyingController(SubcontractingController):
) )
if self.is_internal_transfer(): if self.is_internal_transfer():
if rate != d.rate: if self.doctype == "Purchase Receipt" or self.get("update_stock"):
d.rate = rate if rate != d.rate:
frappe.msgprint( d.rate = rate
_( frappe.msgprint(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" _(
).format(d.idx), "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
alert=1, ).format(d.idx),
) alert=1,
d.discount_percentage = 0.0 )
d.discount_amount = 0.0 d.discount_percentage = 0.0
d.margin_rate_or_amount = 0.0 d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
def validate_for_subcontracting(self): def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"): if self.is_subcontracted and self.get("is_old_subcontracting_flow"):

View File

@@ -442,30 +442,31 @@ class SellingController(StockController):
# For internal transfers use incoming rate as the valuation rate # For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer(): if self.is_internal_transfer():
if d.doctype == "Packed Item": if self.doctype == "Delivery Note" or self.get("update_stock"):
incoming_rate = flt( if d.doctype == "Packed Item":
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, incoming_rate = flt(
d.precision("incoming_rate"), flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
) d.precision("incoming_rate"),
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
) )
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0 d.discount_percentage = 0.0
d.discount_amount = 0.0 d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0 d.margin_rate_or_amount = 0.0
elif self.get("return_against"): elif self.get("return_against"):
# Get incoming rate of return entry from reference document # Get incoming rate of return entry from reference document

View File

@@ -217,7 +217,7 @@ class SalesPipelineAnalytics(object):
def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info): def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info):
if self.filters.get("assigned_to"): if self.filters.get("assigned_to"):
for data in json.loads(info.get("opportunity_owner")): for data in json.loads(info.get("opportunity_owner") or "[]"):
if data == self.filters.get("assigned_to"): if data == self.filters.get("assigned_to"):
self.set_formatted_data(period, data, count_or_amount, assigned_to) self.set_formatted_data(period, data, count_or_amount, assigned_to)
else: else:

View File

@@ -635,6 +635,10 @@ class TestWorkOrder(FrappeTestCase):
bom.submit() bom.submit()
bom_name = bom.name bom_name = bom.name
ste1 = test_stock_entry.make_stock_entry(
item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0
)
work_order = make_wo_order_test_record( work_order = make_wo_order_test_record(
item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1 item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
) )
@@ -659,11 +663,29 @@ class TestWorkOrder(FrappeTestCase):
work_order.insert() work_order.insert()
work_order.submit() work_order.submit()
self.assertEqual(work_order.has_batch_no, 1) self.assertEqual(work_order.has_batch_no, 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) batches = frappe.get_all("Batch", filters={"reference_name": work_order.name})
self.assertEqual(len(batches), 3)
batches = [batch.name for batch in batches]
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10))
for row in ste1.get("items"): for row in ste1.get("items"):
if row.is_finished_item: if row.is_finished_item:
self.assertEqual(row.item_code, fg_item) self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10) self.assertEqual(row.qty, 10)
self.assertTrue(row.batch_no in batches)
batches.remove(row.batch_no)
ste1.submit()
remaining_batches = []
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20))
for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
remaining_batches.append(row.batch_no)
self.assertEqual(sorted(remaining_batches), sorted(batches))
frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0) frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)

View File

@@ -319,3 +319,4 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v13_0.update_schedule_type_in_loans erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.update_partial_tds_fields erpnext.patches.v14_0.update_partial_tds_fields
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
erpnext.patches.v14_0.setup_clear_repost_logs

View File

@@ -1,5 +1,8 @@
import frappe
from erpnext.setup.install import setup_currency_exchange from erpnext.setup.install import setup_currency_exchange
def execute(): def execute():
frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
setup_currency_exchange() setup_currency_exchange()

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from erpnext.setup.install import setup_log_settings
def execute():
setup_log_settings()

View File

@@ -47,29 +47,36 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
await this.calculate_shipping_charges(); await this.calculate_shipping_charges();
// Advance calculation applicable to Sales /Purchase Invoice // Advance calculation applicable to Sales/Purchase Invoice
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype) if (
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) { in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
&& this.frm.doc.docstatus < 2
&& !this.frm.doc.is_return
) {
this.calculate_total_advance(update_paid_amount); this.calculate_total_advance(update_paid_amount);
} }
if (in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_pos && if (
this.frm.doc.is_return) { in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
if (this.frm.doc.doctype == "Sales Invoice") { && this.frm.doc.s_pos
this.set_total_amount_to_default_mop(); && this.frm.doc.is_return
} ) {
this.set_total_amount_to_default_mop();
this.calculate_paid_amount(); this.calculate_paid_amount();
} }
// Sales person's commission // Sales person's commission
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) { if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
this.calculate_commission(); this.calculate_commission();
this.calculate_contribution(); this.calculate_contribution();
} }
// Update paid amount on return/debit note creation // Update paid amount on return/debit note creation
if(this.frm.doc.doctype === "Purchase Invoice" && this.frm.doc.is_return if (
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)) { this.frm.doc.doctype === "Purchase Invoice"
&& this.frm.doc.is_return
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)
) {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
} }
@@ -775,21 +782,30 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total; let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total; let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
if(this.frm.doc.party_account_currency == this.frm.doc.currency) { if (this.frm.doc.party_account_currency == this.frm.doc.currency) {
var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance var total_amount_to_pay = flt(
- this.frm.doc.write_off_amount), precision("grand_total")); grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount,
precision("grand_total")
);
} else { } else {
var total_amount_to_pay = flt( var total_amount_to_pay = flt(
(flt(base_grand_total, precision("base_grand_total")) (
- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount), flt(
base_grand_total,
precision("base_grand_total")
)
- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount
),
precision("base_grand_total") precision("base_grand_total")
); );
} }
this.frm.doc.payments.find(pay => { this.frm.doc.payments.find(pay => {
if (pay.default) { if (pay.default) {
pay.amount = total_amount_to_pay; pay.amount = total_amount_to_pay;
} }
}); });
this.frm.refresh_fields(); this.frm.refresh_fields();
} }

View File

@@ -30,6 +30,7 @@ def after_install():
add_company_to_session_defaults() add_company_to_session_defaults()
add_standard_navbar_items() add_standard_navbar_items()
add_app_name() add_app_name()
setup_log_settings()
frappe.db.commit() frappe.db.commit()
@@ -197,3 +198,10 @@ def add_standard_navbar_items():
def add_app_name(): def add_app_name():
frappe.db.set_value("System Settings", None, "app_name", "ERPNext") frappe.db.set_value("System Settings", None, "app_name", "ERPNext")
def setup_log_settings():
log_settings = frappe.get_single("Log Settings")
log_settings.append("logs_to_clear", {"ref_doctype": "Repost Item Valuation", "days": 60})
log_settings.save(ignore_permissions=True)

View File

@@ -596,7 +596,9 @@ def make_stock_entry(source_name, target_doc=None):
if source.material_request_type == "Customer Provided": if source.material_request_type == "Customer Provided":
target.purpose = "Material Receipt" target.purpose = "Material Receipt"
target.set_missing_values() target.set_transfer_qty()
target.set_actual_qty()
target.calculate_rate_and_amount(raise_error_if_no_rate=False)
target.set_stock_entry_type() target.set_stock_entry_type()
target.set_job_card_data() target.set_job_card_data()

View File

@@ -48,7 +48,7 @@ def make_packing_list(doc):
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc) update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
if set_price_from_children: # create/update bundle item wise price dict if set_price_from_children: # create/update bundle item wise price dict
update_product_bundle_rate(parent_items_price, pi_row) update_product_bundle_rate(parent_items_price, pi_row, item_row)
if parent_items_price: if parent_items_price:
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
@@ -247,7 +247,7 @@ def get_cancelled_doc_packed_item_details(old_packed_items):
return prev_doc_packed_items_map return prev_doc_packed_items_map
def update_product_bundle_rate(parent_items_price, pi_row): def update_product_bundle_rate(parent_items_price, pi_row, item_row):
""" """
Update the price dict of Product Bundles based on the rates of the Items in the bundle. Update the price dict of Product Bundles based on the rates of the Items in the bundle.
@@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row):
if not rate: if not rate:
parent_items_price[key] = 0.0 parent_items_price[key] = 0.0
parent_items_price[key] += flt(pi_row.rate) parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty)
def set_product_bundle_rate_amount(doc, parent_items_price): def set_product_bundle_rate_amount(doc, parent_items_price):

View File

@@ -126,8 +126,8 @@ class TestPackedItem(FrappeTestCase):
so.packed_items[1].rate = 200 so.packed_items[1].rate = 200
so.save() so.save()
self.assertEqual(so.items[0].rate, 350) self.assertEqual(so.items[0].rate, 700)
self.assertEqual(so.items[0].amount, 700) self.assertEqual(so.items[0].amount, 1400)
def test_newly_mapped_doc_packed_items(self): def test_newly_mapped_doc_packed_items(self):
"Test impact on packed items in newly mapped DN from SO." "Test impact on packed items in newly mapped DN from SO."

View File

@@ -173,7 +173,9 @@ class PurchaseReceipt(BuyingController):
) )
if ( if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
): ):
self.validate_rate_with_reference_doc( self.validate_rate_with_reference_doc(
[["Purchase Order", "purchase_order", "purchase_order_item"]] [["Purchase Order", "purchase_order", "purchase_order_item"]]

View File

@@ -5,6 +5,8 @@ import frappe
from frappe import _ from frappe import _
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import Now
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
from frappe.utils.user import get_users_with_role from frappe.utils.user import get_users_with_role
from rq.timeouts import JobTimeoutException from rq.timeouts import JobTimeoutException
@@ -21,6 +23,18 @@ RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
class RepostItemValuation(Document): class RepostItemValuation(Document):
@staticmethod
def clear_old_logs(days=None):
days = days or 90
table = DocType("Repost Item Valuation")
frappe.db.delete(
table,
filters=(
(table.modified < (Now() - Interval(days=days)))
& (table.status.isin(["Completed", "Skipped"]))
),
)
def validate(self): def validate(self):
self.set_status(write=False) self.set_status(write=False)
self.reset_field_values() self.reset_field_values()

View File

@@ -6,8 +6,7 @@ from unittest.mock import MagicMock, call
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate from frappe.utils import add_days, add_to_date, now, nowdate, today
from frappe.utils.data import add_to_date, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.utils import repost_gle_for_stock_vouchers from erpnext.accounts.utils import repost_gle_for_stock_vouchers
@@ -86,6 +85,33 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
msg=f"Exepcted false from : {case}", msg=f"Exepcted false from : {case}",
) )
def test_clear_old_logs(self):
# create 10 logs
for i in range(1, 20):
repost_doc = frappe.get_doc(
doctype="Repost Item Valuation",
item_code="_Test Item",
warehouse="_Test Warehouse - _TC",
based_on="Item and Warehouse",
posting_date=nowdate(),
status="Skipped",
posting_time="00:01:00",
).insert(ignore_permissions=True)
repost_doc.load_from_db()
repost_doc.modified = add_days(now(), days=-i * 10)
repost_doc.db_update_all()
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
self.assertTrue(len(logs) > 10)
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation
RepostItemValuation.clear_old_logs(days=1)
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
self.assertTrue(len(logs) == 0)
def test_create_item_wise_repost_item_valuation_entries(self): def test_create_item_wise_repost_item_valuation_entries(self):
pr = make_purchase_receipt( pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", company="_Test Company with perpetual inventory",

View File

@@ -659,6 +659,13 @@ class StockEntry(StockController):
if d.allow_zero_valuation_rate: if d.allow_zero_valuation_rate:
d.basic_rate = 0.0 d.basic_rate = 0.0
frappe.msgprint(
_(
"Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
).format(d.idx, d.item_code),
alert=1,
)
elif d.is_finished_item: elif d.is_finished_item:
if self.purpose == "Manufacture": if self.purpose == "Manufacture":
d.basic_rate = self.get_basic_rate_for_manufactured_item( d.basic_rate = self.get_basic_rate_for_manufactured_item(
@@ -1538,6 +1545,7 @@ class StockEntry(StockController):
"reference_name": self.pro_doc.name, "reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype, "reference_doctype": self.pro_doc.doctype,
"qty_to_produce": (">", 0), "qty_to_produce": (">", 0),
"batch_qty": ("=", 0),
} }
fields = ["qty_to_produce as qty", "produced_qty", "name"] fields = ["qty_to_produce as qty", "produced_qty", "name"]
@@ -2231,14 +2239,14 @@ class StockEntry(StockController):
d.qty -= process_loss_dict[d.item_code][1] d.qty -= process_loss_dict[d.item_code][1]
def set_serial_no_batch_for_finished_good(self): def set_serial_no_batch_for_finished_good(self):
args = {} serial_nos = ""
if self.pro_doc.serial_no: if self.pro_doc.serial_no:
self.get_serial_nos_for_fg(args) serial_nos = self.get_serial_nos_for_fg()
for row in self.items: for row in self.items:
if row.is_finished_item and row.item_code == self.pro_doc.production_item: if row.is_finished_item and row.item_code == self.pro_doc.production_item:
if args.get("serial_no"): if serial_nos:
row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)]) row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
def get_serial_nos_for_fg(self, args): def get_serial_nos_for_fg(self, args):
fields = [ fields = [
@@ -2251,14 +2259,14 @@ class StockEntry(StockController):
filters = [ filters = [
["Stock Entry", "work_order", "=", self.work_order], ["Stock Entry", "work_order", "=", self.work_order],
["Stock Entry", "purpose", "=", "Manufacture"], ["Stock Entry", "purpose", "=", "Manufacture"],
["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "docstatus", "<", 2],
["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item], ["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
] ]
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
if self.pro_doc.serial_no: if self.pro_doc.serial_no:
args["serial_no"] = self.get_available_serial_nos(stock_entries) return self.get_available_serial_nos(stock_entries)
def get_available_serial_nos(self, stock_entries): def get_available_serial_nos(self, stock_entries):
used_serial_nos = [] used_serial_nos = []

View File

@@ -2,12 +2,10 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from collections import defaultdict
import frappe import frappe
from frappe import _, throw from frappe import _, throw
from frappe.contacts.address_and_contact import load_address_and_contact from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils import cint, flt from frappe.utils import cint
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
from pypika.terms import ExistsCriterion from pypika.terms import ExistsCriterion
@@ -166,60 +164,7 @@ def get_children(doctype, parent=None, company=None, is_root=False):
["company", "in", (company, None, "")], ["company", "in", (company, None, "")],
] ]
warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name") return frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
company_currency = ""
if company:
company_currency = frappe.get_cached_value("Company", company, "default_currency")
warehouse_wise_value = get_warehouse_wise_stock_value(company)
# return warehouses
for wh in warehouses:
wh["balance"] = warehouse_wise_value.get(wh.value)
if company_currency:
wh["company_currency"] = company_currency
return warehouses
def get_warehouse_wise_stock_value(company):
warehouses = frappe.get_all(
"Warehouse", fields=["name", "parent_warehouse"], filters={"company": company}
)
parent_warehouse = {d.name: d.parent_warehouse for d in warehouses}
filters = {"warehouse": ("in", [data.name for data in warehouses])}
bin_data = frappe.get_all(
"Bin",
fields=["sum(stock_value) as stock_value", "warehouse"],
filters=filters,
group_by="warehouse",
)
warehouse_wise_stock_value = defaultdict(float)
for row in bin_data:
if not row.stock_value:
continue
warehouse_wise_stock_value[row.warehouse] = row.stock_value
update_value_in_parent_warehouse(
warehouse_wise_stock_value, parent_warehouse, row.warehouse, row.stock_value
)
return warehouse_wise_stock_value
def update_value_in_parent_warehouse(
warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value
):
parent_warehouse = parent_warehouse_dict.get(warehouse)
if not parent_warehouse:
return
warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
update_value_in_parent_warehouse(
warehouse_wise_stock_value, parent_warehouse_dict, parent_warehouse, stock_value
)
@frappe.whitelist() @frappe.whitelist()

View File

@@ -17,11 +17,4 @@ frappe.treeview_settings['Warehouse'] = {
description: __("Child nodes can be only created under 'Group' type nodes")} description: __("Child nodes can be only created under 'Group' type nodes")}
], ],
ignore_fields:["parent_warehouse"], ignore_fields:["parent_warehouse"],
onrender: function(node) {
if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right">'
+ format_currency((node.data.balance), node.data.company_currency)
+ '</span>').insertBefore(node.$ul);
}
}
} }

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Warehouse Wise Stock Balance"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
}
],
"initial_depth": 3,
"tree": true,
"parent_field": "parent_warehouse",
"name_field": "warehouse"
};

View File

@@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2022-12-06 14:15:31.924345",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"modified": "2022-12-06 14:16:55.969214",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse Wise Stock Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Warehouse Wise Stock Balance",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Accounts Manager"
}
]
}

View File

@@ -0,0 +1,89 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from typing import Any, Dict, List, Optional, TypedDict
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
class StockBalanceFilter(TypedDict):
company: Optional[str]
warehouse: Optional[str]
SLEntry = Dict[str, Any]
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
.select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance"))
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
.groupby(sle.warehouse)
)
if filters.get("company"):
query = query.where(sle.company == filters.get("company"))
data = query.run(as_list=True)
return frappe._dict(data) if data else frappe._dict()
def get_warehouses(report_filters: StockBalanceFilter):
return frappe.get_all(
"Warehouse",
fields=["name", "parent_warehouse", "is_group"],
filters={"company": report_filters.company, "disabled": 0},
order_by="lft",
)
def get_data(filters: StockBalanceFilter):
warehouse_balance = get_warehouse_wise_balance(filters)
warehouses = get_warehouses(filters)
for warehouse in warehouses:
warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0)
update_indent(warehouses)
return warehouses
def update_indent(warehouses):
for warehouse in warehouses:
def add_indent(warehouse, indent):
warehouse.indent = indent
for child in warehouses:
if child.parent_warehouse == warehouse.name:
warehouse.stock_balance += child.stock_balance
add_indent(child, indent + 1)
if warehouse.is_group:
add_indent(warehouse, warehouse.indent or 0)
def get_columns():
return [
{
"label": _("Warehouse"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Warehouse",
"width": 200,
},
{"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
]

View File

@@ -5,7 +5,7 @@
"label": "Warehouse wise Stock Value" "label": "Warehouse wise Stock Value"
} }
], ],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters &amp; Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528", "creation": "2020-03-02 15:43:10.096528",
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
@@ -207,80 +207,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@@ -705,15 +631,100 @@
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Warehouse Wise Stock Balance",
"link_count": 0,
"link_to": "Warehouse Wise Stock Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2022-01-13 17:47:38.339931", "modified": "2022-12-06 17:03:56.397272",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock", "name": "Stock",
"owner": "Administrator", "owner": "Administrator",
"parent_page": "", "parent_page": "",
"public": 1, "public": 1,
"quick_lists": [],
"restrict_to_domain": "", "restrict_to_domain": "",
"roles": [], "roles": [],
"sequence_id": 24.0, "sequence_id": 24.0,