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

chore: release v14
This commit is contained in:
rohitwaghchaure
2024-02-21 10:58:47 +05:30
committed by GitHub
21 changed files with 218 additions and 49 deletions

View File

@@ -41,9 +41,10 @@ class BankTransaction(StatusUpdater):
else: else:
allocated_amount = 0.0 allocated_amount = 0.0
amount = abs(flt(self.withdrawal) - flt(self.deposit)) unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
self.db_set("allocated_amount", flt(allocated_amount))
self.db_set("unallocated_amount", amount - flt(allocated_amount)) self.db_set("allocated_amount", flt(allocated_amount, self.precision("allocated_amount")))
self.db_set("unallocated_amount", flt(unallocated_amount, self.precision("unallocated_amount")))
self.reload() self.reload()
self.set_status(update=True) self.set_status(update=True)

View File

@@ -11,23 +11,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.setup(doc); super.setup(doc);
} }
company() { company() {
super.company();
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
let me = this;
if (this.frm.doc.company) {
frappe.call({
method:
"erpnext.accounts.party.get_party_account",
args: {
party_type: 'Customer',
party: this.frm.doc.customer,
company: this.frm.doc.company
},
callback: (response) => {
if (response) me.frm.set_value("debit_to", response.message);
},
});
}
} }
onload() { onload() {
var me = this; var me = this;

View File

@@ -85,7 +85,10 @@ class ReceivablePayableReport(object):
self.skip_total_row = 1 self.skip_total_row = 1
if self.filters.get("in_party_currency"): if self.filters.get("in_party_currency"):
self.skip_total_row = 1 if self.filters.get("party") and len(self.filters.get("party")) == 1:
self.skip_total_row = 0
else:
self.skip_total_row = 1
def get_data(self): def get_data(self):
self.get_ple_entries() self.get_ple_entries()

View File

@@ -6,6 +6,19 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Balance Sheet', 10); erpnext.utils.add_dimensions('Balance Sheet', 10);
frappe.query_reports["Balance Sheet"]["filters"].push(
{
"fieldname": "selected_view",
"label": __("Select View"),
"fieldtype": "Select",
"options": [
{ "value": "Report", "label": __("Report View") },
{ "value": "Growth", "label": __("Growth View") }
],
"default": "Report",
"reqd": 1
},
);
frappe.query_reports["Balance Sheet"]["filters"].push({ frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "accumulated_values", "fieldname": "accumulated_values",
"label": __("Accumulated Values"), "label": __("Accumulated Values"),

View File

@@ -319,7 +319,8 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice`.unrealized_profit_loss_account, `tabPurchase Invoice`.unrealized_profit_loss_account,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group, `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group`
,`tabPurchase Invoice Item`.`item_group` as pi_item_group,
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group, `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,

View File

@@ -350,7 +350,13 @@ def get_conditions(filters, additional_conditions=None):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"): if filters.get("warehouse"):
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s""" if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
lft, rgt = frappe.db.get_all(
"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
)[0]
conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
else:
conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
if filters.get("brand"): if filters.get("brand"):
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s""" conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""

View File

@@ -8,6 +8,21 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Profit and Loss Statement', 10); erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
"fieldname": "selected_view",
"label": __("Select View"),
"fieldtype": "Select",
"options": [
{ "value": "Report", "label": __("Report View") },
{ "value": "Growth", "label": __("Growth View") },
{ "value": "Margin", "label": __("Margin View") },
],
"default": "Report",
"reqd": 1
},
);
frappe.query_reports["Profit and Loss Statement"]["filters"].push( frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{ {
"fieldname": "include_default_book_entries", "fieldname": "include_default_book_entries",

View File

@@ -10,7 +10,7 @@ import frappe.defaults
from frappe import _, qb, throw from frappe import _, qb, throw
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Criterion, Table from frappe.query_builder import AliasedQuery, Criterion, Table
from frappe.query_builder.functions import Sum from frappe.query_builder.functions import Round, Sum
from frappe.query_builder.utils import DocType from frappe.query_builder.utils import DocType
from frappe.utils import ( from frappe.utils import (
cint, cint,
@@ -549,16 +549,19 @@ def check_if_advance_entry_modified(args):
args, args,
) )
else: else:
ret = frappe.db.sql( pe = qb.DocType("Payment Entry")
"""select name from `tabPayment Entry` ret = (
where qb.from_(pe)
name = %(voucher_no)s and docstatus = 1 .select(pe.name)
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s .where(
and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1}) (pe.name == args.voucher_no)
""".format( & (pe.docstatus == 1)
party_account_field, precision & (pe.party_type == args.party_type)
), & (pe.party == args.party)
args, & (pe[party_account_field] == args.account)
& (Round(pe.unallocated_amount, precision) == Round(args.unreconciled_amount, precision))
)
.run()
) )
if not ret: if not ret:

View File

@@ -513,14 +513,15 @@ class Asset(AccountsController):
) )
# Adjust depreciation amount in the last period based on the expected value after useful life # Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and ( if (
( n == cint(final_number_of_depreciations) - 1
n == cint(final_number_of_depreciations) - 1 and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life)
and value_after_depreciation != finance_book.expected_value_after_useful_life ) or flt(value_after_depreciation) < flt(
) finance_book.expected_value_after_useful_life
or value_after_depreciation < finance_book.expected_value_after_useful_life
): ):
depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life depreciation_amount += flt(value_after_depreciation) - flt(
finance_book.expected_value_after_useful_life
)
skip_row = True skip_row = True
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0: if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:

