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:
allocated_amount = 0.0
amount = abs(flt(self.withdrawal) - flt(self.deposit))
self.db_set("allocated_amount", flt(allocated_amount))
self.db_set("unallocated_amount", amount - flt(allocated_amount))
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - 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.set_status(update=True)

View File

@@ -11,23 +11,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.setup(doc);
}
company() {
super.company();
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() {
var me = this;

View File

@@ -85,7 +85,10 @@ class ReceivablePayableReport(object):
self.skip_total_row = 1
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):
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);
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({
"fieldname": "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`.unrealized_profit_loss_account,
`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,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`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)"""
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"):
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);
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(
{
"fieldname": "include_default_book_entries",

View File

@@ -10,7 +10,7 @@ import frappe.defaults
from frappe import _, qb, throw
from frappe.model.meta import get_field_precision
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.utils import (
cint,
@@ -549,16 +549,19 @@ def check_if_advance_entry_modified(args):
args,
)
else:
ret = frappe.db.sql(
"""select name from `tabPayment Entry`
where
name = %(voucher_no)s and docstatus = 1
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1})
""".format(
party_account_field, precision
),
args,
pe = qb.DocType("Payment Entry")
ret = (
qb.from_(pe)
.select(pe.name)
.where(
(pe.name == args.voucher_no)
& (pe.docstatus == 1)
& (pe.party_type == args.party_type)
& (pe.party == args.party)
& (pe[party_account_field] == args.account)
& (Round(pe.unallocated_amount, precision) == Round(args.unreconciled_amount, precision))
)
.run()
)
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
if finance_book.expected_value_after_useful_life and (
(
n == cint(final_number_of_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
if (
n == cint(final_number_of_depreciations) - 1
and flt(value_after_depreciation) != flt(finance_book.expected_value_after_useful_life)
) or flt(value_after_depreciation) < flt(
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
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:
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(
asset, schedule, row, date
) or disposal_happens_in_the_future(date):

View File

@@ -77,13 +77,13 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Asset",
"Asset Movement"
"Asset Movement",
)
self.cancel_target_asset()
self.update_stock_ledger()
self.make_gl_entries()
self.restore_consumed_asset_items()
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and 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"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()

View File

@@ -16,11 +16,49 @@ class BlanketOrder(Document):
def validate(self):
self.validate_dates()
self.validate_duplicate_items()
self.set_party_item_code()
def validate_dates(self):
if getdate(self.from_date) > getdate(self.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):
item_list = []
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 erpnext import get_company_currency
from erpnext.stock.doctype.item.test_item import make_item
from .blanket_order import make_order
@@ -90,6 +91,30 @@ class TestBlanketOrder(FrappeTestCase):
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
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):
args = frappe._dict(args)

View File

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

View File

@@ -2,7 +2,57 @@ frappe.provide("erpnext.financial_statements");
erpnext.financial_statements = {
"filters": get_filters(),
"baseData": null,
"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") {
value = data.account_name || value;

View File

@@ -250,7 +250,7 @@ frappe.ui.form.on('Material Request', {
fields: [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
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"),
options:"Warehouse", reqd: 1},

View File

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

View File

@@ -548,7 +548,9 @@ frappe.ui.form.on('Stock Entry', {
let fields = [
{"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"),
options:"Warehouse"},
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),

View File

@@ -72,8 +72,8 @@ class Issue(Document):
"reference_name": self.name,
}
)
communication.ignore_permissions = True
communication.ignore_mandatory = True
communication.flags.ignore_permissions = True
communication.flags.ignore_mandatory = True
communication.save()
@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))
).run()
stock_qty = stock_qty[0][0]
if stock_qty:
stock_qty = flt(stock_qty[0][0])
total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse)
in_stock = int(total_stock > 0)