Merge branch 'develop' into print-language

This commit is contained in:
barredterra
2024-05-27 18:52:05 +02:00
26 changed files with 5866 additions and 5019 deletions

View File

@@ -82,7 +82,7 @@
"icon": "fa fa-calendar", "icon": "fa fa-calendar",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2024-01-30 12:35:38.645968", "modified": "2024-05-27 17:29:55.560840",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Fiscal Year", "name": "Fiscal Year",
@@ -127,6 +127,10 @@
{ {
"read": 1, "read": 1,
"role": "Stock Manager" "role": "Stock Manager"
},
{
"read": 1,
"role": "Auditor"
} }
], ],
"show_name_in_global_search": 1, "show_name_in_global_search": 1,

View File

@@ -481,7 +481,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry: if closing_entry:
closing_entry.set_status(update=True, status="Failed") closing_entry.set_status(update=True, status="Failed")
if isinstance(error_message, list): if isinstance(error_message, list):
error_message = frappe.json.dumps(error_message) error_message = json.dumps(error_message)
closing_entry.db_set("error_message", error_message) closing_entry.db_set("error_message", error_message)
raise raise

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
import json
import frappe import frappe
from frappe import _, qb from frappe import _, qb
from frappe.model.document import Document from frappe.model.document import Document
@@ -502,7 +504,7 @@ def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None running_doc = None
if for_filter: if for_filter:
if isinstance(for_filter, str): if isinstance(for_filter, str):
for_filter = frappe.json.loads(for_filter) for_filter = json.loads(for_filter)
running_doc = frappe.db.get_value( running_doc = frappe.db.get_value(
"Process Payment Reconciliation", "Process Payment Reconciliation",

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import copy import copy
import json
import frappe import frappe
from frappe.model.dynamic_links import get_dynamic_link_map from frappe.model.dynamic_links import get_dynamic_link_map
@@ -3733,9 +3734,9 @@ class TestSalesInvoice(FrappeTestCase):
map_docs( map_docs(
method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
source_names=frappe.json.dumps([dn1.name, dn2.name]), source_names=json.dumps([dn1.name, dn2.name]),
target_doc=si, target_doc=si,
args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}), args=json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
) )
si.save().submit() si.save().submit()

View File

@@ -870,7 +870,8 @@
"label": "Purchase Order", "label": "Purchase Order",
"options": "Purchase Order", "options": "Purchase Order",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "column_break_92", "fieldname": "column_break_92",
@@ -926,7 +927,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:10:36.139679", "modified": "2024-05-23 16:36:18.970862",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
import json
import frappe import frappe
from frappe import _, qb from frappe import _, qb
from frappe.model.document import Document from frappe.model.document import Document
@@ -163,7 +165,7 @@ def get_linked_payments_for_doc(
@frappe.whitelist() @frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None): def create_unreconcile_doc_for_selection(selections=None):
if selections: if selections:
selections = frappe.json.loads(selections) selections = json.loads(selections)
# assuming each row is a unique voucher # assuming each row is a unique voucher
for row in selections: for row in selections:
unrecon = frappe.new_doc("Unreconcile Payment") unrecon = frappe.new_doc("Unreconcile Payment")

View File

@@ -56,7 +56,7 @@ def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
): ):
if isinstance(boolean, str): if isinstance(boolean, str):
boolean = frappe.json.loads(boolean) boolean = loads(boolean)
fiscal_years = get_fiscal_years( fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
@@ -1109,7 +1109,7 @@ def get_companies():
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent, company, is_root=False, include_disabled=False): def get_children(doctype, parent, company, is_root=False, include_disabled=False):
if isinstance(include_disabled, str): if isinstance(include_disabled, str):
include_disabled = frappe.json.loads(include_disabled) include_disabled = loads(include_disabled)
from erpnext.accounts.report.financial_statements import sort_accounts from erpnext.accounts.report.financial_statements import sort_accounts
parent_fieldname = "parent_" + doctype.lower().replace(" ", "_") parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")

View File

