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

chore: release v14
This commit is contained in:
Deepesh Garg
2023-04-19 07:04:25 +05:30
committed by GitHub
25 changed files with 253 additions and 222 deletions

View File

@@ -3,13 +3,13 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, # the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007 erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 erpnext/loan_management/ @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @nextchamp-saqib @deepeshgarg007 erpnext/support/ @deepeshgarg007
pos* @nextchamp-saqib pos*
erpnext/buying/ @rohitwaghchaure @s-aga-r erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r erpnext/maintenance/ @rohitwaghchaure @s-aga-r
@@ -18,12 +18,8 @@ erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/education/ @rutwikhdev erpnext/patches/ @deepeshgarg007
erpnext/projects/ @ruchamahabal
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure .github/ @deepeshgarg007
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib
.github/ @ankush
pyproject.toml @ankush pyproject.toml @ankush

View File

@@ -18,7 +18,6 @@
"root_type", "root_type",
"report_type", "report_type",
"account_currency", "account_currency",
"inter_company_account",
"column_break1", "column_break1",
"parent_account", "parent_account",
"account_type", "account_type",
@@ -34,15 +33,11 @@
{ {
"fieldname": "properties", "fieldname": "properties",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -53,9 +48,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "account_name", "oldfieldname": "account_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"reqd": 1, "reqd": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "account_number", "fieldname": "account_number",
@@ -63,17 +56,13 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Account Number", "label": "Account Number",
"read_only": 1, "read_only": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Group", "label": "Is Group"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@@ -85,9 +74,7 @@
"options": "Company", "options": "Company",
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1, "reqd": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "root_type", "fieldname": "root_type",
@@ -95,9 +82,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Root Type", "label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1, "read_only": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "report_type", "fieldname": "report_type",
@@ -105,32 +90,18 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Report Type", "label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss", "options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1, "read_only": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.is_group==0", "depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency", "fieldname": "account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Currency", "label": "Currency",
"options": "Currency", "options": "Currency"
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "inter_company_account",
"fieldtype": "Check",
"label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -142,9 +113,7 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Account", "options": "Account",
"reqd": 1, "reqd": 1,
"search_index": 1, "search_index": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Setting Account Type helps in selecting this Account in transactions.", "description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -154,9 +123,7 @@
"label": "Account Type", "label": "Account Type",
"oldfieldname": "account_type", "oldfieldname": "account_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
@@ -164,9 +131,7 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Rate", "label": "Rate",
"oldfieldname": "tax_rate", "oldfieldname": "tax_rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "If the account is frozen, entries are allowed to restricted users.", "description": "If the account is frozen, entries are allowed to restricted users.",
@@ -175,17 +140,13 @@
"label": "Frozen", "label": "Frozen",
"oldfieldname": "freeze_account", "oldfieldname": "freeze_account",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "No\nYes", "options": "No\nYes"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "balance_must_be", "fieldname": "balance_must_be",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Balance must be", "label": "Balance must be",
"options": "\nDebit\nCredit", "options": "\nDebit\nCredit"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "lft", "fieldname": "lft",
@@ -194,9 +155,7 @@
"label": "Lft", "label": "Lft",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1, "search_index": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "rgt", "fieldname": "rgt",
@@ -205,9 +164,7 @@
"label": "Rgt", "label": "Rgt",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1, "search_index": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "old_parent", "fieldname": "old_parent",
@@ -215,33 +172,27 @@
"hidden": 1, "hidden": 1,
"label": "Old Parent", "label": "Old Parent",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross", "fieldname": "include_in_gross",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Include in gross", "label": "Include in gross"
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable", "label": "Disable"
"show_days": 1,
"show_seconds": 1
} }
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-06-11 15:15:54.338622", "modified": "2023-04-11 16:08:46.983677",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
@@ -301,5 +252,6 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -182,6 +182,7 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms", "fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order" "label": "Automatically Fetch Payment Terms from Order"
@@ -362,7 +363,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-03-28 09:50:20.375233", "modified": "2023-04-14 17:22:03.680886",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -287,10 +287,6 @@ class TestJournalEntry(unittest.TestCase):
jv.submit() jv.submit()
def test_inter_company_jv(self): def test_inter_company_jv(self):
frappe.db.set_value("Account", "Sales Expenses - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1)
frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1)
frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1)
jv = make_journal_entry( jv = make_journal_entry(
"Sales Expenses - _TC", "Sales Expenses - _TC",
"Buildings - _TC", "Buildings - _TC",

View File

@@ -971,29 +971,47 @@ frappe.ui.form.on('Payment Entry', {
}, },
callback: function(r, rt) { callback: function(r, rt) {
if(r.message) { if(r.message) {
var write_off_row = $.map(frm.doc["deductions"] || [], function(t) { const write_off_row = $.map(frm.doc["deductions"] || [], function(t) {
return t.account==r.message[account] ? t : null; }); return t.account==r.message[account] ? t : null; });
var row = []; const difference_amount = flt(frm.doc.difference_amount,
var difference_amount = flt(frm.doc.difference_amount,
precision("difference_amount")); precision("difference_amount"));
if (!write_off_row.length && difference_amount) { const add_deductions = (details) => {
row = frm.add_child("deductions"); if (!write_off_row.length && difference_amount) {
row.account = r.message[account]; row = frm.add_child("deductions");
row.cost_center = r.message["cost_center"]; row.account = details[account];
} else { row.cost_center = details["cost_center"];
row = write_off_row[0]; } else {
} row = write_off_row[0];
}
if (row) { if (row) {
row.amount = flt(row.amount) + difference_amount; row.amount = flt(row.amount) + difference_amount;
} else { } else {
frappe.msgprint(__("No gain or loss in the exchange rate")) frappe.msgprint(__("No gain or loss in the exchange rate"))
} }
refresh_field("deductions");
};
refresh_field("deductions"); if (!r.message[account]) {
frappe.prompt({
label: __("Please Specify Account"),
fieldname: account,
fieldtype: "Link",
options: "Account",
get_query: () => ({
filters: {
company: frm.doc.company,
}
})
}, (values) => {
const details = Object.assign({}, r.message, values);
add_deductions(details);
}, __(frappe.unscrub(account)));
} else {
add_deductions(r.message);
}
frm.events.set_unallocated_amount(frm); frm.events.set_unallocated_amount(frm);
} }

View File

@@ -1594,17 +1594,7 @@ def get_account_details(account, date, cost_center=None):
@frappe.whitelist() @frappe.whitelist()
def get_company_defaults(company): def get_company_defaults(company):
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"] fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
ret = frappe.get_cached_value("Company", company, fields, as_dict=1) return frappe.get_cached_value("Company", company, fields, as_dict=1)
for fieldname in fields:
if not ret[fieldname]:
frappe.throw(
_("Please set default {0} in Company {1}").format(
frappe.get_meta("Company").get_label(fieldname), company
)
)
return ret
def get_outstanding_on_journal_entry(name): def get_outstanding_on_journal_entry(name):
@@ -1764,7 +1754,12 @@ def get_payment_entry(
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date)) frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
else: else:
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value( if doc.doctype in (
"Sales Invoice",
"Purchase Invoice",
"Purchase Order",
"Sales Order",
) and frappe.get_cached_value(
"Payment Terms Template", "Payment Terms Template",
{"name": doc.payment_terms_template}, {"name": doc.payment_terms_template},
"allocate_payment_based_on_payment_terms", "allocate_payment_based_on_payment_terms",

View File

@@ -24,7 +24,6 @@ class TestGeneralLedger(FrappeTestCase):
"root_type": "Asset", "root_type": "Asset",
"report_type": "Balance Sheet", "report_type": "Balance Sheet",
"account_currency": "USD", "account_currency": "USD",
"inter_company_account": 0,
"parent_account": "Bank Accounts - _TC", "parent_account": "Bank Accounts - _TC",
"account_type": "Bank", "account_type": "Bank",
"doctype": "Account", "doctype": "Account",

View File

@@ -495,6 +495,7 @@
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
"fieldname": "items", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Items",
"oldfieldname": "po_details", "oldfieldname": "po_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Purchase Order Item", "options": "Purchase Order Item",
@@ -1100,8 +1101,7 @@
{ {
"fieldname": "before_items_section", "fieldname": "before_items_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1, "hide_border": 1
"label": "Items"
}, },
{ {
"fieldname": "items_col_break", "fieldname": "items_col_break",
@@ -1271,7 +1271,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-01-28 18:59:16.322824", "modified": "2023-04-14 16:42:29.448464",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@@ -310,7 +310,6 @@
"fieldname": "items_section", "fieldname": "items_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1, "hide_border": 1,
"label": "Items",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart" "options": "fa fa-shopping-cart"
}, },
@@ -318,6 +317,7 @@
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
"fieldname": "items", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Items",
"oldfieldname": "po_details", "oldfieldname": "po_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Supplier Quotation Item", "options": "Supplier Quotation Item",
@@ -844,7 +844,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-12-12 18:35:39.740974", "modified": "2023-04-14 16:43:41.714832",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation", "name": "Supplier Quotation",

View File

@@ -273,8 +273,8 @@ class AccountsController(TransactionBase):
self.validate_payment_schedule_dates() self.validate_payment_schedule_dates()
self.set_due_date() self.set_due_date()
self.set_payment_schedule() self.set_payment_schedule()
self.validate_payment_schedule_amount()
if not self.get("ignore_default_payment_terms_template"): if not self.get("ignore_default_payment_terms_template"):
self.validate_payment_schedule_amount()
self.validate_due_date() self.validate_due_date()
self.validate_advance_entries() self.validate_advance_entries()
@@ -515,7 +515,6 @@ class AccountsController(TransactionBase):
parent_dict.update({"customer": parent_dict.get("party_name")}) parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = [] self.pricing_rules = []
basic_item_details_map = {}
for item in self.get("items"): for item in self.get("items"):
if item.get("item_code"): if item.get("item_code"):
@@ -535,17 +534,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted args["is_subcontracted"] = self.is_subcontracted
basic_details = basic_item_details_map.get(item.item_code) ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
ret, basic_item_details = get_item_details(
args,
self,
for_validate=True,
overwrite_warehouse=False,
return_basic_details=True,
basic_details=basic_details,
)
basic_item_details_map.setdefault(item.item_code, basic_item_details)
for fieldname, value in ret.items(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:
@@ -1618,6 +1607,7 @@ class AccountsController(TransactionBase):
base_grand_total = self.get("base_rounded_total") or self.base_grand_total base_grand_total = self.get("base_rounded_total") or self.base_grand_total
grand_total = self.get("rounded_total") or self.grand_total grand_total = self.get("rounded_total") or self.grand_total
automatically_fetch_payment_terms = 0
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount) base_grand_total = base_grand_total - flt(self.base_write_off_amount)
@@ -1663,19 +1653,20 @@ class AccountsController(TransactionBase):
) )
self.append("payment_schedule", data) self.append("payment_schedule", data)
for d in self.get("payment_schedule"): if not automatically_fetch_payment_terms:
if d.invoice_portion: for d in self.get("payment_schedule"):
d.payment_amount = flt( if d.invoice_portion:
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount") d.payment_amount = flt(
) grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
d.base_payment_amount = flt( )
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount") d.base_payment_amount = flt(
) base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
d.outstanding = d.payment_amount )
elif not d.invoice_portion: d.outstanding = d.payment_amount
d.base_payment_amount = flt( elif not d.invoice_portion:
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") d.base_payment_amount = flt(
) d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
)
def get_order_details(self): def get_order_details(self):
if self.doctype == "Sales Invoice": if self.doctype == "Sales Invoice":
@@ -1728,6 +1719,10 @@ class AccountsController(TransactionBase):
"invoice_portion": schedule.invoice_portion, "invoice_portion": schedule.invoice_portion,
"mode_of_payment": schedule.mode_of_payment, "mode_of_payment": schedule.mode_of_payment,
"description": schedule.description, "description": schedule.description,
"payment_amount": schedule.payment_amount,
"base_payment_amount": schedule.base_payment_amount,
"outstanding": schedule.outstanding,
"paid_amount": schedule.paid_amount,
} }
if schedule.discount_type == "Percentage": if schedule.discount_type == "Percentage":