View File

@@ -520,7 +520,7 @@ def reverse_depreciation_entry_made_after_disposal(asset, date):
else: else:
row += 1 row += 1
if schedule.schedule_date == date: if schedule.schedule_date == date and schedule.journal_entry:
if not disposal_was_made_on_original_schedule_date( if not disposal_was_made_on_original_schedule_date(
asset, schedule, row, date asset, schedule, row, date
) or disposal_happens_in_the_future(date): ) or disposal_happens_in_the_future(date):

View File

@@ -77,13 +77,13 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry", "Stock Ledger Entry",
"Repost Item Valuation", "Repost Item Valuation",
"Asset", "Asset",
"Asset Movement" "Asset Movement",
) )
self.cancel_target_asset() self.cancel_target_asset()
self.update_stock_ledger() self.update_stock_ledger()
self.make_gl_entries() self.make_gl_entries()
self.restore_consumed_asset_items() self.restore_consumed_asset_items()
def cancel_target_asset(self): def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset: if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset) asset_doc = frappe.get_doc("Asset", self.target_asset)

View File

@@ -201,6 +201,18 @@ class AccountsController(TransactionBase):
) )
) )
if self.get("is_return") and self.get("return_against"):
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
frappe.msgprint(
_(
"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
).format(
document_type,
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
get_link_to_form(self.doctype, self.get("return_against")),
)
)
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances() self.set_advances()

View File

@@ -16,11 +16,49 @@ class BlanketOrder(Document):
def validate(self): def validate(self):
self.validate_dates() self.validate_dates()
self.validate_duplicate_items() self.validate_duplicate_items()
self.set_party_item_code()
def validate_dates(self): def validate_dates(self):
if getdate(self.from_date) > getdate(self.to_date): if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(_("From date cannot be greater than To date")) frappe.throw(_("From date cannot be greater than To date"))
def set_party_item_code(self):
item_ref = {}
if self.blanket_order_type == "Selling":
item_ref = self.get_customer_items_ref()
else:
item_ref = self.get_supplier_items_ref()
if not item_ref:
return
for row in self.items:
row.party_item_code = item_ref.get(row.item_code)
def get_customer_items_ref(self):
items = [d.item_code for d in self.items]
return frappe._dict(
frappe.get_all(
"Item Customer Detail",
filters={"parent": ("in", items), "customer_name": self.customer},
fields=["parent", "ref_code"],
as_list=True,
)
)
def get_supplier_items_ref(self):
items = [d.item_code for d in self.items]
return frappe._dict(
frappe.get_all(
"Item Supplier",
filters={"parent": ("in", items), "supplier": self.supplier},
fields=["parent", "supplier_part_no"],
as_list=True,
)
)
def validate_duplicate_items(self): def validate_duplicate_items(self):
item_list = [] item_list = []
for item in self.items: for item in self.items:

View File