@@ -513,7 +513,7 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll
method: "frappe.desk.doctype.tag.tag.get_tagged_docs", method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
args: { args: {
doctype: "Supplier", doctype: "Supplier",
tag: args.tag, tag: "%" + args.tag + "%",
}, },
callback: load_suppliers, callback: load_suppliers,
}); });

View File

@@ -1942,7 +1942,7 @@ class AccountsController(TransactionBase):
def set_advance_payment_status(self): def set_advance_payment_status(self):
new_status = None new_status = None
stati = frappe.get_list( stati = frappe.get_all(
"Payment Request", "Payment Request",
{ {
"reference_doctype": self.doctype, "reference_doctype": self.doctype,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,14 @@ frappe.ui.form.on("Project", {
filters: filters, filters: filters,
}; };
}); });
frm.set_query("cost_center", () => {
return {
filters: {
company: frm.doc.company,
},
};
});
}, },
refresh: function (frm) { refresh: function (frm) {

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import json
import frappe import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
@@ -321,7 +323,7 @@ class TestCustomer(FrappeTestCase):
frappe.ValidationError, frappe.ValidationError,
update_child_qty_rate, update_child_qty_rate,
so.doctype, so.doctype,
frappe.json.dumps([modified_item]), json.dumps([modified_item]),
so.name, so.name,
) )

View File