View File

@@ -859,6 +859,8 @@ def is_reposting_pending():
def future_sle_exists(args, sl_entries=None): def future_sle_exists(args, sl_entries=None):
key = (args.voucher_type, args.voucher_no) key = (args.voucher_type, args.voucher_no)
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if validate_future_sle_not_exists(args, key, sl_entries): if validate_future_sle_not_exists(args, key, sl_entries):
return False return False
@@ -892,6 +894,9 @@ def future_sle_exists(args, sl_entries=None):
) )
for d in data: for d in data:
if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({})
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
return len(data) return len(data)
@@ -903,6 +908,9 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
item_key = (args.get("item_code"), args.get("warehouse")) item_key = (args.get("item_code"), args.get("warehouse"))
if not sl_entries and hasattr(frappe.local, "future_sle"): if not sl_entries and hasattr(frappe.local, "future_sle"):
if key not in frappe.local.future_sle:
return False
if not frappe.local.future_sle.get(key) or ( if not frappe.local.future_sle.get(key) or (
item_key and item_key not in frappe.local.future_sle.get(key) item_key and item_key not in frappe.local.future_sle.get(key)
): ):
@@ -910,11 +918,8 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
def get_cached_data(args, key): def get_cached_data(args, key):
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}
if key not in frappe.local.future_sle: if key not in frappe.local.future_sle:
frappe.local.future_sle[key] = frappe._dict({}) return False
if args.get("item_code"): if args.get("item_code"):
item_key = (args.get("item_code"), args.get("warehouse")) item_key = (args.get("item_code"), args.get("warehouse"))