@@ -5,6 +5,7 @@ from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, today from frappe.utils import add_months, today
from erpnext import get_company_currency from erpnext import get_company_currency
from erpnext.stock.doctype.item.test_item import make_item
from .blanket_order import make_order from .blanket_order import make_order
@@ -90,6 +91,30 @@ class TestBlanketOrder(FrappeTestCase):
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10) frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
po.submit() po.submit()
def test_party_item_code(self):
item_doc = make_item("_Test Item 1 for Blanket Order")
item_code = item_doc.name
customer = "_Test Customer"
supplier = "_Test Supplier"
if not frappe.db.exists(
"Item Customer Detail", {"customer_name": customer, "parent": item_code}
):
item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"})
item_doc.save()
if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}):
item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"})
item_doc.save()
# Blanket Order for Selling
bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code)
self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1")
bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
def make_blanket_order(**args): def make_blanket_order(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2018-05-24 07:20:04.255236", "creation": "2018-05-24 07:20:04.255236",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -6,6 +7,7 @@
"field_order": [ "field_order": [
"item_code", "item_code",
"item_name", "item_name",
"party_item_code",
"column_break_3", "column_break_3",
"qty", "qty",
"rate", "rate",
@@ -62,10 +64,17 @@
"fieldname": "terms_and_conditions", "fieldname": "terms_and_conditions",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Terms and Conditions" "label": "Terms and Conditions"
},
{
"fieldname": "party_item_code",
"fieldtype": "Data",
"label": "Party Item Code",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-11-18 19:37:46.245878", "links": [],
"modified": "2024-02-14 18:25:26.479672",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Blanket Order Item", "name": "Blanket Order Item",
@@ -74,5 +83,6 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -2,7 +2,57 @@ frappe.provide("erpnext.financial_statements");
erpnext.financial_statements = { erpnext.financial_statements = {
"filters": get_filters(), "filters": get_filters(),
"baseData": null,
"formatter": function(value, row, column, data, default_formatter, filter) { "formatter": function(value, row, column, data, default_formatter, filter) {
if(frappe.query_report.get_filter_value("selected_view") == "Growth" && data && column.colIndex >= 3){
//Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns.
const lastAnnualValue = row[column.colIndex - 1].content;
const currentAnnualvalue = data[column.fieldname];
if(currentAnnualvalue == undefined) return 'NA'; //making this not applicable for undefined/null values
let annualGrowth = 0;
if(lastAnnualValue == 0 && currentAnnualvalue > 0){
//If the previous year value is 0 and the current value is greater than 0
annualGrowth = 1;
}
else if(lastAnnualValue > 0){
annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue;
}
const growthPercent = (Math.round(annualGrowth*10000)/100); //calculating the rounded off percentage
value = $(`<span>${((growthPercent >=0)? '+':'' )+growthPercent+'%'}</span>`);
if(growthPercent < 0){
value = $(value).addClass("text-danger");
}
else{
value = $(value).addClass("text-success");
}
value = $(value).wrap("<p></p>").parent().html();
return value;
}
else if(frappe.query_report.get_filter_value("selected_view") == "Margin" && data){
if(column.fieldname =="account" && data.account_name == __("Income")){
//Taking the total income from each column (for all the financial years) as the base (100%)
this.baseData = row;
}
if(column.colIndex >= 2){
//Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns.
const currentAnnualvalue = data[column.fieldname];
const baseValue = this.baseData[column.colIndex].content;
if(currentAnnualvalue == undefined || baseValue <= 0) return 'NA';
const marginPercent = Math.round((currentAnnualvalue/baseValue)*10000)/100;
value = $(`<span>${marginPercent+'%'}</span>`);
if(marginPercent < 0)
value = $(value).addClass("text-danger");
else
value = $(value).addClass("text-success");
value = $(value).wrap("<p></p>").parent().html();
return value;
}
}
if (data && column.fieldname=="account") { if (data && column.fieldname=="account") {
value = data.account_name || value; value = data.account_name || value;

View File

@@ -250,7 +250,7 @@ frappe.ui.form.on('Material Request', {
fields: [ fields: [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
options:"BOM", reqd: 1, get_query: function() { options:"BOM", reqd: 1, get_query: function() {
return {filters: { docstatus:1 }}; return {filters: { docstatus:1, "is_active": 1 }};
}}, }},
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"), {"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
options:"Warehouse", reqd: 1}, options:"Warehouse", reqd: 1},

View File

@@ -32,6 +32,10 @@ class PickList(Document):
self.update_status() self.update_status()
self.set_item_locations() self.set_item_locations()
if self.get("locations"):
self.validate_sales_order_percentage()
def validate_sales_order_percentage(self):
# set percentage picked in SO # set percentage picked in SO
for location in self.get("locations"): for location in self.get("locations"):
if ( if (

View File

@@ -548,7 +548,9 @@ frappe.ui.form.on('Stock Entry', {
let fields = [ let fields = [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
options:"BOM", reqd: 1, get_query: filters()}, options:"BOM", reqd: 1, get_query: () => {
return {filters: { docstatus:1, "is_active": 1 }};
}},
{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"), {"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
options:"Warehouse"}, options:"Warehouse"},
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"), {"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),

View File

@@ -72,8 +72,8 @@ class Issue(Document):
"reference_name": self.name, "reference_name": self.name,
} }
) )
communication.ignore_permissions = True communication.flags.ignore_permissions = True
communication.ignore_mandatory = True communication.flags.ignore_mandatory = True
communication.save() communication.save()
@frappe.whitelist() @frappe.whitelist()

View File

@@ -51,8 +51,8 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
.where((BIN.item_code == item_code) & (BIN.warehouse == warehouse)) .where((BIN.item_code == item_code) & (BIN.warehouse == warehouse))
).run() ).run()
stock_qty = stock_qty[0][0]
if stock_qty: if stock_qty:
stock_qty = flt(stock_qty[0][0])
total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse) total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse)
in_stock = int(total_stock > 0) in_stock = int(total_stock > 0)