mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-21 20:00:38 +00:00
Compare commits
1 Commits
v15.18.2
...
rohitwaghc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a748f45c9 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "15.18.2"
|
||||
__version__ = "15.16.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -148,9 +148,6 @@ def start_import(
|
||||
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
|
||||
|
||||
data = parse_data_from_template(import_file.raw_data)
|
||||
# Importer expects 'Data Import' class, which has 'payload_count' attribute
|
||||
if not data_import.get("payload_count"):
|
||||
data_import.payload_count = len(data) - 1
|
||||
|
||||
if import_file_path:
|
||||
add_bank_account(data, bank_account)
|
||||
|
||||
@@ -56,17 +56,17 @@ class BankTransaction(Document):
|
||||
Bank Transaction should be on the same currency as the Bank Account.
|
||||
"""
|
||||
if self.currency and self.bank_account:
|
||||
if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
|
||||
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
|
||||
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||
|
||||
if self.currency != account_currency:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||
).format(
|
||||
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
|
||||
)
|
||||
if self.currency != account_currency:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||
).format(
|
||||
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
|
||||
)
|
||||
)
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bisect Accounting Statements", {
|
||||
onload(frm) {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__("Bisect Left"), () => {
|
||||
frm.trigger("bisect_left");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Bisect Right"), () => {
|
||||
frm.trigger("bisect_right");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Up"), () => {
|
||||
frm.trigger("move_up");
|
||||
});
|
||||
frm.add_custom_button(__("Build Tree"), () => {
|
||||
frm.trigger("build_tree");
|
||||
});
|
||||
},
|
||||
render_heatmap(frm) {
|
||||
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
|
||||
bisect_heatmap.addClass("bisect_heatmap_location");
|
||||
|
||||
// milliseconds in a day
|
||||
let msiad = 24 * 60 * 60 * 1000;
|
||||
let datapoints = {};
|
||||
let fr_dt = new Date(frm.doc.from_date).getTime();
|
||||
let to_dt = new Date(frm.doc.to_date).getTime();
|
||||
let bisect_start = new Date(frm.doc.current_from_date).getTime();
|
||||
let bisect_end = new Date(frm.doc.current_to_date).getTime();
|
||||
|
||||
for (let x = fr_dt; x <= to_dt; x += msiad) {
|
||||
let epoch_in_seconds = x / 1000;
|
||||
if (bisect_start <= x && x <= bisect_end) {
|
||||
datapoints[epoch_in_seconds] = 1.0;
|
||||
} else {
|
||||
datapoints[epoch_in_seconds] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
new frappe.Chart(".bisect_heatmap_location", {
|
||||
type: "heatmap",
|
||||
data: {
|
||||
dataPoints: datapoints,
|
||||
start: new Date(frm.doc.from_date),
|
||||
end: new Date(frm.doc.to_date),
|
||||
},
|
||||
countLabel: "Bisecting",
|
||||
discreteDomains: 1,
|
||||
});
|
||||
},
|
||||
bisect_left(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "bisect_left",
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Left ..."),
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
bisect_right(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Right ..."),
|
||||
method: "bisect_right",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
move_up(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Moving up in tree ..."),
|
||||
method: "move_up",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
build_tree(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Rebuilding BTree for period ..."),
|
||||
method: "build_tree",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-09-15 21:28:28.054773",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_cvfg",
|
||||
"company",
|
||||
"column_break_hcam",
|
||||
"from_date",
|
||||
"column_break_qxbi",
|
||||
"to_date",
|
||||
"column_break_iwny",
|
||||
"algorithm",
|
||||
"section_break_8ph9",
|
||||
"current_node",
|
||||
"section_break_ngid",
|
||||
"bisect_heatmap",
|
||||
"section_break_hmsy",
|
||||
"bisecting_from",
|
||||
"current_from_date",
|
||||
"column_break_uqyd",
|
||||
"bisecting_to",
|
||||
"current_to_date",
|
||||
"section_break_hbyo",
|
||||
"heading_cppb",
|
||||
"p_l_summary",
|
||||
"column_break_aivo",
|
||||
"balance_sheet_summary",
|
||||
"b_s_summary",
|
||||
"column_break_gvwx",
|
||||
"difference_heading",
|
||||
"difference"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_qxbi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"default": "BFS",
|
||||
"fieldname": "algorithm",
|
||||
"fieldtype": "Select",
|
||||
"label": "Algorithm",
|
||||
"options": "BFS\nDFS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_iwny",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_node",
|
||||
"fieldtype": "Link",
|
||||
"label": "Current Node",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hmsy",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_uqyd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hbyo",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "p_l_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "b_s_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_aivo",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gvwx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hcam",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ngid",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8ph9",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "bisect_heatmap",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Heatmap"
|
||||
},
|
||||
{
|
||||
"fieldname": "heading_cppb",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference_heading",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_from",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting From"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_to",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting To"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_cvfg",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 16:49:54.073890",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Accounting Statements",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import datetime
|
||||
from collections import deque
|
||||
from math import floor
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils.data import guess_date_format
|
||||
|
||||
|
||||
class BisectAccountingStatements(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
algorithm: DF.Literal["BFS", "DFS"]
|
||||
b_s_summary: DF.Float
|
||||
company: DF.Link | None
|
||||
current_from_date: DF.Datetime | None
|
||||
current_node: DF.Link | None
|
||||
current_to_date: DF.Datetime | None
|
||||
difference: DF.Float
|
||||
from_date: DF.Datetime | None
|
||||
p_l_summary: DF.Float
|
||||
to_date: DF.Datetime | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
frappe.throw(
|
||||
_("From Date: {0} cannot be greater than To date: {1}").format(
|
||||
frappe.bold(self.from_date), frappe.bold(self.to_date)
|
||||
)
|
||||
)
|
||||
|
||||
def bfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_queue = deque([node])
|
||||
while period_queue:
|
||||
cur_node = period_queue.popleft()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_queue.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_queue.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
def dfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_stack = [node]
|
||||
while period_stack:
|
||||
cur_node = period_stack.pop()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_stack.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_stack.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree(self):
|
||||
frappe.db.delete("Bisect Nodes")
|
||||
|
||||
# Convert str to datetime format
|
||||
dt_format = guess_date_format(self.from_date)
|
||||
from_date = datetime.datetime.strptime(self.from_date, dt_format)
|
||||
to_date = datetime.datetime.strptime(self.to_date, dt_format)
|
||||
|
||||
if self.algorithm == "BFS":
|
||||
self.bfs(from_date, to_date)
|
||||
|
||||
if self.algorithm == "DFS":
|
||||
self.dfs(from_date, to_date)
|
||||
|
||||
# set root as current node
|
||||
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
|
||||
self.get_report_summary()
|
||||
self.current_node = root.name
|
||||
self.current_from_date = self.from_date
|
||||
self.current_to_date = self.to_date
|
||||
self.save()
|
||||
|
||||
def get_report_summary(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": self.current_from_date,
|
||||
"period_end_date": self.current_to_date,
|
||||
"periodicity": "Yearly",
|
||||
}
|
||||
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
|
||||
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
|
||||
bs_summary = frappe.get_doc("Report", "Balance Sheet")
|
||||
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def update_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
current_node.balance_sheet_summary = self.b_s_summary
|
||||
current_node.profit_loss_summary = self.p_l_summary
|
||||
current_node.difference = self.difference
|
||||
current_node.generated = True
|
||||
current_node.save()
|
||||
|
||||
def current_node_has_summary_info(self):
|
||||
"Assertion method"
|
||||
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
|
||||
|
||||
def fetch_summary_info_from_current_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
self.p_l_summary = current_node.balance_sheet_summary
|
||||
self.b_s_summary = current_node.profit_loss_summary
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def fetch_or_calculate(self):
|
||||
if self.current_node_has_summary_info():
|
||||
self.fetch_summary_info_from_current_node()
|
||||
else:
|
||||
self.get_report_summary()
|
||||
self.update_node()
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_left(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.left_child is not None:
|
||||
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
|
||||
self.current_node = cur_node.left_child
|
||||
self.current_from_date = lft_node.period_from_date
|
||||
self.current_to_date = lft_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Left"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_right(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.right_child is not None:
|
||||
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
|
||||
self.current_node = cur_node.right_child
|
||||
self.current_from_date = rgt_node.period_from_date
|
||||
self.current_to_date = rgt_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Right"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_up(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.root is not None:
|
||||
root = frappe.get_doc("Bisect Nodes", cur_node.root)
|
||||
self.current_node = cur_node.root
|
||||
self.current_from_date = root.period_from_date
|
||||
self.current_to_date = root.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("Reached Root"))
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBisectAccountingStatements(FrappeTestCase):
|
||||
pass
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Bisect Nodes", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,97 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2023-09-27 14:56:38.112462",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"root",
|
||||
"left_child",
|
||||
"right_child",
|
||||
"period_from_date",
|
||||
"period_to_date",
|
||||
"difference",
|
||||
"balance_sheet_summary",
|
||||
"profit_loss_summary",
|
||||
"generated"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "root",
|
||||
"fieldtype": "Link",
|
||||
"label": "Root",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "left_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Left Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "right_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Right Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period_from_date"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "profit_loss_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generated"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 17:46:12.437996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Nodes",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BisectNodes(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
balance_sheet_summary: DF.Float
|
||||
difference: DF.Float
|
||||
generated: DF.Check
|
||||
left_child: DF.Link | None
|
||||
name: DF.Int | None
|
||||
period_from_date: DF.Datetime | None
|
||||
period_to_date: DF.Datetime | None
|
||||
profit_loss_summary: DF.Float
|
||||
right_child: DF.Link | None
|
||||
root: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBisectNodes(FrappeTestCase):
|
||||
pass
|
||||
@@ -3,36 +3,22 @@
|
||||
|
||||
frappe.ui.form.on("Currency Exchange Settings", {
|
||||
service_provider: function (frm) {
|
||||
frm.call({
|
||||
method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
|
||||
args: {
|
||||
service_provider: frm.doc.service_provider,
|
||||
use_http: frm.doc.use_http,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r && r.message) {
|
||||
if (frm.doc.service_provider == "exchangerate.host") {
|
||||
let result = ["result"];
|
||||
let params = {
|
||||
date: "{transaction_date}",
|
||||
from: "{from_currency}",
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
symbols: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
use_http: function (frm) {
|
||||
frm.trigger("service_provider");
|
||||
if (frm.doc.service_provider == "exchangerate.host") {
|
||||
let result = ["result"];
|
||||
let params = {
|
||||
date: "{transaction_date}",
|
||||
from: "{from_currency}",
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, "https://api.exchangerate.host/convert", params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
symbols: "{to_currency}",
|
||||
};
|
||||
add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"disabled",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"use_http",
|
||||
"access_key",
|
||||
"url",
|
||||
"column_break_3",
|
||||
@@ -92,19 +91,12 @@
|
||||
"fieldname": "access_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Key"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.service_provider != \"Custom\"",
|
||||
"fieldname": "use_http",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-18 08:32:26.895076",
|
||||
"modified": "2023-10-04 15:30:25.333860",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -31,7 +31,6 @@ class CurrencyExchangeSettings(Document):
|
||||
result_key: DF.Table[CurrencyExchangeSettingsResult]
|
||||
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
|
||||
url: DF.Data | None
|
||||
use_http: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
@@ -54,7 +53,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
|
||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||
self.append("result_key", {"key": "result"})
|
||||
self.append("req_params", {"key": "access_key", "value": self.access_key})
|
||||
self.append("req_params", {"key": "amount", "value": "1"})
|
||||
@@ -65,7 +64,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
|
||||
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
|
||||
self.append("result_key", {"key": "rates"})
|
||||
self.append("result_key", {"key": "{to_currency}"})
|
||||
self.append("req_params", {"key": "base", "value": "{from_currency}"})
|
||||
@@ -104,19 +103,3 @@ class CurrencyExchangeSettings(Document):
|
||||
frappe.throw(_("Returned exchange rate is neither integer not float."))
|
||||
|
||||
self.url = response.url
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_api_endpoint(service_provider: str = None, use_http: bool = False):
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
|
||||
if service_provider == "exchangerate.host":
|
||||
api = "api.exchangerate.host/convert"
|
||||
elif service_provider == "frankfurter.app":
|
||||
api = "frankfurter.app/{transaction_date}"
|
||||
|
||||
protocol = "https://"
|
||||
if use_http:
|
||||
protocol = "http://"
|
||||
|
||||
return protocol + api
|
||||
return None
|
||||
|
||||
@@ -628,21 +628,21 @@ def get_account_details(
|
||||
if account_balance and (
|
||||
account_balance[0].balance or account_balance[0].balance_in_account_currency
|
||||
):
|
||||
if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
|
||||
account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
|
||||
company, posting_date, account_balance
|
||||
):
|
||||
row = account_with_new_balance[0]
|
||||
account_details.update(
|
||||
{
|
||||
"balance_in_base_currency": row["balance_in_base_currency"],
|
||||
"balance_in_account_currency": row["balance_in_account_currency"],
|
||||
"current_exchange_rate": row["current_exchange_rate"],
|
||||
"new_exchange_rate": row["new_exchange_rate"],
|
||||
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
|
||||
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
|
||||
"zero_balance": row["zero_balance"],
|
||||
"gain_loss": row["gain_loss"],
|
||||
}
|
||||
)
|
||||
)
|
||||
row = account_with_new_balance[0]
|
||||
account_details.update(
|
||||
{
|
||||
"balance_in_base_currency": row["balance_in_base_currency"],
|
||||
"balance_in_account_currency": row["balance_in_account_currency"],
|
||||
"current_exchange_rate": row["current_exchange_rate"],
|
||||
"new_exchange_rate": row["new_exchange_rate"],
|
||||
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
|
||||
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
|
||||
"zero_balance": row["zero_balance"],
|
||||
"gain_loss": row["gain_loss"],
|
||||
}
|
||||
)
|
||||
|
||||
return account_details
|
||||
|
||||
@@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
!(frm.doc.accounts || []).length ||
|
||||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
|
||||
) {
|
||||
if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
|
||||
if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
|
||||
@@ -308,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
filters: [[jvd.reference_type, "docstatus", "=", 1]],
|
||||
};
|
||||
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
|
||||
if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
|
||||
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
|
||||
// Filter by cost center
|
||||
if (jvd.cost_center) {
|
||||
@@ -320,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
|
||||
}
|
||||
|
||||
if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
|
||||
if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
|
||||
// party_type and party mandatory
|
||||
frappe.model.validate_missing(jvd, "party_type");
|
||||
frappe.model.validate_missing(jvd, "party");
|
||||
|
||||
@@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_query("paid_from", function () {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
return {
|
||||
@@ -87,7 +87,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_query("paid_to", function () {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
return {
|
||||
@@ -134,7 +134,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_query("payment_term", "references", function (frm, cdt, cdn) {
|
||||
const child = locals[cdt][cdn];
|
||||
if (
|
||||
["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) &&
|
||||
in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) &&
|
||||
child.reference_name
|
||||
) {
|
||||
return {
|
||||
@@ -395,6 +395,10 @@ frappe.ui.form.on("Payment Entry", {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
};
|
||||
} else if (frm.doc.party_type == "Customer") {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.customer_query",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -623,7 +627,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
if (frm.doc.paid_from_account_currency == company_currency) {
|
||||
frm.set_value("source_exchange_rate", 1);
|
||||
} else if (frm.doc.paid_from) {
|
||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
||||
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
@@ -1042,7 +1046,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
}
|
||||
|
||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
||||
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
|
||||
if (paid_amount > total_negative_outstanding) {
|
||||
if (total_negative_outstanding == 0) {
|
||||
@@ -1213,7 +1217,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
if (
|
||||
frm.doc.party_type == "Customer" &&
|
||||
!["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
|
||||
!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
|
||||
) {
|
||||
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
|
||||
frappe.msgprint(
|
||||
@@ -1227,7 +1231,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
if (
|
||||
frm.doc.party_type == "Supplier" &&
|
||||
!["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
|
||||
!in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
|
||||
) {
|
||||
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
|
||||
frappe.msgprint(
|
||||
@@ -1323,7 +1327,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
bank_account: function (frm) {
|
||||
const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to";
|
||||
if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) {
|
||||
if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
|
||||
args: {
|
||||
|
||||
@@ -404,9 +404,7 @@ class PaymentEntry(AccountsController):
|
||||
ref_details = get_reference_details(
|
||||
d.reference_doctype, d.reference_name, self.party_account_currency
|
||||
)
|
||||
|
||||
# Only update exchange rate when the reference is Journal Entry
|
||||
if ref_exchange_rate and d.reference_doctype == "Journal Entry":
|
||||
if ref_exchange_rate:
|
||||
ref_details.update({"exchange_rate": ref_exchange_rate})
|
||||
|
||||
for field, value in ref_details.items():
|
||||
@@ -528,9 +526,9 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
def get_valid_reference_doctypes(self):
|
||||
if self.party_type == "Customer":
|
||||
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
|
||||
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
||||
elif self.party_type == "Supplier":
|
||||
return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
|
||||
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||
elif self.party_type == "Shareholder":
|
||||
return ("Journal Entry",)
|
||||
elif self.party_type == "Employee":
|
||||
@@ -1193,7 +1191,6 @@ class PaymentEntry(AccountsController):
|
||||
"Journal Entry",
|
||||
"Sales Order",
|
||||
"Purchase Order",
|
||||
"Payment Entry",
|
||||
):
|
||||
self.add_advance_gl_for_reference(gl_entries, ref)
|
||||
|
||||
@@ -1216,9 +1213,7 @@ class PaymentEntry(AccountsController):
|
||||
if getdate(posting_date) < getdate(self.posting_date):
|
||||
posting_date = self.posting_date
|
||||
|
||||
dr_or_cr = (
|
||||
"credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit"
|
||||
)
|
||||
dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
|
||||
args_dict["account"] = invoice.account
|
||||
args_dict[dr_or_cr] = invoice.allocated_amount
|
||||
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
|
||||
@@ -1665,7 +1660,7 @@ def get_outstanding_reference_documents(args, validate=False):
|
||||
outstanding_invoices = get_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
[party_account],
|
||||
party_account,
|
||||
common_filter=common_filter,
|
||||
posting_date=posting_and_due_date,
|
||||
min_outstanding=args.get("outstanding_amt_greater_than"),
|
||||
|
||||
@@ -1514,168 +1514,6 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
def test_reverse_payment_reconciliation(self):
|
||||
customer = create_customer(frappe.generate_hash(length=10), "INR")
|
||||
pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
payment_type="Receive",
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Cash - _TC",
|
||||
)
|
||||
pe.submit()
|
||||
|
||||
reverse_pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
payment_type="Pay",
|
||||
paid_from="_Test Cash - _TC",
|
||||
paid_to="Debtors - _TC",
|
||||
)
|
||||
reverse_pe.submit()
|
||||
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = "_Test Company"
|
||||
pr.party_type = "Customer"
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = "Debtors - _TC"
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number)
|
||||
self.assertEqual(pe.name, pr.payments[0].reference_name)
|
||||
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [pr.payments[0].as_dict()]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
def test_advance_reverse_payment_reconciliation(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
|
||||
company = "_Test Company"
|
||||
customer = create_customer(frappe.generate_hash(length=10), "INR")
|
||||
advance_account = create_account(
|
||||
parent_account="Current Assets - _TC",
|
||||
account_name="Advances Received",
|
||||
company=company,
|
||||
account_type="Receivable",
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
company,
|
||||
{
|
||||
"book_advance_payments_in_separate_party_account": 1,
|
||||
"default_advance_received_account": advance_account,
|
||||
},
|
||||
)
|
||||
# Reverse Payment(essentially an Invoice)
|
||||
reverse_pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
payment_type="Pay",
|
||||
paid_from="_Test Cash - _TC",
|
||||
paid_to=advance_account,
|
||||
)
|
||||
reverse_pe.save() # use save() to trigger set_liability_account()
|
||||
reverse_pe.submit()
|
||||
|
||||
# Advance Payment
|
||||
pe = create_payment_entry(
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
payment_type="Receive",
|
||||
paid_from=advance_account,
|
||||
paid_to="_Test Cash - _TC",
|
||||
)
|
||||
pe.save() # use save() to trigger set_liability_account()
|
||||
pe.submit()
|
||||
|
||||
# Partially reconcile advance against invoice
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = company
|
||||
pr.party_type = "Customer"
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = "Debtors - _TC"
|
||||
pr.default_advance_account = advance_account
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||
payments = [x.as_dict() for x in pr.get("payments")]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.allocation[0].allocated_amount = 400
|
||||
pr.reconcile()
|
||||
|
||||
# assert General and Payment Ledger entries post partial reconciliation
|
||||
self.expected_gle = [
|
||||
{"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0},
|
||||
{"account": advance_account, "debit": 400.0, "credit": 0.0},
|
||||
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.expected_ple = [
|
||||
{
|
||||
"account": advance_account,
|
||||
"voucher_no": pe.name,
|
||||
"against_voucher_no": pe.name,
|
||||
"amount": -1000.0,
|
||||
},
|
||||
{
|
||||
"account": "Debtors - _TC",
|
||||
"voucher_no": pe.name,
|
||||
"against_voucher_no": reverse_pe.name,
|
||||
"amount": -400.0,
|
||||
},
|
||||
{
|
||||
"account": advance_account,
|
||||
"voucher_no": pe.name,
|
||||
"against_voucher_no": pe.name,
|
||||
"amount": 400.0,
|
||||
},
|
||||
]
|
||||
self.voucher_no = pe.name
|
||||
self.check_gl_entries()
|
||||
self.check_pl_entries()
|
||||
|
||||
# Unreconcile
|
||||
unrecon = (
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
"allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}],
|
||||
}
|
||||
)
|
||||
.save()
|
||||
.submit()
|
||||
)
|
||||
|
||||
# assert General and Payment Ledger entries post unreconciliation
|
||||
self.expected_gle = [
|
||||
{"account": advance_account, "debit": 0.0, "credit": 1000.0},
|
||||
{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
|
||||
]
|
||||
self.expected_ple = [
|
||||
{
|
||||
"account": advance_account,
|
||||
"voucher_no": pe.name,
|
||||
"against_voucher_no": pe.name,
|
||||
"amount": -1000.0,
|
||||
},
|
||||
]
|
||||
self.voucher_no = pe.name
|
||||
self.check_gl_entries()
|
||||
self.check_pl_entries()
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -254,7 +254,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.data = [];
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Difference Account"),
|
||||
size: "extra-large",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "allocation",
|
||||
@@ -280,13 +279,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
in_list_view: 1,
|
||||
read_only: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
fieldname: "gain_loss_posting_date",
|
||||
label: __("Posting Date"),
|
||||
in_list_view: 1,
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
options: "Account",
|
||||
@@ -327,12 +319,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
"difference_account",
|
||||
d.difference_account
|
||||
);
|
||||
frappe.model.set_value(
|
||||
"Payment Reconciliation Allocation",
|
||||
d.docname,
|
||||
"gain_loss_posting_date",
|
||||
d.gain_loss_posting_date
|
||||
);
|
||||
});
|
||||
|
||||
this.reconcile_payment_entries();
|
||||
@@ -348,7 +334,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
reference_name: d.reference_name,
|
||||
difference_amount: d.difference_amount,
|
||||
difference_account: d.difference_account,
|
||||
gain_loss_posting_date: d.gain_loss_posting_date,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -340,15 +340,10 @@ class PaymentReconciliation(Document):
|
||||
|
||||
self.build_qb_filter_conditions(get_invoices=True)
|
||||
|
||||
accounts = [self.receivable_payable_account]
|
||||
|
||||
if self.default_advance_account:
|
||||
accounts.append(self.default_advance_account)
|
||||
|
||||
non_reconciled_invoices = get_outstanding_invoices(
|
||||
self.party_type,
|
||||
self.party,
|
||||
accounts,
|
||||
self.receivable_payable_account,
|
||||
common_filter=self.common_filter_conditions,
|
||||
posting_date=self.ple_posting_date_filter,
|
||||
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
|
||||
@@ -446,7 +441,6 @@ class PaymentReconciliation(Document):
|
||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||
res.difference_account = default_exchange_gain_loss_account
|
||||
res.exchange_rate = inv.get("exchange_rate")
|
||||
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||
|
||||
if pay.get("amount") == 0:
|
||||
entries.append(res)
|
||||
@@ -563,7 +557,6 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": flt(row.get("allocated_amount")),
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1130,17 +1130,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pr.allocation[0].allocated_amount, 85)
|
||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||
|
||||
pr.reconcile()
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 0)
|
||||
# No Exchange Gain/Loss journal should be generated
|
||||
exc_gain_loss_journals = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1},
|
||||
fields=["parent"],
|
||||
)
|
||||
self.assertEqual(exc_gain_loss_journals, [])
|
||||
|
||||
def test_reconciliation_purchase_invoice_against_return(self):
|
||||
self.supplier = "_Test Supplier USD"
|
||||
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"is_advance",
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
|
||||
@@ -28,7 +28,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
|
||||
if (
|
||||
frm.doc.payment_request_type == "Inward" &&
|
||||
frm.doc.payment_channel !== "Phone" &&
|
||||
!["Initiated", "Paid"].includes(frm.doc.status) &&
|
||||
!in_list(["Initiated", "Paid"], frm.doc.status) &&
|
||||
!frm.doc.__islocal &&
|
||||
frm.doc.docstatus == 1
|
||||
) {
|
||||
|
||||
@@ -141,8 +141,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
|
||||
|
||||
if previous_fiscal_year and not frappe.db.exists(
|
||||
"GL Entry",
|
||||
{"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0},
|
||||
"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
@@ -775,7 +775,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1563,7 +1563,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:00:34.268756",
|
||||
"modified": "2023-11-20 12:27:12.848149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -109,7 +109,7 @@ class POSInvoice(SalesInvoice):
|
||||
loyalty_redemption_cost_center: DF.Link | None
|
||||
naming_series: DF.Literal["ACC-PSINV-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
other_charges_calculation: DF.LongText | None
|
||||
outstanding_amount: DF.Currency
|
||||
packed_items: DF.Table[PackedItem]
|
||||
paid_amount: DF.Currency
|
||||
|
||||
@@ -89,11 +89,10 @@
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">0 - 30 Days</th>
|
||||
<th style="width: 20%">30 - 60 Days</th>
|
||||
<th style="width: 20%">60 - 90 Days</th>
|
||||
<th style="width: 20%">90 - 120 Days</th>
|
||||
<th style="width: 20%">Above 120 Days</th>
|
||||
<th style="width: 25%">30 Days</th>
|
||||
<th style="width: 25%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 25%">120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -102,7 +101,6 @@
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
|
||||
|
||||
erpnext.accounts.payment_triggers.setup("Purchase Invoice");
|
||||
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
|
||||
erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice");
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"is_paid",
|
||||
"is_return",
|
||||
"return_against",
|
||||
"update_outstanding_for_self",
|
||||
"update_billed_amount_in_purchase_order",
|
||||
"update_billed_amount_in_purchase_receipt",
|
||||
"apply_tds",
|
||||
@@ -760,7 +759,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1623,21 +1622,13 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Group",
|
||||
"options": "Supplier Group"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||
"description": "Debit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
|
||||
"fieldname": "update_outstanding_for_self",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Outstanding for Self"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 15:57:00.736868",
|
||||
"modified": "2024-02-25 11:20:28.366808",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -147,7 +147,7 @@ class PurchaseInvoice(BuyingController):
|
||||
net_total: DF.Currency
|
||||
on_hold: DF.Check
|
||||
only_include_allocated_payments: DF.Check
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
other_charges_calculation: DF.LongText | None
|
||||
outstanding_amount: DF.Currency
|
||||
paid_amount: DF.Currency
|
||||
party_account_currency: DF.Link | None
|
||||
@@ -216,7 +216,6 @@ class PurchaseInvoice(BuyingController):
|
||||
unrealized_profit_loss_account: DF.Link | None
|
||||
update_billed_amount_in_purchase_order: DF.Check
|
||||
update_billed_amount_in_purchase_receipt: DF.Check
|
||||
update_outstanding_for_self: DF.Check
|
||||
update_stock: DF.Check
|
||||
use_company_roundoff_cost_center: DF.Check
|
||||
use_transaction_date_exchange_rate: DF.Check
|
||||
@@ -829,10 +828,6 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
against_voucher = self.name
|
||||
if self.is_return and self.return_against and not self.update_outstanding_for_self:
|
||||
against_voucher = self.return_against
|
||||
|
||||
# Did not use base_grand_total to book rounding loss gle
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -846,7 +841,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"credit_in_account_currency": base_grand_total
|
||||
if self.party_account_currency == self.company_currency
|
||||
else grand_total,
|
||||
"against_voucher": against_voucher,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"project": self.project,
|
||||
"cost_center": self.cost_center,
|
||||
|
||||
@@ -745,7 +745,6 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Landed Cost Voucher Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -939,7 +938,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-19 19:09:47.210965",
|
||||
"modified": "2024-02-04 14:11:52.742228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
|
||||
erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template");
|
||||
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
cur_frm.cscript.tax_table = "Sales Taxes and Charges";
|
||||
|
||||
erpnext.accounts.taxes.setup_tax_validations("Sales Invoice");
|
||||
erpnext.accounts.payment_triggers.setup("Sales Invoice");
|
||||
erpnext.accounts.pos.setup("Sales Invoice");
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"is_consolidated",
|
||||
"is_return",
|
||||
"return_against",
|
||||
"update_outstanding_for_self",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"is_debit_note",
|
||||
@@ -944,7 +943,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Long Text",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Taxes and Charges Calculation",
|
||||
@@ -2163,16 +2162,6 @@
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||
"description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
|
||||
"fieldname": "update_outstanding_for_self",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Outstanding for Self",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2185,7 +2174,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-22 17:50:34.395602",
|
||||
"modified": "2023-11-23 16:56:29.679499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -146,7 +146,7 @@ class SalesInvoice(SellingController):
|
||||
naming_series: DF.Literal["ACC-SINV-.YYYY.-", "ACC-SINV-RET-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
only_include_allocated_payments: DF.Check
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
other_charges_calculation: DF.LongText | None
|
||||
outstanding_amount: DF.Currency
|
||||
packed_items: DF.Table[PackedItem]
|
||||
paid_amount: DF.Currency
|
||||
@@ -220,7 +220,6 @@ class SalesInvoice(SellingController):
|
||||
unrealized_profit_loss_account: DF.Link | None
|
||||
update_billed_amount_in_delivery_note: DF.Check
|
||||
update_billed_amount_in_sales_order: DF.Check
|
||||
update_outstanding_for_self: DF.Check
|
||||
update_stock: DF.Check
|
||||
use_company_roundoff_cost_center: DF.Check
|
||||
write_off_account: DF.Link | None
|
||||
@@ -1229,10 +1228,6 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
against_voucher = self.name
|
||||
if self.is_return and self.return_against and not self.update_outstanding_for_self:
|
||||
against_voucher = self.return_against
|
||||
|
||||
# Did not use base_grand_total to book rounding loss gle
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -1246,7 +1241,7 @@ class SalesInvoice(SellingController):
|
||||
"debit_in_account_currency": base_grand_total
|
||||
if self.party_account_currency == self.company_currency
|
||||
else grand_total,
|
||||
"against_voucher": against_voucher,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
|
||||
@@ -1571,12 +1571,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
|
||||
|
||||
def test_zero_qty_return_invoice_with_stock_effect(self):
|
||||
cr_note = create_sales_invoice(qty=-1, rate=300, is_return=1, do_not_submit=True)
|
||||
cr_note.update_stock = True
|
||||
cr_note.items[0].qty = 0
|
||||
self.assertRaises(frappe.ValidationError, cr_note.save)
|
||||
|
||||
def test_return_invoice_with_account_mismatch(self):
|
||||
debtors2 = create_account(
|
||||
parent_account="Accounts Receivable - _TC",
|
||||
@@ -3938,6 +3932,7 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
|
||||
)
|
||||
|
||||
supplier.append("companies", {"company": allowed_to_interact_with})
|
||||
|
||||
supplier.insert()
|
||||
supplier_name = supplier.name
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.cscript.tax_table = "Sales Taxes and Charges";
|
||||
erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
|
||||
erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
|
||||
|
||||
@@ -410,11 +410,11 @@ class Subscription(Document):
|
||||
# Earlier subscription didn't had any company field
|
||||
company = self.get("company") or get_default_company()
|
||||
if not company:
|
||||
# fmt: off
|
||||
frappe.throw(
|
||||
_(
|
||||
"Company is mandatory for generating an invoice. Please set a default company in Global Defaults."
|
||||
)
|
||||
_("Company is mandatory was generating invoice. Please set default company in Global Defaults.")
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
invoice = frappe.new_doc(self.invoice_document_type)
|
||||
invoice.company = company
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-02-04 10:53:32.307930",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doctype_name",
|
||||
"docfield_name",
|
||||
"no_of_docs",
|
||||
"done"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "doctype_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "docfield_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "DocField",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_docs",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "No of Docs",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "done",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Done",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-05 17:35:09.556054",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Transaction Deletion Record Details",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class TransactionDeletionRecordDetails(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
docfield_name: DF.Data | None
|
||||
doctype_name: DF.Link
|
||||
done: DF.Check
|
||||
no_of_docs: DF.Int
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -690,12 +690,7 @@ class ReceivablePayableReport(object):
|
||||
|
||||
def get_return_entries(self):
|
||||
doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice"
|
||||
filters = {
|
||||
"is_return": 1,
|
||||
"docstatus": 1,
|
||||
"company": self.filters.company,
|
||||
"update_outstanding_for_self": 0,
|
||||
}
|
||||
filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
|
||||
or_filters = {}
|
||||
for party_type in self.party_type:
|
||||
party_field = scrub(party_type)
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
def create_credit_note(self, docname, do_not_submit=False):
|
||||
def create_credit_note(self, docname):
|
||||
credit_note = create_sales_invoice(
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
@@ -72,7 +72,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
cost_center=self.cost_center,
|
||||
is_return=1,
|
||||
return_against=docname,
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
|
||||
return credit_note
|
||||
@@ -150,9 +149,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
)
|
||||
|
||||
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
|
||||
cr_note = self.create_credit_note(si.name, do_not_submit=True)
|
||||
cr_note.update_outstanding_for_self = False
|
||||
cr_note.save().submit()
|
||||
self.create_credit_note(si.name)
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to]
|
||||
@@ -170,82 +167,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_cr_note_flag_to_update_self(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"show_remarks": True,
|
||||
}
|
||||
|
||||
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
si.set_posting_time = True
|
||||
si.posting_date = add_days(today(), -1)
|
||||
si.save().submit()
|
||||
|
||||
report = execute(filters)
|
||||
|
||||
expected_data = [100, 100, "No Remarks"]
|
||||
|
||||
self.assertEqual(len(report[1]), 1)
|
||||
row = report[1][0]
|
||||
self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
|
||||
|
||||
# check invoice grand total, invoiced, paid and outstanding column's value after payment
|
||||
self.create_payment_entry(si.name)
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_payment = [100, 100, 40, 60]
|
||||
self.assertEqual(len(report[1]), 1)
|
||||
row = report[1][0]
|
||||
self.assertEqual(
|
||||
expected_data_after_payment,
|
||||
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
|
||||
)
|
||||
|
||||
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
|
||||
cr_note = self.create_credit_note(si.name, do_not_submit=True)
|
||||
cr_note.update_outstanding_for_self = True
|
||||
cr_note.save().submit()
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_credit_note = [
|
||||
[100.0, 100.0, 40.0, 0.0, 60.0, si.name],
|
||||
[0, 0, 100.0, 0.0, -100.0, cr_note.name],
|
||||
]
|
||||
self.assertEqual(len(report[1]), 2)
|
||||
si_row = [
|
||||
[
|
||||
row.invoice_grand_total,
|
||||
row.invoiced,
|
||||
row.paid,
|
||||
row.credit_note,
|
||||
row.outstanding,
|
||||
row.voucher_no,
|
||||
]
|
||||
for row in report[1]
|
||||
if row.voucher_no == si.name
|
||||
][0]
|
||||
|
||||
cr_note_row = [
|
||||
[
|
||||
row.invoice_grand_total,
|
||||
row.invoiced,
|
||||
row.paid,
|
||||
row.credit_note,
|
||||
row.outstanding,
|
||||
row.voucher_no,
|
||||
]
|
||||
for row in report[1]
|
||||
if row.voucher_no == cr_note.name
|
||||
][0]
|
||||
self.assertEqual(expected_data_after_credit_note[0], si_row)
|
||||
self.assertEqual(expected_data_after_credit_note[1], cr_note_row)
|
||||
|
||||
def test_payment_againt_po_in_receivable_report(self):
|
||||
"""
|
||||
Payments made against Purchase Order will show up as outstanding amount
|
||||
|
||||
@@ -97,11 +97,11 @@ def execute(filters=None):
|
||||
|
||||
chart = get_chart_data(filters, columns, asset, liability, equity)
|
||||
|
||||
report_summary, primitive_summary = get_report_summary(
|
||||
report_summary = get_report_summary(
|
||||
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
||||
)
|
||||
|
||||
return columns, data, message, chart, report_summary, primitive_summary
|
||||
return columns, data, message, chart, report_summary
|
||||
|
||||
|
||||
def get_provisional_profit_loss(
|
||||
@@ -217,7 +217,7 @@ def get_report_summary(
|
||||
"datatype": "Currency",
|
||||
"currency": currency,
|
||||
},
|
||||
], (net_asset - net_liability + net_equity)
|
||||
]
|
||||
|
||||
|
||||
def get_chart_data(filters, columns, asset, liability, equity):
|
||||
|
||||
@@ -669,20 +669,20 @@ class GrossProfitGenerator(object):
|
||||
elif row.sales_order and row.so_detail:
|
||||
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
|
||||
if incoming_amount:
|
||||
return flt(row.qty) * incoming_amount
|
||||
return incoming_amount
|
||||
else:
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
|
||||
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
||||
from frappe.query_builder.functions import Avg
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(delivery_note_item)
|
||||
.select(Avg(delivery_note_item.incoming_rate))
|
||||
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
|
||||
.where(delivery_note_item.docstatus == 1)
|
||||
.where(delivery_note_item.item_code == item_code)
|
||||
.where(delivery_note_item.against_sales_order == sales_order)
|
||||
|
||||
@@ -460,95 +460,3 @@ class TestGrossProfit(FrappeTestCase):
|
||||
}
|
||||
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||
|
||||
def test_different_rates_in_si_and_dn(self):
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
"""
|
||||
Test gp calculation when invoice and delivery note differ in qty and aren't connected
|
||||
SO -- INV
|
||||
|
|
||||
DN
|
||||
"""
|
||||
se = make_stock_entry(
|
||||
company=self.company,
|
||||
item_code=self.item,
|
||||
target=self.warehouse,
|
||||
qty=3,
|
||||
basic_rate=700,
|
||||
do_not_submit=True,
|
||||
)
|
||||
item = se.items[0]
|
||||
se.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"s_warehouse": item.s_warehouse,
|
||||
"t_warehouse": item.t_warehouse,
|
||||
"qty": 10,
|
||||
"basic_rate": 700,
|
||||
"conversion_factor": item.conversion_factor or 1.0,
|
||||
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
|
||||
"serial_no": item.serial_no,
|
||||
"batch_no": item.batch_no,
|
||||
"cost_center": item.cost_center,
|
||||
"expense_account": item.expense_account,
|
||||
},
|
||||
)
|
||||
se = se.save().submit()
|
||||
|
||||
so = make_sales_order(
|
||||
customer=self.customer,
|
||||
company=self.company,
|
||||
warehouse=self.warehouse,
|
||||
item=self.item,
|
||||
rate=800,
|
||||
qty=10,
|
||||
do_not_save=False,
|
||||
do_not_submit=False,
|
||||
)
|
||||
|
||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||
make_delivery_note,
|
||||
make_sales_invoice,
|
||||
)
|
||||
|
||||
dn1 = make_delivery_note(so.name)
|
||||
dn1.items[0].qty = 4
|
||||
dn1.items[0].rate = 800
|
||||
dn1.save().submit()
|
||||
|
||||
dn2 = make_delivery_note(so.name)
|
||||
dn2.items[0].qty = 6
|
||||
dn2.items[0].rate = 800
|
||||
dn2.save().submit()
|
||||
|
||||
sinv = make_sales_invoice(so.name)
|
||||
sinv.items[0].qty = 4
|
||||
sinv.items[0].rate = 800
|
||||
sinv.save().submit()
|
||||
|
||||
filters = frappe._dict(
|
||||
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
|
||||
)
|
||||
|
||||
columns, data = execute(filters=filters)
|
||||
expected_entry = {
|
||||
"parent_invoice": sinv.name,
|
||||
"currency": "INR",
|
||||
"sales_invoice": self.item,
|
||||
"customer": self.customer,
|
||||
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
|
||||
"item_code": self.item,
|
||||
"item_name": self.item,
|
||||
"warehouse": "Stores - _GP",
|
||||
"qty": 4.0,
|
||||
"avg._selling_rate": 800.0,
|
||||
"valuation_rate": 700.0,
|
||||
"selling_amount": 3200.0,
|
||||
"buying_amount": 2800.0,
|
||||
"gross_profit": 400.0,
|
||||
"gross_profit_%": 12.5,
|
||||
}
|
||||
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||
|
||||
@@ -24,10 +24,3 @@ frappe.query_reports["Profit and Loss Statement"]["filters"].push({
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
|
||||
fieldname: "include_default_book_entries",
|
||||
label: __("Include Default FB Entries"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
@@ -66,11 +66,11 @@ def execute(filters=None):
|
||||
currency = filters.presentation_currency or frappe.get_cached_value(
|
||||
"Company", filters.company, "default_currency"
|
||||
)
|
||||
report_summary, primitive_summary = get_report_summary(
|
||||
report_summary = get_report_summary(
|
||||
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
|
||||
)
|
||||
|
||||
return columns, data, None, chart, report_summary, primitive_summary
|
||||
return columns, data, None, chart, report_summary
|
||||
|
||||
|
||||
def get_report_summary(
|
||||
@@ -123,7 +123,7 @@ def get_report_summary(
|
||||
"datatype": "Currency",
|
||||
"currency": currency,
|
||||
},
|
||||
], net_profit
|
||||
]
|
||||
|
||||
|
||||
def get_net_profit_loss(income, expense, period_list, company, currency=None, consolidated=False):
|
||||
|
||||
@@ -1015,7 +1015,7 @@ def get_outstanding_invoices(
|
||||
|
||||
if account:
|
||||
root_type, account_type = frappe.get_cached_value(
|
||||
"Account", account[0], ["root_type", "account_type"]
|
||||
"Account", account, ["root_type", "account_type"]
|
||||
)
|
||||
party_account_type = "Receivable" if root_type == "Asset" else "Payable"
|
||||
party_account_type = account_type or party_account_type
|
||||
@@ -1026,7 +1026,7 @@ def get_outstanding_invoices(
|
||||
|
||||
common_filter = common_filter or []
|
||||
common_filter.append(ple.account_type == party_account_type)
|
||||
common_filter.append(ple.account.isin(account))
|
||||
common_filter.append(ple.account == account)
|
||||
common_filter.append(ple.party_type == party_type)
|
||||
common_filter.append(ple.party == party)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ frappe.ui.form.on("Asset", {
|
||||
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
|
||||
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) {
|
||||
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
||||
frm.add_custom_button(
|
||||
__("Transfer Asset"),
|
||||
function () {
|
||||
@@ -365,7 +365,7 @@ frappe.ui.form.on("Asset", {
|
||||
if (v.journal_entry) {
|
||||
asset_values.push(asset_value);
|
||||
} else {
|
||||
if (["Scrapped", "Sold"].includes(frm.doc.status)) {
|
||||
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
||||
asset_values.push(null);
|
||||
} else {
|
||||
asset_values.push(asset_value);
|
||||
@@ -400,7 +400,7 @@ frappe.ui.form.on("Asset", {
|
||||
});
|
||||
}
|
||||
|
||||
if (["Scrapped", "Sold"].includes(frm.doc.status)) {
|
||||
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
|
||||
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" }));
|
||||
asset_values.push(0);
|
||||
}
|
||||
|
||||
@@ -242,7 +242,9 @@ def make_depreciation_entry(
|
||||
debit_account,
|
||||
accounting_dimensions,
|
||||
)
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
depreciation_posting_error = e
|
||||
|
||||
asset.set_status()
|
||||
@@ -521,7 +523,6 @@ def depreciate_asset(asset_doc, date, notes):
|
||||
|
||||
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
|
||||
|
||||
asset_doc.reload()
|
||||
cancel_depreciation_entries(asset_doc, date)
|
||||
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ class AssetDepreciationSchedule(Document):
|
||||
schedule_date = get_last_day(schedule_date)
|
||||
|
||||
# if asset is being sold or scrapped
|
||||
if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
|
||||
if date_of_disposal:
|
||||
from_date = add_months(
|
||||
getdate(asset_doc.available_for_use_date),
|
||||
(asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
frappe.provide("erpnext.buying");
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
|
||||
|
||||
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
|
||||
erpnext.accounts.taxes.setup_tax_validations("Purchase Order");
|
||||
erpnext.buying.setup_buying_controller();
|
||||
@@ -291,7 +289,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
this.frm.fields_dict.items_section.wrapper.removeClass("hide-border");
|
||||
}
|
||||
|
||||
if (!["Closed", "Delivered"].includes(doc.status)) {
|
||||
if (!in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (
|
||||
this.frm.doc.status !== "Closed" &&
|
||||
flt(this.frm.doc.per_received, 2) < 100 &&
|
||||
@@ -336,7 +334,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
|
||||
}
|
||||
} else if (["Closed", "Delivered"].includes(doc.status)) {
|
||||
} else if (in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (this.frm.has_perm("submit")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Re-open"),
|
||||
@@ -509,6 +507,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
target: me.frm,
|
||||
setters: {
|
||||
schedule_date: undefined,
|
||||
status: undefined,
|
||||
},
|
||||
get_query_filters: {
|
||||
material_request_type: "Purchase",
|
||||
|
||||
@@ -641,7 +641,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Text Editor",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1275,7 +1275,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:03:31.611808",
|
||||
"modified": "2023-10-01 20:58:07.851037",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
@@ -1330,4 +1330,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,6 @@ class PurchaseOrder(BuyingController):
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.SmallText | None
|
||||
advance_paid: DF.Currency
|
||||
advance_payment_status: DF.Literal["Not Initiated", "Initiated", "Partially Paid", "Fully Paid"]
|
||||
amended_from: DF.Link | None
|
||||
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
||||
apply_tds: DF.Check
|
||||
@@ -110,7 +109,7 @@ class PurchaseOrder(BuyingController):
|
||||
net_total: DF.Currency
|
||||
order_confirmation_date: DF.Date | None
|
||||
order_confirmation_no: DF.Data | None
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
other_charges_calculation: DF.LongText | None
|
||||
party_account_currency: DF.Link | None
|
||||
payment_schedule: DF.Table[PaymentSchedule]
|
||||
payment_terms_template: DF.Link | None
|
||||
|
||||
@@ -485,7 +485,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-13 11:14:06.516519",
|
||||
"modified": "2023-10-19 16:55:15.148325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
@@ -544,7 +544,7 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "supplier_group",
|
||||
"search_fields": "supplier_name, supplier_group",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
|
||||
@@ -154,6 +154,44 @@ class TestSupplier(FrappeTestCase):
|
||||
# Rollback
|
||||
address.delete()
|
||||
|
||||
def test_serach_fields_for_supplier(self):
|
||||
from erpnext.controllers.queries import supplier_query
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series")
|
||||
|
||||
supplier_name = create_supplier(supplier_name="Test Supplier 1").name
|
||||
|
||||
make_property_setter(
|
||||
"Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype"
|
||||
)
|
||||
|
||||
data = supplier_query(
|
||||
"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
|
||||
)
|
||||
|
||||
self.assertEqual(data[0].name, supplier_name)
|
||||
self.assertEqual(data[0].supplier_group, "Services")
|
||||
self.assertTrue("supplier_type" not in data[0])
|
||||
|
||||
make_property_setter(
|
||||
"Supplier",
|
||||
None,
|
||||
"search_fields",
|
||||
"supplier_group, supplier_type",
|
||||
"Data",
|
||||
for_doctype="Doctype",
|
||||
)
|
||||
data = supplier_query(
|
||||
"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
|
||||
)
|
||||
|
||||
self.assertEqual(data[0].name, supplier_name)
|
||||
self.assertEqual(data[0].supplier_group, "Services")
|
||||
self.assertEqual(data[0].supplier_type, "Company")
|
||||
self.assertTrue("supplier_type" in data[0])
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name")
|
||||
|
||||
|
||||
def create_supplier(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -462,7 +462,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -928,7 +928,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:03:59.069145",
|
||||
"modified": "2023-11-20 11:15:30.083077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
@@ -996,4 +996,4 @@
|
||||
"states": [],
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title"
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController):
|
||||
naming_series: DF.Literal["PUR-SQTN-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
opportunity: DF.Link | None
|
||||
other_charges_calculation: DF.MarkdownEditor | None
|
||||
other_charges_calculation: DF.LongText | None
|
||||
plc_conversion_rate: DF.Float
|
||||
price_list_currency: DF.Link | None
|
||||
pricing_rules: DF.Table[PricingRuleDetail]
|
||||
|
||||
@@ -77,10 +77,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
fieldname: "group_by",
|
||||
label: __("Group by"),
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
{ label: __("Group by Supplier"), value: "Group by Supplier" },
|
||||
{ label: __("Group by Item"), value: "Group by Item" },
|
||||
],
|
||||
options: [__("Group by Supplier"), __("Group by Item")],
|
||||
default: __("Group by Supplier"),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -89,7 +89,6 @@ force_item_fields = (
|
||||
"weight_per_unit",
|
||||
"weight_uom",
|
||||
"total_weight",
|
||||
"valuation_rate",
|
||||
)
|
||||
|
||||
|
||||
@@ -169,13 +168,6 @@ class AccountsController(TransactionBase):
|
||||
if not self.get("is_return") and not self.get("is_debit_note"):
|
||||
self.validate_qty_is_not_zero()
|
||||
|
||||
if (
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
||||
and self.get("is_return")
|
||||
and self.get("update_stock")
|
||||
):
|
||||
self.validate_zero_qty_for_return_invoices_with_stock()
|
||||
|
||||
if self.get("_action") and self._action != "update_after_submit":
|
||||
self.set_missing_values(for_validate=True)
|
||||
|
||||
@@ -226,18 +218,17 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
|
||||
if self.get("update_outstanding_for_self"):
|
||||
document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox. <br><br> Or you can use {3} tool to reconcile against {1} later."
|
||||
).format(
|
||||
frappe.bold(document_type),
|
||||
get_link_to_form(self.doctype, self.get("return_against")),
|
||||
frappe.bold("Update Outstanding for Self"),
|
||||
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
|
||||
)
|
||||
# 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)):
|
||||
@@ -380,12 +371,6 @@ class AccountsController(TransactionBase):
|
||||
for bundle in bundles:
|
||||
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
|
||||
|
||||
batches = frappe.get_all(
|
||||
"Batch", filters={"reference_doctype": self.doctype, "reference_name": self.name}
|
||||
)
|
||||
for row in batches:
|
||||
frappe.delete_doc("Batch", row.name)
|
||||
|
||||
def validate_return_against_account(self):
|
||||
if (
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against
|
||||
@@ -616,31 +601,23 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get("is_pos") or self.doctype not in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if self.get("is_pos"):
|
||||
return
|
||||
|
||||
from erpnext.accounts.party import validate_due_date
|
||||
|
||||
posting_date = (
|
||||
self.posting_date if self.doctype == "Sales Invoice" else (self.bill_date or self.posting_date)
|
||||
)
|
||||
|
||||
# skip due date validation for records via Data Import
|
||||
if frappe.flags.in_import and getdate(self.due_date) < getdate(posting_date):
|
||||
self.due_date = posting_date
|
||||
|
||||
elif self.doctype == "Sales Invoice":
|
||||
if self.doctype == "Sales Invoice":
|
||||
if not self.due_date:
|
||||
frappe.throw(_("Due Date is mandatory"))
|
||||
|
||||
validate_due_date(
|
||||
posting_date,
|
||||
self.posting_date,
|
||||
self.due_date,
|
||||
self.payment_terms_template,
|
||||
)
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
validate_due_date(
|
||||
posting_date,
|
||||
self.bill_date or self.posting_date,
|
||||
self.due_date,
|
||||
self.bill_date,
|
||||
self.payment_terms_template,
|
||||
@@ -1066,18 +1043,6 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
return flt(args.get(field, 0) / self.get("conversion_rate", 1))
|
||||
|
||||
def validate_zero_qty_for_return_invoices_with_stock(self):
|
||||
rows = []
|
||||
for item in self.items:
|
||||
if not flt(item.qty):
|
||||
rows.append(item)
|
||||
if rows:
|
||||
frappe.throw(
|
||||
_(
|
||||
"For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}"
|
||||
).format(frappe.bold(comma_and(["#" + str(x.idx) for x in rows])))
|
||||
)
|
||||
|
||||
def validate_qty_is_not_zero(self):
|
||||
if self.doctype == "Purchase Receipt":
|
||||
return
|
||||
@@ -1360,9 +1325,7 @@ class AccountsController(TransactionBase):
|
||||
self.name,
|
||||
arg.get("referenced_row"),
|
||||
):
|
||||
posting_date = arg.get("difference_posting_date") or frappe.db.get_value(
|
||||
arg.voucher_type, arg.voucher_no, "posting_date"
|
||||
)
|
||||
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
posting_date,
|
||||
@@ -1446,7 +1409,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
args.get("difference_posting_date") if args else self.posting_date,
|
||||
self.posting_date,
|
||||
self.party_type,
|
||||
self.party,
|
||||
party_account,
|
||||
@@ -2704,20 +2667,14 @@ def get_advance_journal_entries(
|
||||
else:
|
||||
q = q.where(journal_acc.debit_in_account_currency > 0)
|
||||
|
||||
reference_or_condition = []
|
||||
|
||||
if include_unallocated:
|
||||
reference_or_condition.append(journal_acc.reference_name.isnull())
|
||||
reference_or_condition.append(journal_acc.reference_name == "")
|
||||
q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == ""))
|
||||
|
||||
if order_list:
|
||||
reference_or_condition.append(
|
||||
q = q.where(
|
||||
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
|
||||
)
|
||||
|
||||
if reference_or_condition:
|
||||
q = q.where(Criterion.any(reference_or_condition))
|
||||
|
||||
q = q.orderby(journal_entry.posting_date)
|
||||
|
||||
journal_entries = q.run(as_dict=True)
|
||||
|
||||
@@ -513,14 +513,6 @@ class BuyingController(SubcontractingController):
|
||||
(not cint(self.is_return) and self.docstatus == 1)
|
||||
or (cint(self.is_return) and self.docstatus == 2)
|
||||
):
|
||||
serial_and_batch_bundle = d.get("serial_and_batch_bundle")
|
||||
if self.is_internal_transfer() and self.is_return and self.docstatus == 2:
|
||||
serial_and_batch_bundle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_detail_no": d.name, "warehouse": d.from_warehouse},
|
||||
"serial_and_batch_bundle",
|
||||
)
|
||||
|
||||
from_warehouse_sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
@@ -529,24 +521,19 @@ class BuyingController(SubcontractingController):
|
||||
"outgoing_rate": d.rate,
|
||||
"recalculate_rate": 1,
|
||||
"dependant_sle_voucher_detail_no": d.name,
|
||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||
},
|
||||
)
|
||||
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
|
||||
type_of_transaction = "Inward"
|
||||
if self.docstatus == 2:
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_and_batch_bundle": (
|
||||
d.serial_and_batch_bundle
|
||||
if not self.is_internal_transfer() or self.is_return
|
||||
else self.get_package_for_target_warehouse(d, type_of_transaction=type_of_transaction)
|
||||
if not self.is_internal_transfer()
|
||||
else self.get_package_for_target_warehouse(d)
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -583,17 +570,7 @@ class BuyingController(SubcontractingController):
|
||||
or (cint(self.is_return) and self.docstatus == 1)
|
||||
):
|
||||
from_warehouse_sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"recalculate_rate": 1,
|
||||
"serial_and_batch_bundle": (
|
||||
self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward")
|
||||
if self.is_internal_transfer() and self.is_return
|
||||
else None
|
||||
),
|
||||
},
|
||||
d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
|
||||
)
|
||||
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
@@ -620,15 +597,13 @@ class BuyingController(SubcontractingController):
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
|
||||
def get_package_for_target_warehouse(self, item) -> str:
|
||||
if not item.serial_and_batch_bundle:
|
||||
return ""
|
||||
|
||||
if not warehouse:
|
||||
warehouse = item.warehouse
|
||||
|
||||
return self.make_package_for_transfer(
|
||||
item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction
|
||||
item.serial_and_batch_bundle,
|
||||
item.warehouse,
|
||||
)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
|
||||
@@ -85,6 +85,79 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
)
|
||||
|
||||
# searches for customer
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
doctype = "Customer"
|
||||
conditions = []
|
||||
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
|
||||
|
||||
fields = ["name"]
|
||||
if cust_master_name != "Customer Name":
|
||||
fields.append("customer_name")
|
||||
|
||||
fields = get_fields(doctype, fields)
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabCustomer`
|
||||
where docstatus < 2
|
||||
and ({scond}) and disabled=0
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
|
||||
(case when locate(%(_txt)s, customer_name) > 0 then locate(%(_txt)s, customer_name) else 99999 end),
|
||||
idx desc,
|
||||
name, customer_name
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{
|
||||
"fields": ", ".join(fields),
|
||||
"scond": searchfields,
|
||||
"mcond": get_match_cond(doctype),
|
||||
"fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
|
||||
}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
as_dict=as_dict,
|
||||
)
|
||||
|
||||
|
||||
# searches for supplier
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
doctype = "Supplier"
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
|
||||
fields = ["name"]
|
||||
if supp_master_name != "Supplier Name":
|
||||
fields.append("supplier_name")
|
||||
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {field} from `tabSupplier`
|
||||
where docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
or supplier_name like %(txt)s) and disabled=0
|
||||
and (on_hold = 0 or (on_hold = 1 and CURRENT_DATE > release_date))
|
||||
{mcond}
|
||||
order by
|
||||
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
|
||||
(case when locate(%(_txt)s, supplier_name) > 0 then locate(%(_txt)s, supplier_name) else 99999 end),
|
||||
idx desc,
|
||||
name, supplier_name
|
||||
limit %(page_len)s offset %(start)s""".format(
|
||||
**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
as_dict=as_dict,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -364,26 +437,16 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
filtered_batches = get_filterd_batches(batches)
|
||||
|
||||
if filters.get("is_inward"):
|
||||
filtered_batches.extend(get_empty_batches(filters, start, page_len, filtered_batches, txt))
|
||||
filtered_batches.extend(get_empty_batches(filters))
|
||||
|
||||
return filtered_batches
|
||||
|
||||
|
||||
def get_empty_batches(filters, start, page_len, filtered_batches=None, txt=None):
|
||||
query_filter = {"item": filters.get("item_code")}
|
||||
if txt:
|
||||
query_filter["name"] = ("like", "%{0}%".format(txt))
|
||||
|
||||
exclude_batches = [batch[0] for batch in filtered_batches] if filtered_batches else []
|
||||
if exclude_batches:
|
||||
query_filter["name"] = ("not in", exclude_batches)
|
||||
|
||||
def get_empty_batches(filters):
|
||||
return frappe.get_all(
|
||||
"Batch",
|
||||
fields=["name", "batch_qty"],
|
||||
filters=query_filter,
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
filters={"item": filters.get("item_code"), "batch_qty": 0.0},
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
@@ -602,7 +665,7 @@ def get_filtered_dimensions(
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.is_tree and meta.has_field("is_group"):
|
||||
if meta.is_tree:
|
||||
query_filters.append(["is_group", "=", 0])
|
||||
|
||||
if meta.has_field("disabled"):
|
||||
|
||||
@@ -423,15 +423,6 @@ def make_return_doc(
|
||||
]:
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
warehouse = source_doc.warehouse if qty_field == "stock_qty" else source_doc.rejected_warehouse
|
||||
if source_parent.doctype in [
|
||||
"Sales Invoice",
|
||||
"POS Invoice",
|
||||
"Delivery Note",
|
||||
] and source_parent.get("is_internal_customer"):
|
||||
type_of_transaction = "Outward"
|
||||
warehouse = source_doc.target_warehouse
|
||||
|
||||
cls_obj = SerialBatchCreation(
|
||||
{
|
||||
"type_of_transaction": type_of_transaction,
|
||||
@@ -441,7 +432,7 @@ def make_return_doc(
|
||||
"returned_serial_nos": returned_serial_nos,
|
||||
"voucher_type": source_parent.doctype,
|
||||
"do_not_submit": True,
|
||||
"warehouse": warehouse,
|
||||
"warehouse": source_doc.warehouse,
|
||||
"has_serial_no": item_details.has_serial_no,
|
||||
"has_batch_no": item_details.has_batch_no,
|
||||
}
|
||||
@@ -584,14 +575,11 @@ def make_return_doc(
|
||||
if not item_details.has_batch_no and not item_details.has_serial_no:
|
||||
return
|
||||
|
||||
if not target_doc.get("use_serial_batch_fields"):
|
||||
for qty_field in ["stock_qty", "rejected_qty"]:
|
||||
if not target_doc.get(qty_field):
|
||||
continue
|
||||
|
||||
for qty_field in ["stock_qty", "rejected_qty"]:
|
||||
if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
|
||||
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
||||
elif target_doc.get("use_serial_batch_fields"):
|
||||
update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
|
||||
elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
|
||||
update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
|
||||
|
||||
def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
@@ -442,10 +442,8 @@ class SellingController(StockController):
|
||||
# Get incoming rate based on original item cost based on valuation method
|
||||
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
|
||||
|
||||
if (
|
||||
not d.incoming_rate
|
||||
or self.is_internal_transfer()
|
||||
or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
|
||||
if not d.incoming_rate or (
|
||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
||||
):
|
||||
d.incoming_rate = get_incoming_rate(
|
||||
{
|
||||
@@ -460,8 +458,6 @@ class SellingController(StockController):
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||
"batch_no": d.batch_no,
|
||||
"serial_no": d.serial_no,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
@@ -534,26 +530,13 @@ class SellingController(StockController):
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def get_sle_for_source_warehouse(self, item_row):
|
||||
serial_and_batch_bundle = item_row.serial_and_batch_bundle
|
||||
if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
|
||||
if self.docstatus == 1:
|
||||
serial_and_batch_bundle = self.make_package_for_transfer(
|
||||
serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
|
||||
)
|
||||
else:
|
||||
serial_and_batch_bundle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
|
||||
"serial_and_batch_bundle",
|
||||
)
|
||||
|
||||
sle = self.get_sl_entries(
|
||||
item_row,
|
||||
{
|
||||
"actual_qty": -1 * flt(item_row.qty),
|
||||
"incoming_rate": item_row.incoming_rate,
|
||||
"recalculate_rate": cint(self.is_return),
|
||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||
"serial_and_batch_bundle": item_row.serial_and_batch_bundle,
|
||||
},
|
||||
)
|
||||
if item_row.target_warehouse and not cint(self.is_return):
|
||||
@@ -574,15 +557,9 @@ class SellingController(StockController):
|
||||
if item_row.warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
if item_row.serial_and_batch_bundle and not cint(self.is_return):
|
||||
type_of_transaction = "Inward"
|
||||
if cint(self.is_return):
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
if item_row.serial_and_batch_bundle:
|
||||
sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
|
||||
item_row.serial_and_batch_bundle,
|
||||
item_row.target_warehouse,
|
||||
type_of_transaction=type_of_transaction,
|
||||
item_row.serial_and_batch_bundle, item_row.target_warehouse
|
||||
)
|
||||
|
||||
return sle
|
||||
|
||||
@@ -48,9 +48,7 @@ class StockController(AccountsController):
|
||||
super(StockController, self).validate()
|
||||
|
||||
if self.docstatus == 0:
|
||||
for table_name in ["items", "packed_items", "supplied_items"]:
|
||||
self.validate_duplicate_serial_and_batch_bundle(table_name)
|
||||
|
||||
self.validate_duplicate_serial_and_batch_bundle()
|
||||
if not self.get("is_return"):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
@@ -60,19 +58,12 @@ class StockController(AccountsController):
|
||||
self.validate_internal_transfer()
|
||||
self.validate_putaway_capacity()
|
||||
|
||||
def validate_duplicate_serial_and_batch_bundle(self, table_name):
|
||||
if not self.get(table_name):
|
||||
return
|
||||
|
||||
sbb_list = []
|
||||
for item in self.get(table_name):
|
||||
if item.get("serial_and_batch_bundle"):
|
||||
sbb_list.append(item.get("serial_and_batch_bundle"))
|
||||
|
||||
if item.get("rejected_serial_and_batch_bundle"):
|
||||
sbb_list.append(item.get("rejected_serial_and_batch_bundle"))
|
||||
|
||||
if sbb_list:
|
||||
def validate_duplicate_serial_and_batch_bundle(self):
|
||||
if sbb_list := [
|
||||
item.get("serial_and_batch_bundle")
|
||||
for item in self.items
|
||||
if item.get("serial_and_batch_bundle")
|
||||
]:
|
||||
SLE = frappe.qb.DocType("Stock Ledger Entry")
|
||||
data = (
|
||||
frappe.qb.from_(SLE)
|
||||
@@ -197,7 +188,7 @@ class StockController(AccountsController):
|
||||
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
||||
):
|
||||
bundle_details = {
|
||||
"item_code": row.get("rm_item_code") or row.item_code,
|
||||
"item_code": row.item_code,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"voucher_type": self.doctype,
|
||||
@@ -209,7 +200,7 @@ class StockController(AccountsController):
|
||||
"do_not_submit": True,
|
||||
}
|
||||
|
||||
if row.get("qty") or row.get("consumed_qty"):
|
||||
if row.qty:
|
||||
self.update_bundle_details(bundle_details, table_name, row)
|
||||
self.create_serial_batch_bundle(bundle_details, row)
|
||||
|
||||
@@ -228,12 +219,6 @@ class StockController(AccountsController):
|
||||
type_of_transaction = "Inward"
|
||||
if not self.is_return:
|
||||
type_of_transaction = "Outward"
|
||||
elif table_name == "supplied_items":
|
||||
qty = row.consumed_qty
|
||||
warehouse = self.supplier_warehouse
|
||||
type_of_transaction = "Outward"
|
||||
if self.is_return:
|
||||
type_of_transaction = "Inward"
|
||||
else:
|
||||
type_of_transaction = get_type_of_transaction(self, row)
|
||||
|
||||
@@ -251,14 +236,6 @@ class StockController(AccountsController):
|
||||
qty = row.get("rejected_qty")
|
||||
warehouse = row.get("rejected_warehouse")
|
||||
|
||||
if (
|
||||
self.is_internal_transfer()
|
||||
and self.doctype in ["Sales Invoice", "Delivery Note"]
|
||||
and self.is_return
|
||||
):
|
||||
warehouse = row.get("target_warehouse") or row.get("warehouse")
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
bundle_details.update(
|
||||
{
|
||||
"qty": qty,
|
||||
@@ -565,30 +542,13 @@ class StockController(AccountsController):
|
||||
)
|
||||
|
||||
def delete_auto_created_batches(self):
|
||||
for table_name in ["items", "packed_items", "supplied_items"]:
|
||||
if not self.get(table_name):
|
||||
continue
|
||||
for row in self.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
for row in self.get(table_name):
|
||||
update_values = {}
|
||||
if row.get("batch_no"):
|
||||
update_values["batch_no"] = None
|
||||
|
||||
if row.serial_and_batch_bundle:
|
||||
update_values["serial_and_batch_bundle"] = None
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
if update_values:
|
||||
row.db_set(update_values)
|
||||
|
||||
if table_name == "items" and row.get("rejected_serial_and_batch_bundle"):
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, {"is_cancelled": 1}
|
||||
)
|
||||
|
||||
row.db_set("rejected_serial_and_batch_bundle", None)
|
||||
row.db_set("serial_and_batch_bundle", None)
|
||||
|
||||
def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False):
|
||||
if not table_name:
|
||||
@@ -619,7 +579,7 @@ class StockController(AccountsController):
|
||||
bundle_doc.warehouse = warehouse
|
||||
bundle_doc.type_of_transaction = type_of_transaction
|
||||
bundle_doc.voucher_type = self.doctype
|
||||
bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name
|
||||
bundle_doc.voucher_no = self.name
|
||||
bundle_doc.is_cancelled = 0
|
||||
|
||||
for row in bundle_doc.entries:
|
||||
@@ -635,7 +595,6 @@ class StockController(AccountsController):
|
||||
|
||||
bundle_doc.calculate_qty_and_amount()
|
||||
bundle_doc.flags.ignore_permissions = True
|
||||
bundle_doc.flags.ignore_validate = True
|
||||
bundle_doc.save(ignore_permissions=True)
|
||||
|
||||
return bundle_doc.name
|
||||
|
||||
@@ -379,10 +379,10 @@ class SubcontractingController(StockController):
|
||||
if row.serial_no:
|
||||
details.serial_no.extend(get_serial_nos(row.serial_no))
|
||||
|
||||
elif row.batch_no:
|
||||
if row.batch_no:
|
||||
details.batch_no[row.batch_no] += row.qty
|
||||
|
||||
elif voucher_bundle_data:
|
||||
if voucher_bundle_data:
|
||||
bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no)
|
||||
|
||||
bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict())
|
||||
@@ -392,9 +392,6 @@ class SubcontractingController(StockController):
|
||||
|
||||
if bundle_data.batch_nos:
|
||||
for batch_no, qty in bundle_data.batch_nos.items():
|
||||
if qty < 0:
|
||||
qty = abs(qty)
|
||||
|
||||
if qty > 0:
|
||||
details.batch_no[batch_no] += qty
|
||||
bundle_data.batch_nos[batch_no] -= qty
|
||||
@@ -548,24 +545,17 @@ class SubcontractingController(StockController):
|
||||
|
||||
rm_obj.reference_name = item_row.name
|
||||
|
||||
use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
|
||||
|
||||
if self.doctype == self.subcontract_data.order_doctype:
|
||||
rm_obj.required_qty = qty
|
||||
rm_obj.amount = rm_obj.required_qty * rm_obj.rate
|
||||
else:
|
||||
rm_obj.consumed_qty = qty
|
||||
rm_obj.required_qty = bom_item.required_qty or qty
|
||||
rm_obj.serial_and_batch_bundle = None
|
||||
setattr(
|
||||
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
|
||||
if use_serial_batch_fields:
|
||||
rm_obj.use_serial_batch_fields = 1
|
||||
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
|
||||
|
||||
if self.doctype == "Subcontracting Receipt" and not use_serial_batch_fields:
|
||||
if self.doctype == "Subcontracting Receipt":
|
||||
args = frappe._dict(
|
||||
{
|
||||
"item_code": rm_obj.rm_item_code,
|
||||
@@ -591,68 +581,6 @@ class SubcontractingController(StockController):
|
||||
|
||||
rm_obj.rate = get_incoming_rate(args)
|
||||
|
||||
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||
new_rm_obj = None
|
||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
||||
if batch_qty >= qty or (
|
||||
rm_obj.consumed_qty == 0
|
||||
and self.backflush_based_on == "BOM"
|
||||
and len(self.available_materials[key]["batch_no"]) == 1
|
||||
):
|
||||
if rm_obj.consumed_qty == 0:
|
||||
self.__set_consumed_qty(rm_obj, qty)
|
||||
|
||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
||||
return
|
||||
|
||||
elif qty > 0 and batch_qty > 0:
|
||||
qty -= batch_qty
|
||||
new_rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
new_rm_obj.serial_and_batch_bundle = None
|
||||
new_rm_obj.use_serial_batch_fields = 1
|
||||
new_rm_obj.reference_name = item_row.name
|
||||
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
|
||||
self.available_materials[key]["batch_no"][batch_no] = 0
|
||||
|
||||
if new_rm_obj:
|
||||
self.remove(rm_obj)
|
||||
elif abs(qty) > 0:
|
||||
self.__set_consumed_qty(rm_obj, qty)
|
||||
|
||||
else:
|
||||
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
|
||||
rm_obj.required_qty = required_qty
|
||||
rm_obj.consumed_qty = consumed_qty
|
||||
|
||||
def __set_serial_nos(self, item_row, rm_obj):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
|
||||
# Removed the used serial nos from the list
|
||||
for sn in used_serial_nos:
|
||||
self.available_materials[key]["serial_no"].remove(sn)
|
||||
|
||||
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
|
||||
rm_obj.update(
|
||||
{
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"required_qty": qty,
|
||||
self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
|
||||
}
|
||||
)
|
||||
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
||||
|
||||
@@ -1148,9 +1076,6 @@ def make_rm_stock_entry(
|
||||
"serial_and_batch_bundle": rm_item.get("serial_and_batch_bundle"),
|
||||
"main_item_code": fg_item_code,
|
||||
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
|
||||
"use_serial_batch_fields": rm_item.get("use_serial_batch_fields"),
|
||||
"serial_no": rm_item.get("serial_no") if rm_item.get("use_serial_batch_fields") else None,
|
||||
"batch_no": rm_item.get("batch_no") if rm_item.get("use_serial_batch_fields") else None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import qb
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
@@ -616,73 +616,6 @@ class TestAccountsController(FrappeTestCase):
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_pe, [])
|
||||
|
||||
def test_15_gain_loss_on_different_posting_date(self):
|
||||
# Invoice in Foreign Currency
|
||||
si = self.create_sales_invoice(
|
||||
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
|
||||
)
|
||||
# Payment
|
||||
pe = (
|
||||
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
|
||||
.save()
|
||||
.submit()
|
||||
)
|
||||
|
||||
# There should be outstanding in both currencies
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 2)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||
|
||||
# Reconcile the remaining amount
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Customer"
|
||||
pr.party = self.customer
|
||||
pr.receivable_payable_account = self.debit_usd
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
|
||||
pr.reconcile()
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created.
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||
self.assertNotEqual(exc_je_for_si, [])
|
||||
self.assertEqual(len(exc_je_for_si), 1)
|
||||
self.assertEqual(len(exc_je_for_pe), 1)
|
||||
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
|
||||
getdate(add_days(nowdate(), 1)),
|
||||
)
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
# There should be no outstanding
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 0)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
||||
|
||||
# Cancel Payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 2)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been cancelled
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_pe, [])
|
||||
|
||||
def test_20_journal_against_sales_invoice(self):
|
||||
# Invoice in Foreign Currency
|
||||
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||
|
||||
@@ -31,6 +31,18 @@ class TestQueries(unittest.TestCase):
|
||||
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
|
||||
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
|
||||
|
||||
def test_customer_query(self):
|
||||
query = add_default_params(queries.customer_query, "Customer")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
|
||||
|
||||
def test_supplier_query(self):
|
||||
query = add_default_params(queries.supplier_query, "Supplier")
|
||||
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
|
||||
self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
|
||||
|
||||
def test_item_query(self):
|
||||
query = add_default_params(queries.item_query, "Item")
|
||||
|
||||
|
||||
@@ -140,7 +140,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
- Create partial SCR against the SCO and check serial nos and batch no.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
@@ -203,8 +202,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
if value.get(field):
|
||||
self.assertEqual(value.get(field), transferred_detais.get(field))
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_subcontracting_with_same_components_different_fg(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
@@ -214,7 +211,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
- Create partial SCR against the SCO and check serial nos.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
@@ -282,8 +278,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
self.assertEqual(value.qty, 6)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_return_non_consumed_materials(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
@@ -294,7 +288,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
- After that return the non consumed material back to the store from supplier's warehouse.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
@@ -340,7 +333,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
get_serial_nos(doc.items[0].serial_no),
|
||||
itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
|
||||
)
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_item_with_batch_based_on_bom(self):
|
||||
"""
|
||||
@@ -586,7 +578,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
- Create SCR for remaining qty against the SCO and change the qty manually.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
@@ -652,8 +643,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
self.assertEqual(value.qty, details.qty)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_incorrect_serial_no_components_based_on_material_transfer(self):
|
||||
"""
|
||||
- Set backflush based on Material Transferred for Subcontract.
|
||||
@@ -663,7 +652,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
- System should throw the error and not allowed to save the SCR.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
serial_no = "ABC"
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
@@ -724,7 +712,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
scr1.save()
|
||||
self.delete_bundle_from_scr(scr1)
|
||||
scr1.delete()
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
@staticmethod
|
||||
def delete_bundle_from_scr(scr):
|
||||
@@ -857,223 +844,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
for item in sco.get("supplied_items"):
|
||||
self.assertEqual(item.supplied_qty, 0.0)
|
||||
|
||||
def test_sco_with_material_transfer_with_use_serial_batch_fields(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
- Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
|
||||
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
|
||||
- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
|
||||
- Create partial SCR against the SCO and check serial nos and batch no.
|
||||
"""
|
||||
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 1",
|
||||
"qty": 5,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA1",
|
||||
"fg_item_qty": 5,
|
||||
},
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 5",
|
||||
"qty": 6,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA5",
|
||||
"fg_item_qty": 6,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
rm_items.append(
|
||||
{
|
||||
"main_item_code": "Subcontracted Item SA5",
|
||||
"item_code": "Subcontracted SRM Item 4",
|
||||
"qty": 6,
|
||||
}
|
||||
)
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
|
||||
for item in rm_items:
|
||||
item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name
|
||||
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
scr1 = make_subcontracting_receipt(sco.name)
|
||||
scr1.remove(scr1.items[1])
|
||||
scr1.save()
|
||||
scr1.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr1).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
|
||||
for field in ["qty", "serial_no", "batch_no"]:
|
||||
if value.get(field):
|
||||
data = value.get(field)
|
||||
if field == "serial_no":
|
||||
data = sorted(data)
|
||||
|
||||
self.assertEqual(data, transferred_detais.get(field))
|
||||
|
||||
scr2 = make_subcontracting_receipt(sco.name)
|
||||
scr2.save()
|
||||
scr2.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr2).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
|
||||
for field in ["qty", "serial_no", "batch_no"]:
|
||||
if value.get(field):
|
||||
data = value.get(field)
|
||||
if field == "serial_no":
|
||||
data = sorted(data)
|
||||
|
||||
self.assertEqual(data, transferred_detais.get(field))
|
||||
|
||||
def test_subcontracting_with_same_components_different_fg_with_serial_batch_fields(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
- Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
|
||||
- Transfer the components from Stores to Supplier warehouse with serial nos.
|
||||
- Transfer extra qty of components for the item Subcontracted Item SA2.
|
||||
- Create partial SCR against the SCO and check serial nos.
|
||||
"""
|
||||
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 2",
|
||||
"qty": 5,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA2",
|
||||
"fg_item_qty": 5,
|
||||
},
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 3",
|
||||
"qty": 6,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA3",
|
||||
"fg_item_qty": 6,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
rm_items[0]["qty"] += 1
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
|
||||
for item in rm_items:
|
||||
item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name
|
||||
item["use_serial_batch_fields"] = 1
|
||||
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
scr1 = make_subcontracting_receipt(sco.name)
|
||||
scr1.items[0].qty = 3
|
||||
scr1.remove(scr1.items[1])
|
||||
scr1.save()
|
||||
scr1.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr1).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
|
||||
self.assertEqual(value.qty, 4)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))
|
||||
|
||||
scr2 = make_subcontracting_receipt(sco.name)
|
||||
scr2.items[0].qty = 2
|
||||
scr2.remove(scr2.items[1])
|
||||
scr2.save()
|
||||
scr2.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr2).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
|
||||
self.assertEqual(value.qty, 2)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))
|
||||
|
||||
scr3 = make_subcontracting_receipt(sco.name)
|
||||
scr3.save()
|
||||
scr3.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr3).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
|
||||
self.assertEqual(value.qty, 6)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
|
||||
|
||||
def test_return_non_consumed_materials_with_serial_batch_fields(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
- Create SCO for item Subcontracted Item SA2.
|
||||
- Transfer the components from Stores to Supplier warehouse with serial nos.
|
||||
- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
|
||||
- Create SCR for full qty against the SCO and change the qty of raw material.
|
||||
- After that return the non consumed material back to the store from supplier's warehouse.
|
||||
"""
|
||||
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 2",
|
||||
"qty": 5,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA2",
|
||||
"fg_item_qty": 5,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
rm_items[0]["qty"] += 1
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
|
||||
for item in rm_items:
|
||||
item["use_serial_batch_fields"] = 1
|
||||
item["sco_rm_detail"] = sco.items[0].name
|
||||
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
scr1 = make_subcontracting_receipt(sco.name)
|
||||
scr1.save()
|
||||
scr1.supplied_items[0].consumed_qty = 5
|
||||
scr1.supplied_items[0].serial_no = "\n".join(
|
||||
sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5])
|
||||
)
|
||||
scr1.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr1).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
self.assertTrue(value.use_serial_batch_fields)
|
||||
self.assertEqual(value.qty, 5)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))
|
||||
|
||||
sco.load_from_db()
|
||||
self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
|
||||
doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
|
||||
self.assertEqual(doc.items[0].qty, 1)
|
||||
self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
|
||||
self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
|
||||
self.assertEqual(
|
||||
get_serial_nos(doc.items[0].serial_no),
|
||||
itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
|
||||
)
|
||||
|
||||
|
||||
def add_second_row_in_scr(scr):
|
||||
item_dict = {}
|
||||
@@ -1144,7 +914,6 @@ def update_item_details(child_row, details):
|
||||
else child_row.get("consumed_qty")
|
||||
)
|
||||
|
||||
details.use_serial_batch_fields = child_row.get("use_serial_batch_fields")
|
||||
if child_row.serial_and_batch_bundle:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
|
||||
for row in doc.get("entries"):
|
||||
@@ -1176,7 +945,6 @@ def make_stock_transfer_entry(**args):
|
||||
"rate": row.rate or 100,
|
||||
"stock_uom": row.stock_uom or "Nos",
|
||||
"warehouse": row.warehouse or "_Test Warehouse - _TC",
|
||||
"use_serial_batch_fields": row.get("use_serial_batch_fields"),
|
||||
}
|
||||
|
||||
item_details = args.itemwise_details.get(row.item_code)
|
||||
@@ -1192,12 +960,9 @@ def make_stock_transfer_entry(**args):
|
||||
if batch_qty >= row.qty:
|
||||
batches[batch_no] = row.qty
|
||||
item_details.batch_no[batch_no] -= row.qty
|
||||
if row.get("use_serial_batch_fields"):
|
||||
item["batch_no"] = batch_no
|
||||
|
||||
break
|
||||
|
||||
if not row.get("use_serial_batch_fields") and (serial_nos or batches):
|
||||
if serial_nos or batches:
|
||||
item["serial_and_batch_bundle"] = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
@@ -1213,9 +978,6 @@ def make_stock_transfer_entry(**args):
|
||||
)
|
||||
).name
|
||||
|
||||
if serial_nos and row.get("use_serial_batch_fields"):
|
||||
item["serial_no"] = "\n".join(serial_nos)
|
||||
|
||||
items.append(item)
|
||||
|
||||
ste_dict = make_rm_stock_entry(args.sco_no, items)
|
||||
@@ -1370,7 +1132,6 @@ def get_rm_items(supplied_items):
|
||||
"rate": item.rate,
|
||||
"stock_uom": item.stock_uom,
|
||||
"warehouse": item.reserve_warehouse,
|
||||
"use_serial_batch_fields": 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
||||
}
|
||||
|
||||
onload() {
|
||||
this.frm.set_query("customer", function (doc, cdt, cdn) {
|
||||
return { query: "erpnext.controllers.queries.customer_query" };
|
||||
});
|
||||
|
||||
this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
|
||||
return { query: "frappe.core.doctype.user.user.user_query" };
|
||||
});
|
||||
|
||||
@@ -288,6 +288,9 @@ has_website_permission = {
|
||||
|
||||
before_tests = "erpnext.setup.utils.before_tests"
|
||||
|
||||
standard_queries = {
|
||||
"Customer": "erpnext.controllers.queries.customer_query",
|
||||
}
|
||||
|
||||
period_closing_doctypes = [
|
||||
"Sales Invoice",
|
||||
@@ -312,10 +315,7 @@ period_closing_doctypes = [
|
||||
|
||||
doc_events = {
|
||||
"*": {
|
||||
"validate": [
|
||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
|
||||
"erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.check_for_running_deletion_job",
|
||||
],
|
||||
"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
|
||||
},
|
||||
tuple(period_closing_doctypes): {
|
||||
"validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save",
|
||||
|
||||
@@ -400,7 +400,7 @@ frappe.ui.form.on("BOM", {
|
||||
},
|
||||
|
||||
rm_cost_as_per(frm) {
|
||||
if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) {
|
||||
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
|
||||
frm.set_value("plc_conversion_rate", 1.0);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -264,7 +264,7 @@ class JobCard(Document):
|
||||
if not self.has_overlap(production_capacity, time_logs):
|
||||
return {}
|
||||
|
||||
if not self.workstation and self.workstation_type and time_logs:
|
||||
if self.workstation_type and time_logs:
|
||||
if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
|
||||
self.workstation = workstation_time.get("workstation")
|
||||
return workstation_time
|
||||
@@ -420,7 +420,7 @@ class JobCard(Document):
|
||||
if not workstation_doc.working_hours or cint(
|
||||
frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
|
||||
):
|
||||
if get_datetime(row.planned_end_time) <= get_datetime(row.planned_start_time):
|
||||
if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
|
||||
row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
|
||||
row.remaining_time_in_mins = 0.0
|
||||
else:
|
||||
@@ -490,7 +490,7 @@ class JobCard(Document):
|
||||
{
|
||||
"to_time": get_datetime(args.get("complete_time")),
|
||||
"operation": args.get("sub_operation"),
|
||||
"completed_qty": (args.get("completed_qty") if last_row.idx == row.idx else 0.0),
|
||||
"completed_qty": args.get("completed_qty") or 0.0,
|
||||
}
|
||||
)
|
||||
elif args.get("start_time"):
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">{{ __("Add") }}</button>
|
||||
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">{{ __("Move") }}</button>
|
||||
<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
|
||||
</div>
|
||||
</div>
|
||||
{% }); %}
|
||||
{% }); %}
|
||||
@@ -129,7 +129,7 @@ frappe.ui.form.on("Production Plan", {
|
||||
if (
|
||||
frm.doc.mr_items &&
|
||||
frm.doc.mr_items.length &&
|
||||
!["Material Requested", "Closed"].includes(frm.doc.status)
|
||||
!in_list(["Material Requested", "Closed"], frm.doc.status)
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Material Request"),
|
||||
|
||||
@@ -1207,51 +1207,6 @@ class TestWorkOrder(FrappeTestCase):
|
||||
except frappe.MandatoryError:
|
||||
self.fail("Batch generation causing failing in Work Order")
|
||||
|
||||
@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
|
||||
def test_auto_serial_no_batch_creation(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
|
||||
fg_item = frappe.generate_hash(length=20)
|
||||
child_item = frappe.generate_hash(length=20)
|
||||
|
||||
bom_tree = {fg_item: {child_item: {}}}
|
||||
|
||||
create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
item = frappe.get_doc("Item", fg_item)
|
||||
item.update(
|
||||
{
|
||||
"has_serial_no": 1,
|
||||
"has_batch_no": 1,
|
||||
"serial_no_series": f"SN-TEST-{item.name}.#####",
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": f"BATCH-TEST-{item.name}.#####",
|
||||
}
|
||||
)
|
||||
item.save()
|
||||
|
||||
try:
|
||||
wo_order = make_wo_order_test_record(item=fg_item, batch_size=5, qty=10, skip_transfer=True)
|
||||
serial_nos = self.get_serial_nos_for_fg(wo_order.name)
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
stock_entry.set_work_order_details()
|
||||
stock_entry.set_serial_no_batch_for_finished_good()
|
||||
for row in stock_entry.items:
|
||||
if row.item_code == fg_item:
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
self.assertEqual(
|
||||
sorted(get_serial_nos_from_bundle(row.serial_and_batch_bundle)), sorted(serial_nos)
|
||||
)
|
||||
|
||||
sn_doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for row in sn_doc.entries:
|
||||
self.assertTrue(row.serial_no)
|
||||
self.assertTrue(row.batch_no)
|
||||
|
||||
except frappe.MandatoryError:
|
||||
self.fail("Batch generation causing failing in Work Order")
|
||||
|
||||
def get_serial_nos_for_fg(self, work_order):
|
||||
serial_nos = []
|
||||
for row in frappe.get_all("Serial No", filters={"work_order": work_order}):
|
||||
@@ -2314,7 +2269,6 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.planned_start_date = args.planned_start_date or now()
|
||||
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
||||
wo_order.from_wip_warehouse = args.from_wip_warehouse or 0
|
||||
wo_order.batch_size = args.batch_size or 0
|
||||
|
||||
if args.source_warehouse:
|
||||
for item in wo_order.get("required_items"):
|
||||
|
||||
@@ -9,8 +9,6 @@ frappe.ui.form.on("Work Order", {
|
||||
"Job Card": "Create Job Card",
|
||||
};
|
||||
|
||||
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
|
||||
|
||||
// Set query for warehouses
|
||||
frm.set_query("wip_warehouse", function () {
|
||||
return {
|
||||
@@ -196,7 +194,7 @@ frappe.ui.form.on("Work Order", {
|
||||
},
|
||||
|
||||
add_custom_button_to_return_components: function (frm) {
|
||||
if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) {
|
||||
if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) {
|
||||
let non_consumed_items = frm.doc.required_items.filter((d) => {
|
||||
return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty);
|
||||
});
|
||||
@@ -596,7 +594,7 @@ erpnext.work_order = {
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) {
|
||||
if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) {
|
||||
if (doc.status != "Stopped" && doc.status != "Completed") {
|
||||
frm.add_custom_button(
|
||||
__("Stop"),
|
||||
|
||||
@@ -330,13 +330,6 @@ class WorkOrder(Document):
|
||||
else:
|
||||
status = "Cancelled"
|
||||
|
||||
if (
|
||||
self.skip_transfer
|
||||
and self.produced_qty
|
||||
and self.qty > (flt(self.produced_qty) + flt(self.process_loss_qty))
|
||||
):
|
||||
status = "In Process"
|
||||
|
||||
return status
|
||||
|
||||
def update_work_order_qty(self):
|
||||
@@ -536,12 +529,6 @@ class WorkOrder(Document):
|
||||
"Item", self.production_item, ["serial_no_series", "item_name", "description"], as_dict=1
|
||||
)
|
||||
|
||||
batches = []
|
||||
if self.has_batch_no:
|
||||
batches = frappe.get_all(
|
||||
"Batch", filters={"reference_name": self.name}, order_by="creation", pluck="name"
|
||||
)
|
||||
|
||||
serial_nos = []
|
||||
if item_details.serial_no_series:
|
||||
serial_nos = get_available_serial_nos(item_details.serial_no_series, self.qty)
|
||||
@@ -562,20 +549,10 @@ class WorkOrder(Document):
|
||||
"description",
|
||||
"status",
|
||||
"work_order",
|
||||
"batch_no",
|
||||
]
|
||||
|
||||
serial_nos_details = []
|
||||
index = 0
|
||||
for serial_no in serial_nos:
|
||||
index += 1
|
||||
batch_no = None
|
||||
if batches and self.batch_size:
|
||||
batch_no = batches[0]
|
||||
|
||||
if index % self.batch_size == 0:
|
||||
batches.remove(batch_no)
|
||||
|
||||
serial_nos_details.append(
|
||||
(
|
||||
serial_no,
|
||||
@@ -590,7 +567,6 @@ class WorkOrder(Document):
|
||||
item_details.description,
|
||||
"Inactive",
|
||||
self.name,
|
||||
batch_no,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -808,7 +784,7 @@ class WorkOrder(Document):
|
||||
)
|
||||
|
||||
def update_completed_qty_in_material_request(self):
|
||||
if self.material_request and self.material_request_item:
|
||||
if self.material_request:
|
||||
frappe.get_doc("Material Request", self.material_request).update_completed_qty(
|
||||
[self.material_request_item]
|
||||
)
|
||||
|
||||
@@ -354,7 +354,6 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency"
|
||||
erpnext.patches.v14_0.update_total_asset_cost_field
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
||||
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
|
||||
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
from frappe import qb
|
||||
|
||||
|
||||
def execute():
|
||||
# Set "update_outstanding_for_self" flag in Credit/Debit Notes
|
||||
# Fetch Credit/Debit notes that does have 'return_against' but still post ledger entries against themselves.
|
||||
|
||||
gle = qb.DocType("GL Entry")
|
||||
|
||||
# Use hardcoded 'creation' date to isolate Credit/Debit notes created post v14 backport
|
||||
# https://github.com/frappe/erpnext/pull/39497
|
||||
creation_date = "2024-01-25"
|
||||
|
||||
si = qb.DocType("Sales Invoice")
|
||||
|
||||
# unset flag, as migration would have set it for all records, as the field was introduced with default '1'
|
||||
qb.update(si).set(si.update_outstanding_for_self, False).run()
|
||||
|
||||
if cr_notes := (
|
||||
qb.from_(si)
|
||||
.select(si.name)
|
||||
.where(
|
||||
(si.creation.gte(creation_date))
|
||||
& (si.docstatus == 1)
|
||||
& (si.is_return == True)
|
||||
& (si.return_against.notnull())
|
||||
)
|
||||
.run()
|
||||
):
|
||||
cr_notes = [x[0] for x in cr_notes]
|
||||
if docs_that_require_update := (
|
||||
qb.from_(gle)
|
||||
.select(gle.voucher_no)
|
||||
.distinct()
|
||||
.where((gle.voucher_no.isin(cr_notes)) & (gle.voucher_no == gle.against_voucher))
|
||||
.run()
|
||||
):
|
||||
docs_that_require_update = [x[0] for x in docs_that_require_update]
|
||||
qb.update(si).set(si.update_outstanding_for_self, True).where(
|
||||
si.name.isin(docs_that_require_update)
|
||||
).run()
|
||||
|
||||
pi = qb.DocType("Purchase Invoice")
|
||||
|
||||
# unset flag, as migration would have set it for all records, as the field was introduced with default '1'
|
||||
qb.update(pi).set(pi.update_outstanding_for_self, False).run()
|
||||
|
||||
if dr_notes := (
|
||||
qb.from_(pi)
|
||||
.select(pi.name)
|
||||
.where(
|
||||
(pi.creation.gte(creation_date))
|
||||
& (pi.docstatus == 1)
|
||||
& (pi.is_return == True)
|
||||
& (pi.return_against.notnull())
|
||||
)
|
||||
.run()
|
||||
):
|
||||
dr_notes = [x[0] for x in dr_notes]
|
||||
if docs_that_require_update := (
|
||||
qb.from_(gle)
|
||||
.select(gle.voucher_no)
|
||||
.distinct()
|
||||
.where((gle.voucher_no.isin(dr_notes)) & (gle.voucher_no == gle.against_voucher))
|
||||
.run()
|
||||
):
|
||||
docs_that_require_update = [x[0] for x in docs_that_require_update]
|
||||
qb.update(pi).set(pi.update_outstanding_for_self, True).where(
|
||||
pi.name.isin(docs_that_require_update)
|
||||
).run()
|
||||
@@ -27,6 +27,8 @@ frappe.ui.form.on("Project", {
|
||||
};
|
||||
};
|
||||
|
||||
frm.set_query("customer", "erpnext.controllers.queries.customer_query");
|
||||
|
||||
frm.set_query("user", "users", function () {
|
||||
return {
|
||||
query: "erpnext.projects.doctype.project.project.get_users_for_project",
|
||||
|
||||
@@ -107,7 +107,7 @@ class BOMConfigurator {
|
||||
this.frm?.doc.docstatus === 0
|
||||
? [
|
||||
{
|
||||
label: `${frappe.utils.icon("edit", "sm")} ${__("Qty")}`,
|
||||
label: __(frappe.utils.icon("edit", "sm") + " Qty"),
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.edit_qty(node, view);
|
||||
@@ -115,7 +115,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: `${frappe.utils.icon("add", "sm")} ${__("Raw Material")}`,
|
||||
label: __(frappe.utils.icon("add", "sm") + " Raw Material"),
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_item(node, view);
|
||||
@@ -126,7 +126,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: `${frappe.utils.icon("add", "sm")} ${__("Sub Assembly")}`,
|
||||
label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"),
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_sub_assembly(node, view);
|
||||
@@ -156,7 +156,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs expand-all-btn",
|
||||
},
|
||||
{
|
||||
label: `${frappe.utils.icon("move", "sm")} ${__("Sub Assembly")}`,
|
||||
label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.convert_to_sub_assembly(node, view);
|
||||
@@ -167,7 +167,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: `${frappe.utils.icon("delete", "sm")} ${__("Item")}`,
|
||||
label: __(frappe.utils.icon("delete", "sm") + __(" Item")),
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.delete_node(node, view);
|
||||
|
||||
@@ -20,7 +20,7 @@ frappe.ui.form.on("Communication", {
|
||||
);
|
||||
}
|
||||
|
||||
if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) {
|
||||
if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
|
||||
frm.add_custom_button(
|
||||
__("Lead"),
|
||||
() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ erpnext.accounts.taxes = {
|
||||
setup: function(frm) {
|
||||
// set conditional display for rate column in taxes
|
||||
$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
|
||||
if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
|
||||
if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) {
|
||||
me.set_conditional_mandatory_rate_or_amount(grid_row);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,6 +74,11 @@ erpnext.buying = {
|
||||
me.frm.set_query('billing_address', erpnext.queries.company_address_query);
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
|
||||
|
||||
if(this.frm.fields_dict.supplier) {
|
||||
this.frm.set_query("supplier", function() {
|
||||
return{ query: "erpnext.controllers.queries.supplier_query" }});
|
||||
}
|
||||
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
if (me.frm.doc.is_subcontracted) {
|
||||
var filters = {'supplier': me.frm.doc.supplier};
|
||||
@@ -129,7 +134,7 @@ erpnext.buying = {
|
||||
}
|
||||
|
||||
toggle_subcontracting_fields() {
|
||||
if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
||||
if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
|
||||
this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
|
||||
'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
apply_pricing_rule_on_item(item) {
|
||||
let effective_item_rate = item.price_list_rate;
|
||||
let item_rate = item.rate;
|
||||
if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) {
|
||||
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
|
||||
effective_item_rate = item.blanket_order_rate;
|
||||
}
|
||||
if (item.margin_type == "Percentage") {
|
||||
@@ -26,7 +26,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
|
||||
}
|
||||
|
||||
if (item.discount_amount > 0) {
|
||||
if (item.discount_amount) {
|
||||
item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
|
||||
item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
// Advance calculation applicable to Sales/Purchase Invoice
|
||||
if (
|
||||
["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)
|
||||
in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2
|
||||
&& !this.frm.doc.is_return
|
||||
) {
|
||||
@@ -60,7 +60,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
if (
|
||||
["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
|
||||
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.is_pos
|
||||
&& this.frm.doc.is_return
|
||||
) {
|
||||
@@ -69,7 +69,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
// Sales person's commission
|
||||
if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) {
|
||||
if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
|
||||
this.calculate_commission();
|
||||
this.calculate_contribution();
|
||||
}
|
||||
@@ -562,7 +562,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment)
|
||||
: this.frm.doc.net_total);
|
||||
|
||||
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
||||
this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ?
|
||||
flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total;
|
||||
} else {
|
||||
@@ -570,7 +570,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0;
|
||||
if(tax_count) {
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (["Valuation and Total", "Total"].includes(tax.category)) {
|
||||
if (in_list(["Valuation and Total", "Total"], tax.category)) {
|
||||
if(tax.add_deduct_tax == "Add") {
|
||||
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
|
||||
} else {
|
||||
@@ -717,7 +717,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
var actual_taxes_dict = {};
|
||||
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
|
||||
if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
|
||||
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
|
||||
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
actual_taxes_dict[tax.idx] = tax_amount;
|
||||
@@ -762,7 +762,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
// NOTE:
|
||||
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
|
||||
// total_advance is only for non POS Invoice
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){
|
||||
this.calculate_paid_amount();
|
||||
}
|
||||
|
||||
@@ -770,7 +770,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
|
||||
|
||||
if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
|
||||
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;
|
||||
|
||||
@@ -793,7 +793,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.refresh_field("base_paid_amount");
|
||||
}
|
||||
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
||||
let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
|
||||
? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
|
||||
: total_amount_to_pay;
|
||||
@@ -897,7 +897,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
calculate_change_amount(){
|
||||
this.frm.doc.change_amount = 0.0;
|
||||
this.frm.doc.base_change_amount = 0.0;
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
|
||||
|
||||
var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });
|
||||
|
||||
@@ -315,7 +315,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
setup_quality_inspection() {
|
||||
if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)) {
|
||||
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)
|
||||
const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)
|
||||
? "Incoming" : "Outgoing";
|
||||
|
||||
let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
|
||||
@@ -359,7 +359,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
make_payment_request() {
|
||||
let me = this;
|
||||
const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype))
|
||||
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
|
||||
? "Inward" : "Outward";
|
||||
|
||||
frappe.call({
|
||||
@@ -474,7 +474,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
setup_sms() {
|
||||
var me = this;
|
||||
let blacklist = ['Purchase Invoice', 'BOM'];
|
||||
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
|
||||
if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status)
|
||||
&& !blacklist.includes(this.frm.doctype)) {
|
||||
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
|
||||
}
|
||||
@@ -760,7 +760,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
on_submit() {
|
||||
if (["Purchase Invoice", "Sales Invoice"].includes(this.frm.doc.doctype)
|
||||
if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype)
|
||||
&& !this.frm.doc.update_stock) {
|
||||
return;
|
||||
}
|
||||
@@ -864,7 +864,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
var set_party_account = function(set_pricing) {
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
||||
if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) {
|
||||
if(me.frm.doc.doctype=="Sales Invoice") {
|
||||
var party_type = "Customer";
|
||||
var party_account_field = 'debit_to';
|
||||
@@ -899,7 +899,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
|
||||
['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
|
||||
in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
|
||||
erpnext.utils.get_shipping_address(this.frm, function() {
|
||||
set_party_account(set_pricing);
|
||||
});
|
||||
@@ -1239,20 +1239,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.frm.fields_dict.items.grid.toggle_enable("conversion_factor",
|
||||
((item.uom != item.stock_uom) && !frappe.meta.get_docfield(cur_frm.fields_dict.items.grid.doctype, "conversion_factor").read_only)? true: false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
qty(doc, cdt, cdn) {
|
||||
if (!this.frm.doc.__onload?.load_after_mapping) {
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
// item.pricing_rules = ''
|
||||
frappe.run_serially([
|
||||
() => this.remove_pricing_rule_for_item(item),
|
||||
() => this.conversion_factor(doc, cdt, cdn, true),
|
||||
() => this.apply_price_list(item, true), //reapply price list before applying pricing rule
|
||||
() => this.calculate_stock_uom_rate(doc, cdt, cdn),
|
||||
() => this.apply_pricing_rule(item, true)
|
||||
]);
|
||||
}
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
// item.pricing_rules = ''
|
||||
frappe.run_serially([
|
||||
() => this.remove_pricing_rule_for_item(item),
|
||||
() => this.conversion_factor(doc, cdt, cdn, true),
|
||||
() => this.apply_price_list(item, true), //reapply price list before applying pricing rule
|
||||
() => this.calculate_stock_uom_rate(doc, cdt, cdn),
|
||||
() => this.apply_pricing_rule(item, true)
|
||||
]);
|
||||
}
|
||||
|
||||
stock_qty(doc, cdt, cdn) {
|
||||
@@ -1267,11 +1266,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
calculate_stock_uom_rate(doc, cdt, cdn) {
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
|
||||
if (item?.rate) {
|
||||
item.stock_uom_rate = flt(item.rate) / flt(item.conversion_factor);
|
||||
refresh_field("stock_uom_rate", item.name, item.parentfield);
|
||||
}
|
||||
item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
|
||||
refresh_field("stock_uom_rate", item.name, item.parentfield);
|
||||
}
|
||||
service_stop_date(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
@@ -1614,7 +1610,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
"doctype": me.frm.doc.doctype,
|
||||
"name": me.frm.doc.name,
|
||||
"is_return": cint(me.frm.doc.is_return),
|
||||
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||
"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||
"conversion_factor": me.frm.doc.conversion_factor,
|
||||
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
||||
"coupon_code": me.frm.doc.coupon_code
|
||||
@@ -2223,7 +2219,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
});
|
||||
|
||||
this.frm.doc.items.forEach(item => {
|
||||
if (this.has_inspection_required(item)) {
|
||||
if (!item.quality_inspection) {
|
||||
let dialog_items = dialog.fields_dict.items;
|
||||
dialog_items.df.data.push({
|
||||
"docname": item.name,
|
||||
@@ -2247,20 +2243,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
}
|
||||
|
||||
has_inspection_required(item) {
|
||||
if (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose == "Manufacture" ) {
|
||||
if (item.is_finished_item && !item.quality_inspection) {
|
||||
return true;
|
||||
}
|
||||
} else if (!item.quality_inspection) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
get_method_for_payment() {
|
||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
|
||||
if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){
|
||||
if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){
|
||||
method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice";
|
||||
}else {
|
||||
method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order";
|
||||
@@ -2500,7 +2486,7 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close
|
||||
}
|
||||
|
||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||
if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) {
|
||||
if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) {
|
||||
item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward";
|
||||
} else {
|
||||
item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward";
|
||||
|
||||
@@ -218,7 +218,7 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
|
||||
|
||||
update_paid_amount(update_write_off) {
|
||||
var me = this;
|
||||
if (["change_amount", "write_off_amount"].includes(this.idx)) {
|
||||
if (in_list(["change_amount", "write_off_amount"], this.idx)) {
|
||||
var value = me.selected_mode.val();
|
||||
if (me.idx == "change_amount") {
|
||||
me.change_amount(value);
|
||||
|
||||
@@ -12,6 +12,14 @@ $.extend(erpnext.queries, {
|
||||
return { query: "erpnext.controllers.queries.lead_query" };
|
||||
},
|
||||
|
||||
customer: function () {
|
||||
return { query: "erpnext.controllers.queries.customer_query" };
|
||||
},
|
||||
|
||||
supplier: function () {
|
||||
return { query: "erpnext.controllers.queries.supplier_query" };
|
||||
},
|
||||
|
||||
item: function (filters) {
|
||||
var args = { query: "erpnext.controllers.queries.item_query" };
|
||||
if (filters) args["filters"] = filters;
|
||||
|
||||
@@ -28,11 +28,11 @@ erpnext.SMSManager = function SMSManager(doc) {
|
||||
"Purchase Receipt": "Items has been received against purchase receipt: " + doc.name,
|
||||
};
|
||||
|
||||
if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype))
|
||||
if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype))
|
||||
this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]);
|
||||
else if (doc.doctype === "Quotation")
|
||||
this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]);
|
||||
else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype))
|
||||
else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype))
|
||||
this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]);
|
||||
else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]);
|
||||
else if (doc.doctype == "Opportunity")
|
||||
|
||||
@@ -14,10 +14,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
|
||||
if (!args) {
|
||||
if (
|
||||
(frm.doctype != "Purchase Order" && frm.doc.customer) ||
|
||||
(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
|
||||
(frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype))
|
||||
) {
|
||||
let party_type = "Customer";
|
||||
if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) {
|
||||
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
|
||||
party_type = frm.doc.quotation_to;
|
||||
}
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ erpnext.sales_common = {
|
||||
if ((doc.packed_items || []).length) {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(true);
|
||||
|
||||
if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
|
||||
if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
|
||||
var help_msg =
|
||||
"<div class='alert alert-warning'>" +
|
||||
__(
|
||||
@@ -315,7 +315,7 @@ erpnext.sales_common = {
|
||||
}
|
||||
} else {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(false);
|
||||
if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
|
||||
if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
|
||||
frappe.meta.get_docfield(doc.doctype, "product_bundle_help", doc.name).options = "";
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ erpnext.sales_common = {
|
||||
|
||||
project() {
|
||||
let me = this;
|
||||
if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) {
|
||||
if (in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) {
|
||||
if (this.frm.doc.project) {
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.project.project.get_cost_center_name",
|
||||
|
||||
@@ -542,10 +542,6 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
frappe.throw(__("Please add atleast one Serial No / Batch No"));
|
||||
}
|
||||
|
||||
if (!warehouse) {
|
||||
frappe.throw(__("Please select a Warehouse"));
|
||||
}
|
||||
|
||||
frappe
|
||||
.call({
|
||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
|
||||
|
||||
@@ -548,7 +548,3 @@ body[data-route="pos"] {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ frappe.ui.form.on("Import Supplier Invoice", {
|
||||
},
|
||||
|
||||
toggle_read_only_fields: function (frm) {
|
||||
if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) {
|
||||
if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) {
|
||||
cur_frm.set_read_only();
|
||||
cur_frm.refresh_fields();
|
||||
frm.set_df_property("import_invoices", "hidden", 1);
|
||||
|
||||
@@ -583,7 +583,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-16 19:41:47.971815",
|
||||
"modified": "2023-12-28 13:15:36.298369",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
@@ -661,7 +661,7 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "customer_group,territory, mobile_no,primary_address",
|
||||
"search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user