View File

@@ -315,6 +315,7 @@ class WebsiteItem(WebsiteGenerator):
self.item_code, skip_quotation_creation=True self.item_code, skip_quotation_creation=True
) )
@frappe.whitelist()
def copy_specification_from_item_group(self): def copy_specification_from_item_group(self):
self.set("website_specifications", []) self.set("website_specifications", [])
if self.item_group: if self.item_group:

View File

@@ -1920,7 +1920,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
prompt_user_for_reference_date(){ prompt_user_for_reference_date(){
var me = this; let me = this;
frappe.prompt({ frappe.prompt({
label: __("Cheque/Reference Date"), label: __("Cheque/Reference Date"),
fieldname: "reference_date", fieldname: "reference_date",
@@ -1947,7 +1947,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length; let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
if(!is_eligible || !has_payment_schedule) return false; if(!is_eligible || !has_payment_schedule) return false;
let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date); let has_discount = this.frm.doc.payment_schedule.some(row => row.discount);
return has_discount; return has_discount;
} }

View File

@@ -416,7 +416,6 @@
"fieldname": "items_section", "fieldname": "items_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1, "hide_border": 1,
"label": "Items",
"oldfieldtype": "Section Break", "oldfieldtype": "Section Break",
"options": "fa fa-shopping-cart" "options": "fa fa-shopping-cart"
}, },
@@ -424,6 +423,7 @@
"allow_bulk_edit": 1, "allow_bulk_edit": 1,
"fieldname": "items", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Items",
"oldfieldname": "quotation_details", "oldfieldname": "quotation_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Quotation Item", "options": "Quotation Item",
@@ -1072,7 +1072,7 @@
"idx": 82, "idx": 82,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-12-12 18:32:28.671332", "modified": "2023-04-14 16:50:44.550098",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -168,7 +168,7 @@ class Analytics(object):
def get_sales_transactions_based_on_items(self): def get_sales_transactions_based_on_items(self):
if self.filters["value_quantity"] == "Value": if self.filters["value_quantity"] == "Value":
value_field = "base_amount" value_field = "base_net_amount"
else: else:
value_field = "stock_qty" value_field = "stock_qty"
@@ -216,7 +216,7 @@ class Analytics(object):
def get_sales_transactions_based_on_item_group(self): def get_sales_transactions_based_on_item_group(self):
if self.filters["value_quantity"] == "Value": if self.filters["value_quantity"] == "Value":
value_field = "base_amount" value_field = "base_net_amount"
else: else:
value_field = "qty" value_field = "qty"

View File

@@ -704,7 +704,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2022-04-26 13:29:55.087240", "modified": "2023-04-16 13:29:55.087240",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling", "name": "Selling",

View File

@@ -161,7 +161,7 @@ def add_standard_navbar_items():
{ {
"item_label": "User Forum", "item_label": "User Forum",
"item_type": "Route", "item_type": "Route",
"route": "https://discuss.erpnext.com", "route": "https://discuss.frappe.io",
"is_standard": 1, "is_standard": 1,
}, },
{ {

View File

@@ -581,6 +581,11 @@
"title": "Bauleistungen nach § 13b UStG", "title": "Bauleistungen nach § 13b UStG",
"is_default": 0, "is_default": 0,
"taxes": [] "taxes": []
},
{
"title": "Nullsteuersatz nach § 12 Abs. 3 UStG",
"is_default": 0,
"taxes": []
} }
], ],
"purchase_tax_templates": [ "purchase_tax_templates": [
@@ -1339,6 +1344,11 @@
"title": "Bauleistungen nach § 13b UStG", "title": "Bauleistungen nach § 13b UStG",
"is_default": 0, "is_default": 0,
"taxes": [] "taxes": []
},
{
"title": "Nullsteuersatz nach § 12 Abs. 3 UStG",
"is_default": 0,
"taxes": []
} }
], ],
"purchase_tax_templates": [ "purchase_tax_templates": [
@@ -2097,6 +2107,11 @@
"title": "Bauleistungen nach § 13b UStG", "title": "Bauleistungen nach § 13b UStG",
"is_default": 0, "is_default": 0,
"taxes": [] "taxes": []
},
{
"title": "Nullsteuersatz nach § 12 Abs. 3 UStG",
"is_default": 0,
"taxes": []
} }
], ],
"purchase_tax_templates": [ "purchase_tax_templates": [
@@ -2849,6 +2864,11 @@
"title": "Bauleistungen nach § 13b UStG", "title": "Bauleistungen nach § 13b UStG",
"is_default": 0, "is_default": 0,
"taxes": [] "taxes": []
},
{
"title": "Nullsteuersatz nach § 12 Abs. 3 UStG",
"is_default": 0,
"taxes": []
} }
], ],
"purchase_tax_templates": [ "purchase_tax_templates": [

View File

@@ -410,10 +410,10 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"set_only_once": 1
}, },
{ {
"fieldname": "status", "fieldname": "status",
@@ -433,7 +433,7 @@
"icon": "fa fa-barcode", "icon": "fa fa-barcode",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2021-12-23 10:44:30.299450", "modified": "2023-04-14 15:58:46.139887",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial No", "name": "Serial No",
@@ -461,7 +461,6 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 1,
"write": 1 "write": 1
}, },
{ {

View File

@@ -571,24 +571,33 @@ class StockReconciliation(StockController):
self._cancel() self._cancel()
def recalculate_current_qty(self, item_code, batch_no): def recalculate_current_qty(self, item_code, batch_no):
from erpnext.stock.stock_ledger import get_valuation_rate
sl_entries = []
for row in self.items: for row in self.items:
if not (row.item_code == item_code and row.batch_no == batch_no): if not (row.item_code == item_code and row.batch_no == batch_no):
continue continue
row.current_qty = get_batch_qty_for_stock_reco( current_qty = get_batch_qty_for_stock_reco(
item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name
) )
qty, val_rate = get_stock_balance( precesion = row.precision("current_qty")
item_code, if flt(current_qty, precesion) == flt(row.current_qty, precesion):
row.warehouse, continue
self.posting_date,
self.posting_time, val_rate = get_valuation_rate(
with_valuation_rate=True, item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no
) )
row.current_valuation_rate = val_rate row.current_valuation_rate = val_rate
if not row.current_qty and current_qty:
sle = self.get_sle_for_items(row)
sle.actual_qty = current_qty * -1
sle.valuation_rate = val_rate
sl_entries.append(sle)
row.current_qty = current_qty
row.db_set( row.db_set(
{ {
"current_qty": row.current_qty, "current_qty": row.current_qty,
@@ -597,6 +606,9 @@ class StockReconciliation(StockController):
} }
) )
if sl_entries:
self.make_sl_entries(sl_entries)
def get_batch_qty_for_stock_reco( def get_batch_qty_for_stock_reco(
item_code, warehouse, batch_no, posting_date, posting_time, voucher_no item_code, warehouse, batch_no, posting_date, posting_time, voucher_no

View File

@@ -35,14 +35,7 @@ purchase_doctypes = [
@frappe.whitelist() @frappe.whitelist()
def get_item_details( def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True):
args,
doc=None,
for_validate=False,
overwrite_warehouse=True,
return_basic_details=False,
basic_details=None,
):
""" """
args = { args = {
"item_code": "", "item_code": "",
@@ -80,12 +73,7 @@ def get_item_details(
if doc.get("doctype") == "Purchase Invoice": if doc.get("doctype") == "Purchase Invoice":
args["bill_date"] = doc.get("bill_date") args["bill_date"] = doc.get("bill_date")
if not basic_details: out = get_basic_details(args, item, overwrite_warehouse)
out = get_basic_details(args, item, overwrite_warehouse)
else:
out = basic_details
basic_details = out.copy()
get_item_tax_template(args, item, out) get_item_tax_template(args, item, out)
out["item_tax_rate"] = get_item_tax_map( out["item_tax_rate"] = get_item_tax_map(
@@ -154,11 +142,7 @@ def get_item_details(
out.amount = flt(args.qty) * flt(out.rate) out.amount = flt(args.qty) * flt(out.rate)
out = remove_standard_fields(out) out = remove_standard_fields(out)
return out
if return_basic_details:
return out, basic_details
else:
return out
def remove_standard_fields(details): def remove_standard_fields(details):

View File

@@ -11,6 +11,13 @@ frappe.query_reports["Warehouse Wise Stock Balance"] = {
"options": "Company", "options": "Company",
"reqd": 1, "reqd": 1,
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
},
{
"fieldname":"show_disabled_warehouses",
"label": __("Show Disabled Warehouses"),
"fieldtype": "Check",
"default": 0
} }
], ],
"initial_depth": 3, "initial_depth": 3,

View File

@@ -11,6 +11,7 @@ from frappe.query_builder.functions import Sum
class StockBalanceFilter(TypedDict): class StockBalanceFilter(TypedDict):
company: Optional[str] company: Optional[str]
warehouse: Optional[str] warehouse: Optional[str]
show_disabled_warehouses: Optional[int]
SLEntry = Dict[str, Any] SLEntry = Dict[str, Any]
@@ -18,7 +19,7 @@ SLEntry = Dict[str, Any]
def execute(filters=None): def execute(filters=None):
columns, data = [], [] columns, data = [], []
columns = get_columns() columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
return columns, data return columns, data
@@ -42,10 +43,14 @@ def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
def get_warehouses(report_filters: StockBalanceFilter): def get_warehouses(report_filters: StockBalanceFilter):
filters = {"company": report_filters.company, "disabled": 0}
if report_filters.get("show_disabled_warehouses"):
filters["disabled"] = ("in", [0, report_filters.show_disabled_warehouses])
return frappe.get_all( return frappe.get_all(
"Warehouse", "Warehouse",
fields=["name", "parent_warehouse", "is_group"], fields=["name", "parent_warehouse", "is_group", "disabled"],
filters={"company": report_filters.company}, filters=filters,
order_by="lft", order_by="lft",
) )
@@ -90,8 +95,8 @@ def set_balance_in_parent(warehouses):
update_balance(warehouse, warehouse.stock_balance) update_balance(warehouse, warehouse.stock_balance)
def get_columns(): def get_columns(filters: StockBalanceFilter) -> List[Dict]:
return [ columns = [
{ {
"label": _("Warehouse"), "label": _("Warehouse"),
"fieldname": "name", "fieldname": "name",
@@ -101,3 +106,15 @@ def get_columns():
}, },
{"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150}, {"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
] ]
if filters.get("show_disabled_warehouses"):
columns.append(
{
"label": _("Warehouse Disabled?"),
"fieldname": "disabled",
"fieldtype": "Check",
"width": 200,
}
)
return columns

View File

@@ -544,6 +544,14 @@ class update_entries_after(object):
if not self.args.get("sle_id"): if not self.args.get("sle_id"):
self.get_dynamic_incoming_outgoing_rate(sle) self.get_dynamic_incoming_outgoing_rate(sle)
if (
sle.voucher_type == "Stock Reconciliation"
and sle.batch_no
and sle.voucher_detail_no
and sle.actual_qty < 0
):
self.reset_actual_qty_for_stock_reco(sle)
if ( if (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no and sle.voucher_detail_no
@@ -605,6 +613,16 @@ class update_entries_after(object):
if not self.args.get("sle_id"): if not self.args.get("sle_id"):
self.update_outgoing_rate_on_transaction(sle) self.update_outgoing_rate_on_transaction(sle)
def reset_actual_qty_for_stock_reco(self, sle):
current_qty = frappe.get_cached_value(
"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
)
if current_qty:
sle.actual_qty = current_qty * -1
elif current_qty == 0:
sle.is_cancelled = 1
def validate_negative_stock(self, sle): def validate_negative_stock(self, sle):
""" """
validate negative stock for entries current datetime onwards validate negative stock for entries current datetime onwards
@@ -1369,12 +1387,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
def regenerate_sle_for_batch_stock_reco(detail): def regenerate_sle_for_batch_stock_reco(detail):
doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no)
doc.docstatus = 2
doc.update_stock_ledger()
doc.recalculate_current_qty(detail.item_code, detail.batch_no) doc.recalculate_current_qty(detail.item_code, detail.batch_no)
doc.docstatus = 1
doc.update_stock_ledger()
doc.repost_future_sle_and_gle() doc.repost_future_sle_and_gle()
@@ -1401,34 +1414,52 @@ def get_stock_reco_qty_shift(args):
return stock_reco_qty_shift return stock_reco_qty_shift
def get_next_stock_reco(args): def get_next_stock_reco(kwargs):
"""Returns next nearest stock reconciliaton's details.""" """Returns next nearest stock reconciliaton's details."""
return frappe.db.sql( sle = frappe.qb.DocType("Stock Ledger Entry")
"""
select query = (
name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty frappe.qb.from_(sle)
from .select(
`tabStock Ledger Entry` sle.name,
where sle.posting_date,
item_code = %(item_code)s sle.posting_time,
and warehouse = %(warehouse)s sle.creation,
and voucher_type = 'Stock Reconciliation' sle.voucher_no,
and voucher_no != %(voucher_no)s sle.item_code,
and is_cancelled = 0 sle.batch_no,
and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) sle.actual_qty,
or ( )
timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) .where(
and creation > %(creation)s (sle.item_code == kwargs.get("item_code"))
& (sle.warehouse == kwargs.get("warehouse"))
& (sle.voucher_type == "Stock Reconciliation")
& (sle.voucher_no != kwargs.get("voucher_no"))
& (sle.is_cancelled == 0)
& (
(
CombineDatetime(sle.posting_date, sle.posting_time)
> CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
| (
(
CombineDatetime(sle.posting_date, sle.posting_time)
== CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
)
& (sle.creation > kwargs.get("creation"))
)
) )
) )
order by timestamp(posting_date, posting_time) asc, creation asc )
limit 1 .orderby(CombineDatetime(sle.posting_date, sle.posting_time))
""", .orderby(sle.creation)
args,
as_dict=1,
) )
if kwargs.get("batch_no"):
query.where(sle.batch_no == kwargs.get("batch_no"))
return query.run(as_dict=True)
def get_datetime_limit_condition(detail): def get_datetime_limit_condition(detail):
return f""" return f"""

View File

@@ -11,7 +11,10 @@
<div class="product-price"> <div class="product-price">
<!-- Final Price --> <!-- Final Price -->
{{ price_info.formatted_price_sales_uom }} <span itemprop="offers" itemscope itemtype="https://schema.org/Offer">
<span itemprop="price" content="{{ price_info.price_list_rate }}">{{ price_info.formatted_price_sales_uom }}</span>
<span style="display:none;" itemprop="priceCurrency" content="{{ price_info.currency }}">{{ price_info.currency }}</span>
</span>
<!-- Striked Price and Discount --> <!-- Striked Price and Discount -->
{% if price_info.formatted_mrp %} {% if price_info.formatted_mrp %}