@@ -770,13 +770,11 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
flt(doc.per_billed, precision("per_billed", doc)) < flt(doc.per_billed, precision("per_billed", doc)) <
100 + frappe.boot.sysdefaults.over_billing_allowance 100 + frappe.boot.sysdefaults.over_billing_allowance
) { ) {
if (frappe.model.can_create("Payment Request")) { this.frm.add_custom_button(
this.frm.add_custom_button( __("Payment Request"),
__("Payment Request"), () => this.make_payment_request(),
() => this.make_payment_request(), __("Create")
__("Create") );
);
}
if (frappe.model.can_create("Payment Entry")) { if (frappe.model.can_create("Payment Entry")) {
this.frm.add_custom_button( this.frm.add_custom_button(

View File

@@ -1140,7 +1140,8 @@
"hide_seconds": 1, "hide_seconds": 1,
"label": "Inter Company Order Reference", "label": "Inter Company Order Reference",
"options": "Purchase Order", "options": "Purchase Order",
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "project", "fieldname": "project",
@@ -1661,7 +1662,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2024-04-20 01:20:11.190908", "modified": "2024-05-27 18:51:54.905804",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

@@ -21,9 +21,17 @@ frappe.query_reports["Sales Analytics"] = {
}, },
{ {
fieldname: "doc_type", fieldname: "doc_type",
label: __("based_on"), label: __("Based On"),
fieldtype: "Select", fieldtype: "Select",
options: ["Sales Order", "Delivery Note", "Sales Invoice"], options: [
"All",
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"Sales Invoice (due)",
"Payment Entry",
],
default: "Sales Invoice", default: "Sales Invoice",
reqd: 1, reqd: 1,
}, },
@@ -42,14 +50,18 @@ frappe.query_reports["Sales Analytics"] = {
fieldname: "from_date", fieldname: "from_date",
label: __("From Date"), label: __("From Date"),
fieldtype: "Date", fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], default:
frappe.defaults.get_user_default("sales_start_date") ||
erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
reqd: 1, reqd: 1,
}, },
{ {
fieldname: "to_date", fieldname: "to_date",
label: __("To Date"), label: __("To Date"),
fieldtype: "Date", fieldtype: "Date",
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], default:
frappe.defaults.get_user_default("sales_end_date") ||
erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
reqd: 1, reqd: 1,
}, },
{ {
@@ -73,6 +85,19 @@ frappe.query_reports["Sales Analytics"] = {
default: "Monthly", default: "Monthly",
reqd: 1, reqd: 1,
}, },
{
fieldname: "curves",
label: __("Curves"),
fieldtype: "Select",
options: [
{ value: "select", label: __("Select") },
{ value: "all", label: __("All") },
{ value: "non-zeros", label: __("Non-Zeros") },
{ value: "total", label: __("Total Only") },
],
default: "select",
reqd: 1,
},
], ],
get_datatable_options(options) { get_datatable_options(options) {
return Object.assign(options, { return Object.assign(options, {

View File

@@ -10,17 +10,58 @@ from erpnext.accounts.utils import get_fiscal_year
def execute(filters=None): def execute(filters=None):
return Analytics(filters).run() filters = frappe._dict(filters or {})
# Special report showing all doctype totals on a single chart; overrides some filters
if filters.doc_type == "All":
filters.tree_type = "Customer"
filters.value_quantity = "Value"
filters.curves = "total"
output = None
for dt in [
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"Sales Invoice (due)",
"Payment Entry",
]:
filters.doc_type = dt
output = append_report(dt, output, Analytics(filters).run())
return output
else:
return Analytics(filters).run()
def append_report(dt, org, new):
# idx 1 is data, 3 is chart
new[1].insert(0, {"entity": dt}) # heading
new[1].append({}) # empty row
# datasets can be an empty list if no dates are supplied by the Dashboard Chart
if not new[3]["data"]["datasets"]:
new[3]["data"]["datasets"].append({"name": None, "values": []})
new[3]["data"]["datasets"][0]["name"] = dt # override curve name
if org:
org[1].extend(new[1])
org[3]["data"]["datasets"].extend(new[3]["data"]["datasets"])
return org
else:
return new
class Analytics: class Analytics:
def __init__(self, filters=None): def __init__(self, filters=None):
self.filters = frappe._dict(filters or {}) self.filters = frappe._dict(filters or {})
if self.filters.doc_type == "Payment Entry" and self.filters.value_quantity == "Quantity":
frappe.throw(_("Only Value available for Payment Entry"))
self.date_field = ( self.date_field = (
"transaction_date" "transaction_date"
if self.filters.doc_type in ["Sales Order", "Purchase Order"] if self.filters.doc_type in ["Quotation", "Sales Order", "Purchase Order"]
else "due_date"
if self.filters.doc_type == "Sales Invoice (due)"
else "posting_date" else "posting_date"
) )
if self.filters.doc_type.startswith("Sales Invoice"):
self.filters.doc_type = "Sales Invoice"
self.months = [ self.months = [
"Jan", "Jan",
"Feb", "Feb",
@@ -95,25 +136,37 @@ class Analytics:
self.get_rows() self.get_rows()
elif self.filters.tree_type == "Item": elif self.filters.tree_type == "Item":
if self.filters.doc_type == "Payment Entry":
self.data = []
return
self.get_sales_transactions_based_on_items() self.get_sales_transactions_based_on_items()
self.get_rows() self.get_rows()
elif self.filters.tree_type in ["Customer Group", "Supplier Group", "Territory"]: elif self.filters.tree_type in ["Customer Group", "Supplier Group", "Territory"]:
if self.filters.doc_type == "Payment Entry":
self.data = []
return
self.get_sales_transactions_based_on_customer_or_territory_group() self.get_sales_transactions_based_on_customer_or_territory_group()
self.get_rows_by_group() self.get_rows_by_group()
elif self.filters.tree_type == "Item Group": elif self.filters.tree_type == "Item Group":
if self.filters.doc_type == "Payment Entry":
self.data = []
return
self.get_sales_transactions_based_on_item_group() self.get_sales_transactions_based_on_item_group()
self.get_rows_by_group() self.get_rows_by_group()
elif self.filters.tree_type == "Order Type": elif self.filters.tree_type == "Order Type":
if self.filters.doc_type != "Sales Order": if self.filters.doc_type not in ["Quotation", "Sales Order"]:
self.data = [] self.data = []
return return
self.get_sales_transactions_based_on_order_type() self.get_sales_transactions_based_on_order_type()
self.get_rows_by_group() self.get_rows_by_group()
elif self.filters.tree_type == "Project": elif self.filters.tree_type == "Project":
if self.filters.doc_type == "Quotation":
self.data = []
return
self.get_sales_transactions_based_on_project() self.get_sales_transactions_based_on_project()
self.get_rows() self.get_rows()
@@ -141,11 +194,22 @@ class Analytics:
value_field = "total_qty as value_field" value_field = "total_qty as value_field"
if self.filters.tree_type == "Customer": if self.filters.tree_type == "Customer":
entity = "customer as entity"
entity_name = "customer_name as entity_name" entity_name = "customer_name as entity_name"
if self.filters.doc_type == "Quotation":
entity = "party_name as entity"
elif self.filters.doc_type == "Payment Entry":
entity = "party as entity"
entity_name = "party_name as entity_name"
value_field = "base_received_amount as value_field"
else:
entity = "customer as entity"
else: else:
entity = "supplier as entity" entity = "supplier as entity"
entity_name = "supplier_name as entity_name" entity_name = "supplier_name as entity_name"
if self.filters.doc_type == "Payment Entry":
entity = "party as entity"
entity_name = "party_name as entity_name"
value_field = "base_paid_amount as value_field"
self.entries = frappe.get_all( self.entries = frappe.get_all(
self.filters.doc_type, self.filters.doc_type,
@@ -232,6 +296,9 @@ class Analytics:
else: else:
value_field = "total_qty as value_field" value_field = "total_qty as value_field"
if self.filters.doc_type == "Payment Entry":
value_field = "base_received_amount as value_field"
entity = "project as entity" entity = "project as entity"
self.entries = frappe.get_all( self.entries = frappe.get_all(
@@ -401,7 +468,33 @@ class Analytics:
labels = [d.get("label") for d in self.columns[3 : length - 1]] labels = [d.get("label") for d in self.columns[3 : length - 1]]
else: else:
labels = [d.get("label") for d in self.columns[1 : length - 1]] labels = [d.get("label") for d in self.columns[1 : length - 1]]
self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"}
datasets = []
if self.filters.curves != "select":
for curve in self.data:
data = {
"name": curve.get("entity_name", curve["entity"]),
"values": [curve[scrub(label)] for label in labels],
}
if self.filters.curves == "non-zeros" and not sum(data["values"]):
continue
elif self.filters.curves == "total" and "indent" in curve:
if curve["indent"] == 0:
datasets.append(data)
elif self.filters.curves == "total":
if datasets:
a = [
data["values"][idx] + datasets[0]["values"][idx]
for idx in range(len(data["values"]))
]
datasets[0]["values"] = a
else:
datasets.append(data)
datasets[0]["name"] = _("Total")
else:
datasets.append(data)
self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
if self.filters["value_quantity"] == "Value": if self.filters["value_quantity"] == "Value":
self.chart["fieldtype"] = "Currency" self.chart["fieldtype"] = "Currency"

View File

@@ -779,7 +779,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2024-05-16 12:39:54.694232", "modified": "2024-05-27 17:32:49.057386",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",
@@ -835,6 +835,10 @@
"role": "Accounts Manager", "role": "Accounts Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"role": "Auditor",
"select": 1
} }
], ],
"show_name_in_global_search": 1, "show_name_in_global_search": 1,

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import json
import frappe import frappe
from frappe.utils.nestedset import NestedSet, get_root_of from frappe.utils.nestedset import NestedSet, get_root_of
@@ -71,7 +73,7 @@ def get_abbreviated_name(name, company):
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, include_disabled=False): def get_children(doctype, parent=None, company=None, is_root=False, include_disabled=False):
if isinstance(include_disabled, str): if isinstance(include_disabled, str):
include_disabled = frappe.json.loads(include_disabled) include_disabled = json.loads(include_disabled)
fields = ["name as value", "is_group as expandable"] fields = ["name as value", "is_group as expandable"]
filters = {} filters = {}

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import json
import frappe import frappe
from frappe import _, throw from frappe import _, throw
from frappe.contacts.address_and_contact import load_address_and_contact from frappe.contacts.address_and_contact import load_address_and_contact
@@ -186,7 +188,7 @@ def get_children(doctype, parent=None, company=None, is_root=False, include_disa
parent = "" parent = ""
if isinstance(include_disabled, str): if isinstance(include_disabled, str):
include_disabled = frappe.json.loads(include_disabled) include_disabled = json.loads(include_disabled)
fields = ["name as value", "is_group as expandable"] fields = ["name as value", "is_group as expandable"]
filters = [ filters = [