mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-21 07:38:29 +00:00
Merge pull request #39091 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -137,7 +137,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
||||
args: {
|
||||
bank_account: frm.doc.bank_account,
|
||||
till_date: frm.doc.bank_statement_from_date,
|
||||
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1)
|
||||
},
|
||||
callback: (response) => {
|
||||
frm.set_value("account_opening_balance", response.message);
|
||||
|
||||
@@ -1,457 +1,152 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2018-06-18 16:51:49.994750",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"user",
|
||||
"date",
|
||||
"from_time",
|
||||
"time",
|
||||
"expense",
|
||||
"custody",
|
||||
"returns",
|
||||
"outstanding_amount",
|
||||
"payments",
|
||||
"net_amount",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "POS-CLO-",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "POS-CLO-",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "To Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "expense",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expense",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Expense"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "custody",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Custody",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Custody"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "returns",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Returns",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Outstanding Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.0",
|
||||
"fieldname": "payments",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payments",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cashier Closing Payments",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Cashier Closing Payments"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "net_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Net Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Cashier Closing",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-19 08:35:24.157327",
|
||||
"links": [],
|
||||
"modified": "2023-12-28 13:15:46.858427",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cashier Closing",
|
||||
"name_case": "",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -81,7 +81,6 @@
|
||||
},
|
||||
{
|
||||
"account": "Sales - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"credit_in_account_currency": 400.0,
|
||||
"debit_in_account_currency": 0.0,
|
||||
"doctype": "Journal Entry Account",
|
||||
|
||||
@@ -705,7 +705,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = (
|
||||
frappe.qb.from_(p_inv)
|
||||
.from_(p_item)
|
||||
.select(Sum(p_item.qty).as_("qty"))
|
||||
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||
.where(
|
||||
(p_inv.name == p_item.parent)
|
||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||
@@ -716,7 +716,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -415,7 +415,7 @@ def reconcile(doc: None | str = None) -> None:
|
||||
# Update the parent doc about the exception
|
||||
frappe.db.rollback()
|
||||
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
if traceback:
|
||||
message = "Traceback: <br>" + traceback
|
||||
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
||||
|
||||
@@ -976,11 +976,17 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
|
||||
@@ -43,7 +43,7 @@ def start_payment_ledger_repost(docname=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
if traceback:
|
||||
message = "Traceback: <br>" + traceback
|
||||
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
||||
|
||||
@@ -2,7 +2,44 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.query_reports["Budget Variance Report"] = {
|
||||
"filters": [
|
||||
"filters": get_filters(),
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname.includes(__("variance"))) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
function get_filters() {
|
||||
function get_dimensions() {
|
||||
let result = [];
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
|
||||
args: {
|
||||
'with_cost_center_and_project': true
|
||||
},
|
||||
async: false,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
result = r.message[0].map(elem => elem.document_type);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
let budget_against_options = get_dimensions();
|
||||
|
||||
let filters = [
|
||||
{
|
||||
fieldname: "from_fiscal_year",
|
||||
label: __("From Fiscal Year"),
|
||||
@@ -44,9 +81,13 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldname: "budget_against",
|
||||
label: __("Budget Against"),
|
||||
fieldtype: "Select",
|
||||
options: ["Cost Center", "Project"],
|
||||
options: budget_against_options,
|
||||
default: "Cost Center",
|
||||
reqd: 1,
|
||||
get_data: function() {
|
||||
console.log(this.options);
|
||||
return ["Emacs", "Rocks"];
|
||||
},
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||
frappe.query_report.refresh();
|
||||
@@ -71,24 +112,8 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldtype: "Check",
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
]
|
||||
|
||||
if (column.fieldname.includes(__("variance"))) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe import _, qb, scrub
|
||||
from frappe.utils import getdate, nowdate
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ class PartyLedgerSummaryReport(object):
|
||||
"""
|
||||
Additional Columns for 'User Permission' based access control
|
||||
"""
|
||||
from frappe import qb
|
||||
|
||||
if self.filters.party_type == "Customer":
|
||||
self.territories = frappe._dict({})
|
||||
@@ -365,13 +364,29 @@ class PartyLedgerSummaryReport(object):
|
||||
|
||||
def get_party_adjustment_amounts(self):
|
||||
conditions = self.prepare_conditions()
|
||||
income_or_expense = (
|
||||
"Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||
account_type = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||
income_or_expense_accounts = frappe.db.get_all(
|
||||
"Account", filters={"account_type": account_type, "company": self.filters.company}, pluck="name"
|
||||
)
|
||||
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
||||
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
||||
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
||||
|
||||
gl = qb.DocType("GL Entry")
|
||||
if not income_or_expense_accounts:
|
||||
# prevent empty 'in' condition
|
||||
income_or_expense_accounts.append("")
|
||||
|
||||
accounts_query = (
|
||||
qb.from_(gl)
|
||||
.select(gl.voucher_type, gl.voucher_no)
|
||||
.where(
|
||||
(gl.account.isin(income_or_expense_accounts))
|
||||
& (gl.posting_date.gte(self.filters.from_date))
|
||||
& (gl.posting_date.lte(self.filters.to_date))
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@@ -381,16 +396,15 @@ class PartyLedgerSummaryReport(object):
|
||||
where
|
||||
docstatus < 2 and is_cancelled = 0
|
||||
and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2
|
||||
{accounts_query}
|
||||
) and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle
|
||||
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
||||
)
|
||||
""".format(
|
||||
conditions=conditions, income_or_expense=income_or_expense
|
||||
""".format(
|
||||
accounts_query=accounts_query,
|
||||
conditions=conditions,
|
||||
),
|
||||
self.filters,
|
||||
as_dict=True,
|
||||
@@ -414,7 +428,7 @@ class PartyLedgerSummaryReport(object):
|
||||
elif gle.party:
|
||||
parties.setdefault(gle.party, 0)
|
||||
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense:
|
||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == account_type:
|
||||
accounts.setdefault(gle.account, 0)
|
||||
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
||||
else:
|
||||
|
||||
@@ -309,7 +309,8 @@ def get_conditions(filters):
|
||||
|
||||
def get_items(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -381,7 +381,8 @@ def get_group_by_conditions(filters, doctype):
|
||||
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
conditions = get_conditions(filters, additional_conditions)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -119,8 +119,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -52,6 +52,12 @@ frappe.query_reports["Purchase Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,13 +4,22 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
apply_common_conditions,
|
||||
get_advance_taxes_and_charges,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -21,9 +30,15 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("supplier"):
|
||||
frappe.throw(_("Please select a supplier for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -33,14 +48,28 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments
|
||||
)
|
||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||
suppliers = list(set(d.supplier for d in invoice_list))
|
||||
supplier_details = get_supplier_details(suppliers)
|
||||
supplier_details = get_party_details("Supplier", suppliers)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Supplier", filters.supplier, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"payable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
# invoice details
|
||||
@@ -48,24 +77,23 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
||||
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||
|
||||
row = [
|
||||
inv.name,
|
||||
inv.posting_date,
|
||||
inv.supplier,
|
||||
inv.supplier_name,
|
||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
||||
supplier_details.get(inv.supplier), # supplier_group
|
||||
inv.tax_id,
|
||||
inv.credit_to,
|
||||
inv.mode_of_payment,
|
||||
", ".join(project),
|
||||
inv.bill_no,
|
||||
inv.bill_date,
|
||||
inv.remarks,
|
||||
", ".join(purchase_order),
|
||||
", ".join(purchase_receipt),
|
||||
company_currency,
|
||||
]
|
||||
row = {
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"supplier_id": inv.supplier,
|
||||
"supplier_name": inv.supplier_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"supplier_group": supplier_details.get(inv.supplier).get("supplier_group"), # supplier_group
|
||||
"tax_id": supplier_details.get(inv.supplier).get("tax_id"),
|
||||
"payable_account": inv.credit_to,
|
||||
"mode_of_payment": inv.mode_of_payment,
|
||||
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
|
||||
"remarks": inv.remarks,
|
||||
"purchase_order": ", ".join(purchase_order),
|
||||
"purchase_receipt": ", ".join(purchase_receipt),
|
||||
"currency": company_currency,
|
||||
}
|
||||
|
||||
# map expense values
|
||||
base_net_total = 0
|
||||
@@ -75,14 +103,16 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
else:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
base_net_total += expense_amount
|
||||
row.append(expense_amount)
|
||||
row.update({frappe.scrub(expense_acc): expense_amount})
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||
row.update(
|
||||
{frappe.scrub(account + "_unrealized"): flt(internal_invoice_map.get((inv.name, account)))}
|
||||
)
|
||||
|
||||
# net total
|
||||
row.append(base_net_total or inv.base_net_total)
|
||||
row.update({"net_total": base_net_total or inv.base_net_total})
|
||||
|
||||
# tax account
|
||||
total_tax = 0
|
||||
@@ -90,45 +120,190 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if tax_acc not in expense_accounts:
|
||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
||||
total_tax += tax_amount
|
||||
row.append(tax_amount)
|
||||
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||
|
||||
# total tax, grand total, rounded total & outstanding amount
|
||||
row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 0), inv.outstanding_amount]
|
||||
row.update(
|
||||
{
|
||||
"total_tax": total_tax,
|
||||
"grand_total": inv.base_grand_total,
|
||||
"rounded_total": inv.base_rounded_total,
|
||||
"outstanding_amount": inv.outstanding_amount,
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
_("Invoice") + ":Link/Purchase Invoice:120",
|
||||
_("Posting Date") + ":Date:80",
|
||||
_("Supplier Id") + "::120",
|
||||
_("Supplier Name") + "::120",
|
||||
{
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Supplier"),
|
||||
"fieldname": "supplier_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
_("Supplier Group") + ":Link/Supplier Group:120",
|
||||
_("Tax Id") + "::80",
|
||||
_("Payable Account") + ":Link/Account:120",
|
||||
_("Mode of Payment") + ":Link/Mode of Payment:80",
|
||||
_("Project") + ":Link/Project:80",
|
||||
_("Bill No") + "::120",
|
||||
_("Bill Date") + ":Date:80",
|
||||
_("Remarks") + "::150",
|
||||
_("Purchase Order") + ":Link/Purchase Order:100",
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Supplier Group"),
|
||||
"fieldname": "supplier_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Payable Account"),
|
||||
"fieldname": "payable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Bill No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 120},
|
||||
{"label": _("Bill Date"), "fieldname": "bill_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"fieldname": "purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "payable_account",
|
||||
"label": _("Payable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[1]
|
||||
+ [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
+ account_columns[2]
|
||||
+ [
|
||||
{
|
||||
"label": _("Total Tax"),
|
||||
"fieldname": "total_tax",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 120}]
|
||||
return columns, accounts[0], accounts[2], accounts[1]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
expense_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
expense_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list(
|
||||
"""select distinct expense_account
|
||||
@@ -139,15 +314,18 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple([inv.name for inv in invoice_list]),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabPurchase Taxes and Charges` where parenttype = 'Purchase Invoice'
|
||||
and docstatus = 1 and (account_head is not null and account_head != '')
|
||||
and category in ('Total', 'Valuation and Total')
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
purchase_taxes_query = get_taxes_query(
|
||||
invoice_list, "Purchase Taxes and Charges", "Purchase Invoice"
|
||||
)
|
||||
purchase_tax_accounts = purchase_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = purchase_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -158,108 +336,109 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||
unrealized_profit_loss_account_columns = [
|
||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
||||
]
|
||||
tax_columns = [
|
||||
(account + ":Currency/currency:120")
|
||||
for account in tax_accounts
|
||||
if account not in expense_accounts
|
||||
]
|
||||
for account in expense_accounts:
|
||||
expense_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ expense_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ [_("Net Total") + ":Currency/currency:120"]
|
||||
+ tax_columns
|
||||
+ [
|
||||
_("Total Tax") + ":Currency/currency:120",
|
||||
_("Grand Total") + ":Currency/currency:120",
|
||||
_("Rounded Total") + ":Currency/currency:120",
|
||||
_("Outstanding Amount") + ":Currency/currency:120",
|
||||
]
|
||||
)
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = [expense_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [expense_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
if filters.get("supplier"):
|
||||
conditions += " and supplier = %(supplier)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date>=%(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date<=%(to_date)s"
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
|
||||
|
||||
if filters.get("cost_center"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.cost_center, '') = %(cost_center)s)"""
|
||||
|
||||
if filters.get("warehouse"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.warehouse, '') = %(warehouse)s)"""
|
||||
|
||||
if filters.get("item_group"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s)"""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
|
||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
||||
mode_of_payment {0}
|
||||
from `tabPurchase Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
query = (
|
||||
frappe.qb.from_(pi)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("doctype"),
|
||||
pi.name,
|
||||
pi.posting_date,
|
||||
pi.credit_to,
|
||||
pi.supplier,
|
||||
pi.supplier_name,
|
||||
pi.tax_id,
|
||||
pi.bill_no,
|
||||
pi.bill_date,
|
||||
pi.remarks,
|
||||
pi.base_net_total,
|
||||
pi.base_grand_total,
|
||||
pi.base_rounded_total,
|
||||
pi.outstanding_amount,
|
||||
pi.mode_of_payment,
|
||||
)
|
||||
.where((pi.docstatus == 1))
|
||||
.orderby(pi.posting_date, pi.name, order=Order.desc)
|
||||
)
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
|
||||
if filters.get("supplier"):
|
||||
query = query.where(pi.supplier == filters.supplier)
|
||||
|
||||
query = get_conditions(filters, query, "Purchase Invoice")
|
||||
|
||||
query = apply_common_conditions(
|
||||
filters, query, doctype="Purchase Invoice", child_doctype="Purchase Invoice Item"
|
||||
)
|
||||
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_conditions(filters, query, doctype):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
query = query.where(parent_doc.mode_of_payment == filters.mode_of_payment)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="credit_to",
|
||||
account_fieldname="paid_to",
|
||||
party="supplier",
|
||||
party_name="supplier_name",
|
||||
party_account=get_party_account("Supplier", filters.supplier, filters.company),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_expense_map(invoice_list):
|
||||
expense_details = frappe.db.sql(
|
||||
@@ -300,7 +479,9 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
def get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments=False
|
||||
):
|
||||
tax_details = frappe.db.sql(
|
||||
"""
|
||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||
@@ -315,6 +496,9 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in expense_accounts:
|
||||
@@ -382,17 +566,3 @@ def get_account_details(invoice_list):
|
||||
account_map[acc.name] = acc.parent_account
|
||||
|
||||
return account_map
|
||||
|
||||
|
||||
def get_supplier_details(suppliers):
|
||||
supplier_details = {}
|
||||
for supp in frappe.db.sql(
|
||||
"""select name, supplier_group from `tabSupplier`
|
||||
where name in (%s)"""
|
||||
% ", ".join(["%s"] * len(suppliers)),
|
||||
tuple(suppliers),
|
||||
as_dict=1,
|
||||
):
|
||||
supplier_details.setdefault(supp.name, supp.supplier_group)
|
||||
|
||||
return supplier_details
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_months, getdate, today
|
||||
|
||||
from erpnext.accounts.report.purchase_register.purchase_register import execute
|
||||
|
||||
|
||||
class TestPurchaseRegister(FrappeTestCase):
|
||||
def test_purchase_register(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6", from_date=add_months(today(), -1), to_date=today()
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][0])
|
||||
self.assertEqual(first_row.voucher_type, "Purchase Invoice")
|
||||
self.assertEqual(first_row.voucher_no, pi.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.net_total, 1000)
|
||||
self.assertEqual(first_row.total_tax, 100)
|
||||
self.assertEqual(first_row.grand_total, 1100)
|
||||
|
||||
def test_purchase_register_ledger_view(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6",
|
||||
from_date=add_months(today(), -1),
|
||||
to_date=today(),
|
||||
include_payments=True,
|
||||
supplier="_Test Supplier",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
pe = make_payment_entry()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][2])
|
||||
self.assertEqual(first_row.voucher_type, "Payment Entry")
|
||||
self.assertEqual(first_row.voucher_no, pe.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.debit, 0)
|
||||
self.assertEqual(first_row.credit, 600)
|
||||
self.assertEqual(first_row.balance, 500)
|
||||
|
||||
|
||||
def make_purchase_invoice():
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
gst_acc = create_account(
|
||||
account_name="GST",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TC6",
|
||||
company="_Test Company 6",
|
||||
account_currency="INR",
|
||||
)
|
||||
create_warehouse(warehouse_name="_Test Warehouse - _TC6", company="_Test Company 6")
|
||||
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company 6")
|
||||
pi = create_purchase_invoice_with_taxes()
|
||||
pi.submit()
|
||||
return pi
|
||||
|
||||
|
||||
def create_purchase_invoice_with_taxes():
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Invoice",
|
||||
"posting_date": today(),
|
||||
"supplier": "_Test Supplier",
|
||||
"company": "_Test Company 6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"taxes_and_charges": "",
|
||||
"currency": "INR",
|
||||
"credit_to": "Creditors - _TC6",
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1,
|
||||
"rate": 1000,
|
||||
"expense_account": "Stock Received But Not Billed - _TC6",
|
||||
}
|
||||
],
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "GST - _TC6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"add_deduct_tax": "Add",
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "Actual",
|
||||
"description": "Shipping Charges",
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"parentfield": "taxes",
|
||||
"rate": 100,
|
||||
"tax_amount": 100.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
frappe.set_user("Administrator")
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
|
||||
return create_payment_entry(
|
||||
company="_Test Company 6",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
payment_type="Pay",
|
||||
paid_from="Cash - _TC6",
|
||||
paid_to="Creditors - _TC6",
|
||||
paid_amount=600,
|
||||
save=1,
|
||||
submit=1,
|
||||
)
|
||||
@@ -64,6 +64,12 @@ frappe.query_reports["Sales Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,13 +5,22 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
apply_common_conditions,
|
||||
get_advance_taxes_and_charges,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -22,9 +31,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = frappe._dict({})
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("customer"):
|
||||
frappe.throw(_("Please select a customer for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, income_accounts, unrealized_profit_loss_accounts, tax_accounts = get_columns(
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -34,13 +49,29 @@ def _execute(filters, additional_table_columns=None):
|
||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_income_map, income_accounts
|
||||
invoice_list, invoice_income_map, income_accounts, include_payments
|
||||
)
|
||||
# Cost Center & Warehouse Map
|
||||
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
||||
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
|
||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
|
||||
customers = list(set(d.customer for d in invoice_list))
|
||||
customer_details = get_party_details("Customer", customers)
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Customer", filters.customer, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"receivable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
@@ -51,14 +82,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||
|
||||
row = {
|
||||
"invoice": inv.name,
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"customer": inv.customer,
|
||||
"customer_name": inv.customer_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"customer_group": inv.get("customer_group"),
|
||||
"territory": inv.get("territory"),
|
||||
"tax_id": inv.get("tax_id"),
|
||||
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
||||
"territory": customer_details.get(inv.customer).get("territory"),
|
||||
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
||||
"receivable_account": inv.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||
"project": inv.project,
|
||||
@@ -66,7 +98,7 @@ def _execute(filters, additional_table_columns=None):
|
||||
"remarks": inv.remarks,
|
||||
"sales_order": ", ".join(sales_order),
|
||||
"delivery_note": ", ".join(delivery_note),
|
||||
"cost_center": ", ".join(cost_center),
|
||||
"cost_center": ", ".join(cost_center) if inv.doctype == "Sales Invoice" else inv.cost_center,
|
||||
"warehouse": ", ".join(warehouse),
|
||||
"currency": company_currency,
|
||||
}
|
||||
@@ -116,19 +148,36 @@ def _execute(filters, additional_table_columns=None):
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Sales Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
{
|
||||
"label": _("Invoice"),
|
||||
"fieldname": "invoice",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Invoice",
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
@@ -142,83 +191,156 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
{"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "receivable_account",
|
||||
"label": _("Receivable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 150},
|
||||
{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
}
|
||||
]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
if not include_payments:
|
||||
total_columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[2]
|
||||
+ net_total_column
|
||||
+ account_columns[1]
|
||||
+ total_columns
|
||||
)
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150}]
|
||||
return columns, accounts[0], accounts[1], accounts[2]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
income_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
income_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
@@ -230,14 +352,16 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
sales_taxes_query = get_taxes_query(invoice_list, "Sales Taxes and Charges", "Sales Invoice")
|
||||
sales_tax_accounts = sales_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = sales_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -283,134 +407,82 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
}
|
||||
)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
columns = [income_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [income_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ income_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ net_total_column
|
||||
+ tax_columns
|
||||
+ total_columns
|
||||
)
|
||||
|
||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False) or []
|
||||
accounting_dimensions_list = [d.fieldname for d in accounting_dimensions]
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
|
||||
if filters.get("customer") and "customer" not in accounting_dimensions_list:
|
||||
conditions += " and customer = %(customer)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date >= %(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " and owner = %(owner)s"
|
||||
|
||||
def get_sales_invoice_item_field_condition(field, table="Sales Invoice Item") -> str:
|
||||
if not filters.get(field) or field in accounting_dimensions_list:
|
||||
return ""
|
||||
return f""" and exists(select name from `tab{table}`
|
||||
where parent=`tabSales Invoice`.name
|
||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||
conditions += get_sales_invoice_item_field_condition("brand")
|
||||
conditions += get_sales_invoice_item_field_condition("item_group")
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabSales Invoice Item`
|
||||
where parent=`tabSales Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select name, posting_date, debit_to, project, customer,
|
||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
query = (
|
||||
frappe.qb.from_(si)
|
||||
.select(
|
||||
ConstantColumn("Sales Invoice").as_("doctype"),
|
||||
si.name,
|
||||
si.posting_date,
|
||||
si.debit_to,
|
||||
si.project,
|
||||
si.customer,
|
||||
si.customer_name,
|
||||
si.owner,
|
||||
si.remarks,
|
||||
si.territory,
|
||||
si.tax_id,
|
||||
si.customer_group,
|
||||
si.base_net_total,
|
||||
si.base_grand_total,
|
||||
si.base_rounded_total,
|
||||
si.outstanding_amount,
|
||||
si.is_internal_customer,
|
||||
si.represents_company,
|
||||
si.company,
|
||||
)
|
||||
.where((si.docstatus == 1))
|
||||
.orderby(si.posting_date, si.name, order=Order.desc)
|
||||
)
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
|
||||
if filters.get("customer"):
|
||||
query = query.where(si.customer == filters.customer)
|
||||
|
||||
query = get_conditions(filters, query, "Sales Invoice")
|
||||
query = apply_common_conditions(
|
||||
filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
|
||||
)
|
||||
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_conditions(filters, query, doctype):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
if filters.get("owner"):
|
||||
query = query.where(parent_doc.owner == filters.owner)
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
payment_doc = frappe.qb.DocType("Sales Invoice Payment")
|
||||
query = query.inner_join(payment_doc).on(parent_doc.name == payment_doc.parent)
|
||||
query = query.where(payment_doc.mode_of_payment == filters.mode_of_payment).distinct()
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="debit_to",
|
||||
account_fieldname="paid_from",
|
||||
party="customer",
|
||||
party_name="customer_name",
|
||||
party_account=get_party_account("Customer", filters.customer, filters.company),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_income_map(invoice_list):
|
||||
income_details = frappe.db.sql(
|
||||
@@ -447,7 +519,7 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts, include_payments=False):
|
||||
tax_details = frappe.db.sql(
|
||||
"""select parent, account_head,
|
||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||
@@ -457,6 +529,9 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in income_accounts:
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import frappe
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
|
||||
from pypika import Order
|
||||
|
||||
from erpnext import get_company_currency, get_default_company
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
__exchange_rates = {}
|
||||
@@ -165,7 +173,7 @@ def get_query_columns(report_columns):
|
||||
else:
|
||||
columns.append(fieldname)
|
||||
|
||||
return ", " + ", ".join(columns)
|
||||
return columns
|
||||
|
||||
|
||||
def get_values_for_columns(report_columns, report_row):
|
||||
@@ -179,3 +187,203 @@ def get_values_for_columns(report_columns, report_row):
|
||||
values[fieldname] = report_row.get(fieldname)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def get_party_details(party_type, party_list):
|
||||
party_details = {}
|
||||
party = frappe.qb.DocType(party_type)
|
||||
query = frappe.qb.from_(party).select(party.name, party.tax_id).where(party.name.isin(party_list))
|
||||
if party_type == "Supplier":
|
||||
query = query.select(party.supplier_group)
|
||||
else:
|
||||
query = query.select(party.customer_group, party.territory)
|
||||
|
||||
party_detail_list = query.run(as_dict=True)
|
||||
for party_dict in party_detail_list:
|
||||
party_details[party_dict.name] = party_dict
|
||||
return party_details
|
||||
|
||||
|
||||
def get_taxes_query(invoice_list, doctype, parenttype):
|
||||
taxes = frappe.qb.DocType(doctype)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(taxes)
|
||||
.select(taxes.account_head)
|
||||
.distinct()
|
||||
.where(
|
||||
(taxes.parenttype == parenttype)
|
||||
& (taxes.docstatus == 1)
|
||||
& (taxes.account_head.isnotnull())
|
||||
& (taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
)
|
||||
.orderby(taxes.account_head)
|
||||
)
|
||||
|
||||
if doctype == "Purchase Taxes and Charges":
|
||||
return query.where(taxes.category.isin(["Total", "Valuation and Total"]))
|
||||
elif doctype == "Sales Taxes and Charges":
|
||||
return query
|
||||
return query.where(taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
|
||||
|
||||
def get_journal_entries(filters, args):
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
journal_account = frappe.qb.DocType("Journal Entry Account")
|
||||
query = (
|
||||
frappe.qb.from_(je)
|
||||
.inner_join(journal_account)
|
||||
.on(je.name == journal_account.parent)
|
||||
.select(
|
||||
je.voucher_type.as_("doctype"),
|
||||
je.name,
|
||||
je.posting_date,
|
||||
journal_account.account.as_(args.account),
|
||||
journal_account.party.as_(args.party),
|
||||
journal_account.party.as_(args.party_name),
|
||||
je.bill_no,
|
||||
je.bill_date,
|
||||
je.remark.as_("remarks"),
|
||||
je.total_amount.as_("base_net_total"),
|
||||
je.total_amount.as_("base_grand_total"),
|
||||
je.mode_of_payment,
|
||||
journal_account.project,
|
||||
)
|
||||
.where(
|
||||
(je.voucher_type == "Journal Entry")
|
||||
& (journal_account.party == filters.get(args.party))
|
||||
& (journal_account.account == args.party_account)
|
||||
)
|
||||
.orderby(je.posting_date, je.name, order=Order.desc)
|
||||
)
|
||||
query = apply_common_conditions(filters, query, doctype="Journal Entry", payments=True)
|
||||
|
||||
journal_entries = query.run(as_dict=True)
|
||||
return journal_entries
|
||||
|
||||
|
||||
def get_payment_entries(filters, args):
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
query = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("doctype"),
|
||||
pe.name,
|
||||
pe.posting_date,
|
||||
pe[args.account_fieldname].as_(args.account),
|
||||
pe.party.as_(args.party),
|
||||
pe.party_name.as_(args.party_name),
|
||||
pe.remarks,
|
||||
pe.paid_amount.as_("base_net_total"),
|
||||
pe.paid_amount_after_tax.as_("base_grand_total"),
|
||||
pe.mode_of_payment,
|
||||
pe.project,
|
||||
pe.cost_center,
|
||||
)
|
||||
.where(
|
||||
(pe.party == filters.get(args.party)) & (pe[args.account_fieldname] == args.party_account)
|
||||
)
|
||||
.orderby(pe.posting_date, pe.name, order=Order.desc)
|
||||
)
|
||||
query = apply_common_conditions(filters, query, doctype="Payment Entry", payments=True)
|
||||
payment_entries = query.run(as_dict=True)
|
||||
return payment_entries
|
||||
|
||||
|
||||
def apply_common_conditions(filters, query, doctype, child_doctype=None, payments=False):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
if child_doctype:
|
||||
child_doc = frappe.qb.DocType(child_doctype)
|
||||
|
||||
join_required = False
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(parent_doc.company == filters.company)
|
||||
if filters.get("from_date"):
|
||||
query = query.where(parent_doc.posting_date >= filters.from_date)
|
||||
if filters.get("to_date"):
|
||||
query = query.where(parent_doc.posting_date <= filters.to_date)
|
||||
|
||||
if payments:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(parent_doc.cost_center == filters.cost_center)
|
||||
else:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(child_doc.cost_center == filters.cost_center)
|
||||
join_required = True
|
||||
if filters.get("warehouse"):
|
||||
query = query.where(child_doc.warehouse == filters.warehouse)
|
||||
join_required = True
|
||||
if filters.get("item_group"):
|
||||
query = query.where(child_doc.item_group == filters.item_group)
|
||||
join_required = True
|
||||
|
||||
if not payments:
|
||||
if filters.get("brand"):
|
||||
query = query.where(child_doc.brand == filters.brand)
|
||||
join_required = True
|
||||
|
||||
if join_required:
|
||||
query = query.inner_join(child_doc).on(parent_doc.name == child_doc.parent)
|
||||
query = query.distinct()
|
||||
|
||||
if parent_doc.get_table_name() != "tabJournal Entry":
|
||||
query = filter_invoices_based_on_dimensions(filters, query, parent_doc)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_advance_taxes_and_charges(invoice_list):
|
||||
adv_taxes = frappe.qb.DocType("Advance Taxes and Charges")
|
||||
return (
|
||||
frappe.qb.from_(adv_taxes)
|
||||
.select(
|
||||
adv_taxes.parent,
|
||||
adv_taxes.account_head,
|
||||
(
|
||||
frappe.qb.terms.Case()
|
||||
.when(adv_taxes.add_deduct_tax == "Add", Sum(adv_taxes.base_tax_amount))
|
||||
.else_(Sum(adv_taxes.base_tax_amount) * -1)
|
||||
).as_("tax_amount"),
|
||||
)
|
||||
.where(
|
||||
(adv_taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
& (adv_taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
& (adv_taxes.base_tax_amount != 0)
|
||||
)
|
||||
.groupby(adv_taxes.parent, adv_taxes.account_head, adv_taxes.add_deduct_tax)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def filter_invoices_based_on_dimensions(filters, query, parent_doc):
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
fieldname = dimension.fieldname
|
||||
query = query.where(parent_doc[fieldname] == filters.fieldname)
|
||||
return query
|
||||
|
||||
|
||||
def get_opening_row(party_type, party, from_date, company):
|
||||
party_account = get_party_account(party_type, party, company)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
return (
|
||||
frappe.qb.from_(gle)
|
||||
.select(
|
||||
ConstantColumn("Opening").as_("account"),
|
||||
Sum(gle.debit).as_("debit"),
|
||||
Sum(gle.credit).as_("credit"),
|
||||
(Sum(gle.debit) - Sum(gle.credit)).as_("balance"),
|
||||
)
|
||||
.where(
|
||||
(gle.account == party_account)
|
||||
& (gle.party == party)
|
||||
& (gle.posting_date < from_date)
|
||||
& (gle.is_cancelled == 0)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
@@ -2365,6 +2365,7 @@ def validate_taxes_and_charges(tax):
|
||||
|
||||
def validate_account_head(idx, account, company, context=""):
|
||||
account_company = frappe.get_cached_value("Account", account, "company")
|
||||
is_group = frappe.get_cached_value("Account", account, "is_group")
|
||||
|
||||
if account_company != company:
|
||||
frappe.throw(
|
||||
@@ -2374,6 +2375,12 @@ def validate_account_head(idx, account, company, context=""):
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
if is_group:
|
||||
frappe.throw(
|
||||
_("Row {0}: Account {1} is a Group Account").format(idx, frappe.bold(account)),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
|
||||
def validate_cost_center(tax, doc):
|
||||
if not tax.cost_center:
|
||||
|
||||
@@ -1395,3 +1395,47 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
|
||||
)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def get_op_cost_from_sub_assemblies(bom_no, op_cost=0):
|
||||
# Get operating cost from sub-assemblies
|
||||
|
||||
bom_items = frappe.get_all(
|
||||
"BOM Item", filters={"parent": bom_no, "docstatus": 1}, fields=["bom_no"], order_by="idx asc"
|
||||
)
|
||||
|
||||
for row in bom_items:
|
||||
if not row.bom_no:
|
||||
continue
|
||||
|
||||
if cost := frappe.get_cached_value("BOM", row.bom_no, "operating_cost_per_bom_quantity"):
|
||||
op_cost += flt(cost)
|
||||
get_op_cost_from_sub_assemblies(row.bom_no, op_cost)
|
||||
|
||||
return op_cost
|
||||
|
||||
|
||||
def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None):
|
||||
if not scrap_items:
|
||||
scrap_items = {}
|
||||
|
||||
bom_items = frappe.get_all(
|
||||
"BOM Item",
|
||||
filters={"parent": bom_no, "docstatus": 1},
|
||||
fields=["bom_no", "qty"],
|
||||
order_by="idx asc",
|
||||
)
|
||||
|
||||
for row in bom_items:
|
||||
if not row.bom_no:
|
||||
continue
|
||||
|
||||
qty = flt(row.qty) * flt(qty)
|
||||
items = get_bom_items_as_dict(
|
||||
row.bom_no, company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||
)
|
||||
scrap_items.update(items)
|
||||
|
||||
get_scrap_items_from_sub_assemblies(row.bom_no, company, qty, scrap_items)
|
||||
|
||||
return scrap_items
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
"oldfieldname": "item_code",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_name",
|
||||
@@ -170,7 +171,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:35:40.856895",
|
||||
"modified": "2024-01-02 13:49:36.211586",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Explosion Item",
|
||||
|
||||
@@ -227,35 +227,39 @@ class JobCard(Document):
|
||||
|
||||
def has_overlap(self, production_capacity, time_logs):
|
||||
overlap = False
|
||||
if production_capacity == 1 and len(time_logs) > 0:
|
||||
if production_capacity == 1 and len(time_logs) >= 1:
|
||||
return True
|
||||
if not len(time_logs):
|
||||
return False
|
||||
|
||||
# Check overlap exists or not between the overlapping time logs with the current Job Card
|
||||
for row in time_logs:
|
||||
count = 1
|
||||
for next_row in time_logs:
|
||||
if row.name == next_row.name:
|
||||
continue
|
||||
|
||||
if (
|
||||
(
|
||||
get_datetime(next_row.from_time) >= get_datetime(row.from_time)
|
||||
and get_datetime(next_row.from_time) <= get_datetime(row.to_time)
|
||||
)
|
||||
or (
|
||||
get_datetime(next_row.to_time) >= get_datetime(row.from_time)
|
||||
and get_datetime(next_row.to_time) <= get_datetime(row.to_time)
|
||||
)
|
||||
or (
|
||||
get_datetime(next_row.from_time) <= get_datetime(row.from_time)
|
||||
and get_datetime(next_row.to_time) >= get_datetime(row.to_time)
|
||||
)
|
||||
):
|
||||
count += 1
|
||||
|
||||
if count > production_capacity:
|
||||
return True
|
||||
|
||||
# sorting overlapping job cards as per from_time
|
||||
time_logs = sorted(time_logs, key=lambda x: x.get("from_time"))
|
||||
# alloted_capacity has key number starting from 1. Key number will increment by 1 if non sequential job card found
|
||||
# if key number reaches/crosses to production_capacity means capacity is full and overlap error generated
|
||||
# this will store last to_time of sequential job cards
|
||||
alloted_capacity = {1: time_logs[0]["to_time"]}
|
||||
# flag for sequential Job card found
|
||||
sequential_job_card_found = False
|
||||
for i in range(1, len(time_logs)):
|
||||
# scanning for all Existing keys
|
||||
for key in alloted_capacity.keys():
|
||||
# if current Job Card from time is greater than last to_time in that key means these job card are sequential
|
||||
if alloted_capacity[key] <= time_logs[i]["from_time"]:
|
||||
# So update key's value with last to_time
|
||||
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||
# flag is true as we get sequential Job Card for that key
|
||||
sequential_job_card_found = True
|
||||
# Immediately break so that job card to time is not added with any other key except this
|
||||
break
|
||||
# if sequential job card not found above means it is overlapping so increment key number to alloted_capacity
|
||||
if not sequential_job_card_found:
|
||||
# increment key number
|
||||
key = key + 1
|
||||
# for that key last to time is assigned.
|
||||
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||
if len(alloted_capacity) >= production_capacity:
|
||||
# if number of keys greater or equal to production caoacity means full capacity is utilized and we should throw overlap error
|
||||
return True
|
||||
return overlap
|
||||
|
||||
def get_workstation_based_on_available_slot(self, existing) -> Optional[str]:
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"job_card_excess_transfer",
|
||||
"other_settings_section",
|
||||
"update_bom_costs_automatically",
|
||||
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||
"column_break_23",
|
||||
"make_serial_no_batch_from_work_order"
|
||||
],
|
||||
@@ -194,13 +195,20 @@
|
||||
"fieldname": "job_card_excess_transfer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Excess Material Transfer"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
|
||||
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-13 22:09:09.401559",
|
||||
"modified": "2023-12-28 16:37:44.874096",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
@@ -216,5 +224,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1591,6 +1591,10 @@ def make_bom(**args):
|
||||
}
|
||||
)
|
||||
|
||||
if args.operating_cost_per_bom_quantity:
|
||||
bom.fg_based_operating_cost = 1
|
||||
bom.operating_cost_per_bom_quantity = args.operating_cost_per_bom_quantity
|
||||
|
||||
for item in args.raw_materials:
|
||||
item_doc = frappe.get_doc("Item", item)
|
||||
bom.append(
|
||||
|
||||
@@ -1726,6 +1726,93 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0)
|
||||
|
||||
def test_op_cost_and_scrap_based_on_sub_assemblies(self):
|
||||
# Make Sub Assembly BOM 1
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 1
|
||||
)
|
||||
|
||||
items = {
|
||||
"Test Final FG Item": 0,
|
||||
"Test Final SF Item 1": 0,
|
||||
"Test Final SF Item 2": 0,
|
||||
"Test Final RM Item 1": 100,
|
||||
"Test Final RM Item 2": 200,
|
||||
"Test Final Scrap Item 1": 50,
|
||||
"Test Final Scrap Item 2": 60,
|
||||
}
|
||||
|
||||
for item in items:
|
||||
if not frappe.db.exists("Item", item):
|
||||
item_properties = {"is_stock_item": 1, "valuation_rate": items[item]}
|
||||
|
||||
make_item(item_code=item, properties=item_properties),
|
||||
|
||||
prepare_boms_for_sub_assembly_test()
|
||||
|
||||
wo_order = make_wo_order_test_record(
|
||||
production_item="Test Final FG Item",
|
||||
qty=10,
|
||||
use_multi_level_bom=1,
|
||||
skip_transfer=1,
|
||||
from_wip_warehouse=1,
|
||||
)
|
||||
|
||||
se_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
se_doc.save()
|
||||
|
||||
self.assertTrue(se_doc.additional_costs)
|
||||
scrap_items = []
|
||||
for item in se_doc.items:
|
||||
if item.is_scrap_item:
|
||||
scrap_items.append(item.item_code)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(scrap_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"])
|
||||
)
|
||||
for row in se_doc.additional_costs:
|
||||
self.assertEqual(row.amount, 3000)
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
|
||||
)
|
||||
|
||||
|
||||
def prepare_boms_for_sub_assembly_test():
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
|
||||
bom = make_bom(
|
||||
item="Test Final SF Item 1",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final RM Item 1"],
|
||||
operating_cost_per_bom_quantity=100,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 1", "qty": 1})
|
||||
|
||||
bom.submit()
|
||||
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 2"}):
|
||||
bom = make_bom(
|
||||
item="Test Final SF Item 2",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final RM Item 2"],
|
||||
operating_cost_per_bom_quantity=200,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 2", "qty": 1})
|
||||
|
||||
bom.submit()
|
||||
|
||||
if not frappe.db.exists("BOM", {"item": "Test Final FG Item"}):
|
||||
bom = make_bom(
|
||||
item="Test Final FG Item",
|
||||
source_warehouse="Stores - _TC",
|
||||
raw_materials=["Test Final SF Item 1", "Test Final SF Item 2"],
|
||||
)
|
||||
|
||||
|
||||
def prepare_data_for_workstation_type_check():
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
@@ -1955,7 +2042,7 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.sales_order = args.sales_order or None
|
||||
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 None
|
||||
wo_order.from_wip_warehouse = args.from_wip_warehouse or 0
|
||||
|
||||
if args.source_warehouse:
|
||||
for item in wo_order.get("required_items"):
|
||||
|
||||
@@ -68,7 +68,7 @@ erpnext.HierarchyChart = class {
|
||||
|
||||
show() {
|
||||
this.setup_actions();
|
||||
if ($(`[data-fieldname="company"]`).length) return;
|
||||
if (this.page.main.find('[data-fieldname="company"]').length) return;
|
||||
let me = this;
|
||||
|
||||
let company = this.page.add_field({
|
||||
@@ -80,7 +80,7 @@ erpnext.HierarchyChart = class {
|
||||
only_select: true,
|
||||
reqd: 1,
|
||||
change: () => {
|
||||
me.company = undefined;
|
||||
me.company = '';
|
||||
$('#hierarchy-chart-wrapper').remove();
|
||||
|
||||
if (company.get_value()) {
|
||||
@@ -219,8 +219,8 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
}).then(r => {
|
||||
if (r.message.length) {
|
||||
let expand_node = undefined;
|
||||
let node = undefined;
|
||||
let expand_node;
|
||||
let node;
|
||||
|
||||
$.each(r.message, (_i, data) => {
|
||||
if ($(`[id="${data.id}"]`).length)
|
||||
@@ -229,7 +229,7 @@ erpnext.HierarchyChart = class {
|
||||
node = new me.Node({
|
||||
id: data.id,
|
||||
parent: $('<li class="child-node"></li>').appendTo(me.$hierarchy.find('.node-children')),
|
||||
parent_id: undefined,
|
||||
parent_id: '',
|
||||
image: data.image,
|
||||
name: data.name,
|
||||
title: data.title,
|
||||
@@ -286,6 +286,10 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
load_children(node, deep=false) {
|
||||
if (!this.company) {
|
||||
frappe.throw(__('Please select a company first.'));
|
||||
}
|
||||
|
||||
if (!deep) {
|
||||
frappe.run_serially([
|
||||
() => this.get_child_nodes(node.id),
|
||||
@@ -367,8 +371,8 @@ erpnext.HierarchyChart = class {
|
||||
}
|
||||
|
||||
render_children_of_all_nodes(data_list) {
|
||||
let entry = undefined;
|
||||
let node = undefined;
|
||||
let entry;
|
||||
let node;
|
||||
|
||||
while (data_list.length) {
|
||||
// to avoid overlapping connectors
|
||||
@@ -423,7 +427,7 @@ erpnext.HierarchyChart = class {
|
||||
title: data.title,
|
||||
expandable: data.expandable,
|
||||
connections: data.connections,
|
||||
children: undefined
|
||||
children: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -519,7 +523,7 @@ erpnext.HierarchyChart = class {
|
||||
collapse_previous_level_nodes(node) {
|
||||
let node_parent = $(`[id="${node.parent_id}"]`);
|
||||
let previous_level_nodes = node_parent.parent().parent().children('li');
|
||||
let node_card = undefined;
|
||||
let node_card;
|
||||
|
||||
previous_level_nodes.each(function() {
|
||||
node_card = $(this).find('.node-card');
|
||||
@@ -582,12 +586,12 @@ erpnext.HierarchyChart = class {
|
||||
level.nextAll('li').remove();
|
||||
|
||||
let nodes = level.find('.node-card');
|
||||
let node_object = undefined;
|
||||
let node_object;
|
||||
|
||||
$.each(nodes, (_i, element) => {
|
||||
node_object = this.nodes[element.id];
|
||||
node_object.expanded = 0;
|
||||
node_object.$children = undefined;
|
||||
node_object.$children = null;
|
||||
});
|
||||
|
||||
nodes.removeClass('collapsed active-path');
|
||||
|
||||
@@ -59,8 +59,8 @@ erpnext.HierarchyChartMobile = class {
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this.page.main.find('[data-fieldname="company"]').length) return;
|
||||
let me = this;
|
||||
if ($(`[data-fieldname="company"]`).length) return;
|
||||
|
||||
let company = this.page.add_field({
|
||||
fieldtype: 'Link',
|
||||
@@ -71,7 +71,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
only_select: true,
|
||||
reqd: 1,
|
||||
change: () => {
|
||||
me.company = undefined;
|
||||
me.company = '';
|
||||
|
||||
if (company.get_value() && me.company != company.get_value()) {
|
||||
me.company = company.get_value();
|
||||
@@ -154,7 +154,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
return new me.Node({
|
||||
id: data.id,
|
||||
parent: root_level,
|
||||
parent_id: undefined,
|
||||
parent_id: '',
|
||||
image: data.image,
|
||||
name: data.name,
|
||||
title: data.title,
|
||||
@@ -174,7 +174,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
if (this.$sibling_group) {
|
||||
const sibling_parent = this.$sibling_group.find('.node-group').attr('data-parent');
|
||||
if (node.parent_id !== undefined && node.parent_id != sibling_parent)
|
||||
if (node.parent_id != '' && node.parent_id != sibling_parent)
|
||||
this.$sibling_group.empty();
|
||||
}
|
||||
|
||||
@@ -225,6 +225,10 @@ erpnext.HierarchyChartMobile = class {
|
||||
}
|
||||
|
||||
load_children(node) {
|
||||
if (!this.company) {
|
||||
frappe.throw(__('Please select a company first'));
|
||||
}
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.get_child_nodes(node.id),
|
||||
(child_nodes) => this.render_child_nodes(node, child_nodes)
|
||||
@@ -281,7 +285,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
title: data.title,
|
||||
expandable: data.expandable,
|
||||
connections: data.connections,
|
||||
children: undefined
|
||||
children: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,7 +295,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
let connector = undefined;
|
||||
let connector = null;
|
||||
|
||||
if ($(`[id="${parent_id}"]`).hasClass('active')) {
|
||||
connector = this.get_connector_for_active_node(parent_node, child_node);
|
||||
@@ -377,7 +381,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
let node_element = $(`[id="${node.id}"]`);
|
||||
|
||||
node_element.click(function() {
|
||||
let el = undefined;
|
||||
let el = null;
|
||||
|
||||
if (node.is_root) {
|
||||
el = $(this).detach();
|
||||
@@ -411,7 +415,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
$('.node-group').on('click', function() {
|
||||
let parent = $(this).attr('data-parent');
|
||||
if (parent === 'undefined') {
|
||||
if (parent == '') {
|
||||
me.setup_hierarchy();
|
||||
me.render_root_nodes();
|
||||
} else {
|
||||
@@ -427,7 +431,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
let node_object = this.nodes[node.id];
|
||||
node_object.expanded = 0;
|
||||
node_object.$children = undefined;
|
||||
node_object.$children = null;
|
||||
this.nodes[node.id] = node_object;
|
||||
}
|
||||
|
||||
@@ -484,7 +488,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
|
||||
node.removeClass('active-child active-path');
|
||||
node_object.expanded = 0;
|
||||
node_object.$children = undefined;
|
||||
node_object.$children = null;
|
||||
this.nodes[node.id] = node_object;
|
||||
|
||||
// show parent's siblings and expand parent node
|
||||
@@ -523,7 +527,7 @@ erpnext.HierarchyChartMobile = class {
|
||||
current_node.removeClass('active-child active-path');
|
||||
|
||||
node_object.expanded = 0;
|
||||
node_object.$children = undefined;
|
||||
node_object.$children = null;
|
||||
|
||||
level.empty().append(current_node);
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
var doc = frappe.model.sync(r.message);
|
||||
frappe.model.sync(r.message);
|
||||
cur_frm.dirty();
|
||||
cur_frm.refresh();
|
||||
}
|
||||
@@ -764,6 +764,11 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
target: opts.target,
|
||||
date_field: opts.date_field || undefined,
|
||||
setters: opts.setters,
|
||||
data_fields: [{
|
||||
fieldname: 'merge_taxes',
|
||||
fieldtype: 'Check',
|
||||
label: __('Merge taxes from multiple documents'),
|
||||
}],
|
||||
get_query: opts.get_query,
|
||||
add_filters_group: 1,
|
||||
allow_child_item_selection: opts.allow_child_item_selection,
|
||||
@@ -777,10 +782,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
return;
|
||||
}
|
||||
opts.source_name = values;
|
||||
if (opts.allow_child_item_selection) {
|
||||
// args contains filtered child docnames
|
||||
opts.args = args;
|
||||
}
|
||||
opts.args = args;
|
||||
d.dialog.hide();
|
||||
_map();
|
||||
},
|
||||
|
||||
@@ -447,7 +447,6 @@
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "credit_limits",
|
||||
"fieldtype": "Table",
|
||||
"label": "Credit Limit",
|
||||
@@ -568,7 +567,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-19 16:56:27.327035",
|
||||
"modified": "2023-12-28 13:15:36.298369",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -690,8 +690,12 @@ def make_contact(args, is_primary_contact=1):
|
||||
"is_primary_contact": is_primary_contact,
|
||||
"links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}],
|
||||
}
|
||||
if args.customer_type == "Individual":
|
||||
first, middle, last = parse_full_name(args.get("customer_name"))
|
||||
|
||||
party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type
|
||||
party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
|
||||
|
||||
if party_type == "Individual":
|
||||
first, middle, last = parse_full_name(args.get(party_name_key))
|
||||
values.update(
|
||||
{
|
||||
"first_name": first,
|
||||
@@ -703,9 +707,10 @@ def make_contact(args, is_primary_contact=1):
|
||||
values.update(
|
||||
{
|
||||
"first_name": args.get("customer_name"),
|
||||
"company_name": args.get("customer_name"),
|
||||
"company_name": args.get(party_name_key),
|
||||
}
|
||||
)
|
||||
|
||||
contact = frappe.get_doc(values)
|
||||
|
||||
if args.get("email_id"):
|
||||
@@ -734,10 +739,12 @@ def make_address(args, is_primary_address=1, is_shipping_address=1):
|
||||
title=_("Missing Values Required"),
|
||||
)
|
||||
|
||||
party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name"
|
||||
|
||||
address = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Address",
|
||||
"address_title": args.get("customer_name"),
|
||||
"address_title": args.get(party_name_key),
|
||||
"address_line1": args.get("address_line1"),
|
||||
"address_line2": args.get("address_line2"),
|
||||
"city": args.get("city"),
|
||||
|
||||
@@ -128,6 +128,4 @@ def prepare_closing_stock_balance(name):
|
||||
doc.db_set("status", "Completed")
|
||||
except Exception as e:
|
||||
doc.db_set("status", "Failed")
|
||||
traceback = frappe.get_traceback()
|
||||
|
||||
frappe.log_error("Closing Stock Balance Failed", traceback, doc.doctype, doc.name)
|
||||
doc.log_error(title="Closing Stock Balance Failed")
|
||||
|
||||
@@ -970,8 +970,39 @@ def get_item_wise_returned_qty(pr_doc):
|
||||
)
|
||||
|
||||
|
||||
def merge_taxes(source_taxes, target_doc):
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||
update_item_wise_tax_detail,
|
||||
)
|
||||
|
||||
existing_taxes = target_doc.get("taxes") or []
|
||||
idx = 1
|
||||
for tax in source_taxes:
|
||||
found = False
|
||||
for t in existing_taxes:
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
update_item_wise_tax_detail(t, tax)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
tax.charge_type = "Actual"
|
||||
tax.idx = idx
|
||||
idx += 1
|
||||
tax.included_in_print_rate = 0
|
||||
tax.dont_recompute_tax = 1
|
||||
tax.row_id = ""
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||
existing_taxes.append(tax)
|
||||
|
||||
target_doc.set("taxes", existing_taxes)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
from erpnext.accounts.party import get_payment_terms_template
|
||||
|
||||
doc = frappe.get_doc("Purchase Receipt", source_name)
|
||||
@@ -988,6 +1019,10 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
)
|
||||
doc.run_method("onload")
|
||||
doc.run_method("set_missing_values")
|
||||
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source.get("taxes") or [], doc)
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
doc.set_payment_schedule()
|
||||
|
||||
@@ -1051,7 +1086,11 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
if not doc.get("is_return")
|
||||
else get_pending_qty(d)[0] > 0,
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
set_missing_values,
|
||||
|
||||
@@ -248,7 +248,7 @@ def repost(doc):
|
||||
raise
|
||||
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
doc.log_error("Unable to repost item valuation")
|
||||
|
||||
message = frappe.message_log.pop() if frappe.message_log else ""
|
||||
|
||||
@@ -24,7 +24,12 @@ from frappe.utils import (
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
||||
from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, validate_bom_no
|
||||
from erpnext.manufacturing.doctype.bom.bom import (
|
||||
add_additional_cost,
|
||||
get_op_cost_from_sub_assemblies,
|
||||
get_scrap_items_from_sub_assemblies,
|
||||
validate_bom_no,
|
||||
)
|
||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_no, get_batch_qty, set_batch_nos
|
||||
@@ -1767,11 +1772,22 @@ class StockEntry(StockController):
|
||||
def get_bom_scrap_material(self, qty):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1)
|
||||
or {}
|
||||
)
|
||||
if (
|
||||
frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||
)
|
||||
and self.work_order
|
||||
and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom")
|
||||
):
|
||||
item_dict = get_scrap_items_from_sub_assemblies(self.bom_no, self.company, qty)
|
||||
else:
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(
|
||||
self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
for item in item_dict.values():
|
||||
item.from_warehouse = ""
|
||||
@@ -2527,6 +2543,15 @@ def get_work_order_details(work_order, company):
|
||||
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
operating_cost_per_unit = 0
|
||||
if work_order:
|
||||
if (
|
||||
bom_no
|
||||
and frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||
)
|
||||
and frappe.get_cached_value("Work Order", work_order, "use_multi_level_bom")
|
||||
):
|
||||
return get_op_cost_from_sub_assemblies(bom_no)
|
||||
|
||||
if not bom_no:
|
||||
bom_no = work_order.bom_no
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ def create_material_request(material_requests):
|
||||
exceptions_list.extend(frappe.local.message_log)
|
||||
frappe.local.message_log = []
|
||||
else:
|
||||
exceptions_list.append(frappe.get_traceback())
|
||||
exceptions_list.append(frappe.get_traceback(with_context=True))
|
||||
|
||||
mr.log_error("Unable to create material request")
|
||||
|
||||
|
||||
@@ -195,7 +195,6 @@ class TestFIFOValuation(unittest.TestCase):
|
||||
total_value -= sum(q * r for q, r in consumed)
|
||||
self.assertTotalQty(total_qty)
|
||||
self.assertTotalValue(total_value)
|
||||
self.assertGreaterEqual(total_value, 0)
|
||||
|
||||
|
||||
class TestLIFOValuation(unittest.TestCase):
|
||||
|
||||
@@ -151,7 +151,7 @@ Advance amount cannot be greater than {0} {1},Montant de l'avance ne peut être
|
||||
Advertising,Publicité,
|
||||
Aerospace,Aérospatial,
|
||||
Against,Contre,
|
||||
Against Account,Pour le Compte,
|
||||
Against Account,Contrepartie,
|
||||
Against Journal Entry {0} does not have any unmatched {1} entry,L'Écriture de Journal {0} n'a pas d'entrée non associée {1},
|
||||
Against Journal Entry {0} is already adjusted against some other voucher,L'Écriture de Journal {0} est déjà ajustée par un autre bon,
|
||||
Against Supplier Invoice {0} dated {1},Pour la Facture Fournisseur {0} datée {1},
|
||||
@@ -3275,7 +3275,7 @@ Import Successful,Importation réussie,
|
||||
Please save first,S'il vous plaît enregistrer en premier,
|
||||
Price not found for item {0} in price list {1},Prix non trouvé pour l'article {0} dans la liste de prix {1},
|
||||
Warehouse Type,Type d'entrepôt,
|
||||
'Date' is required,'Date' est requis,
|
||||
'Date' is required,La 'date' est obligatoire,
|
||||
Budgets,Budgets,
|
||||
Bundle Qty,Quantité de paquet,
|
||||
Company GSTIN,GSTIN de la Société,
|
||||
@@ -3329,7 +3329,7 @@ Account: <b>{0}</b> is capital Work in progress and can not be updated by Journa
|
||||
Account: {0} is not permitted under Payment Entry,Compte: {0} n'est pas autorisé sous Saisie du paiement.,
|
||||
Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte "Bilan" {1}.,
|
||||
Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.,La dimension de comptabilité <b>{0}</b> est requise pour le compte 'Bénéfices et pertes' {1}.,
|
||||
Accounting Masters,Maîtres Comptables,
|
||||
Accounting Masters,Données de base,
|
||||
Accounting Period overlaps with {0},La période comptable chevauche avec {0},
|
||||
Activity,Activité,
|
||||
Add / Manage Email Accounts.,Ajouter / Gérer les Comptes de Messagerie.,
|
||||
@@ -4214,7 +4214,7 @@ Mandatory For Profit and Loss Account,Compte de résultat obligatoire,
|
||||
Accounting Period,Période comptable,
|
||||
Period Name,Nom de période,
|
||||
Closed Documents,Documents fermés,
|
||||
Accounts Settings,Paramètres des Comptes,
|
||||
Accounts Settings,Paramètres de comptabilité,
|
||||
Settings for Accounts,Paramètres des Comptes,
|
||||
Make Accounting Entry For Every Stock Movement,Faites une Écriture Comptable Pour Chaque Mouvement du Stock,
|
||||
Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Les utilisateurs ayant ce rôle sont autorisés à définir les comptes gelés et à créer / modifier des écritures comptables sur des comptes gelés,
|
||||
@@ -4231,7 +4231,7 @@ Show Payment Schedule in Print,Afficher le calendrier de paiement dans Imprimer,
|
||||
Currency Exchange Settings,Paramètres d'échange de devises,
|
||||
Allow Stale Exchange Rates,Autoriser les Taux de Change Existants,
|
||||
Stale Days,Journées Passées,
|
||||
Report Settings,Paramètres de rapport,
|
||||
Report Settings,Paramètres des rapports,
|
||||
Use Custom Cash Flow Format,Utiliser le format de flux de trésorerie personnalisé,
|
||||
Allowed To Transact With,Autorisé à faire affaire avec,
|
||||
SWIFT number,Numéro rapide,
|
||||
@@ -4837,7 +4837,7 @@ Redemption Account,Compte pour l'échange,
|
||||
Redemption Cost Center,Centre de coûts pour l'échange,
|
||||
In Words will be visible once you save the Sales Invoice.,En Toutes Lettres. Sera visible une fois que vous enregistrerez la Facture.,
|
||||
Allocate Advances Automatically (FIFO),Allouer automatiquement les avances (FIFO),
|
||||
Get Advances Received,Obtenir Acomptes Reçus,
|
||||
Get Advances Received,Obtenir les paiements des avances,
|
||||
Base Change Amount (Company Currency),Montant de Base à Rendre (Devise de la Société),
|
||||
Write Off Outstanding Amount,Encours de Reprise,
|
||||
Terms and Conditions Details,Détails des Termes et Conditions,
|
||||
@@ -4845,7 +4845,7 @@ Is Internal Customer,Est un client interne,
|
||||
Is Discounted,Est réduit,
|
||||
Unpaid and Discounted,Non payé et à prix réduit,
|
||||
Overdue and Discounted,En retard et à prix réduit,
|
||||
Accounting Details,Détails Comptabilité,
|
||||
Accounting Details,Détails Comptable,
|
||||
Debit To,Débit Pour,
|
||||
Is Opening Entry,Est Écriture Ouverte,
|
||||
C-Form Applicable,Formulaire-C Applicable,
|
||||
@@ -8742,7 +8742,7 @@ Import Chart of Accounts from CSV / Excel files,Importer un plan comptable à pa
|
||||
Completed Qty cannot be greater than 'Qty to Manufacture',La quantité terminée ne peut pas être supérieure à la `` quantité à fabriquer '',
|
||||
"Row {0}: For Supplier {1}, Email Address is Required to send an email","Ligne {0}: pour le fournisseur {1}, l'adresse e-mail est obligatoire pour envoyer un e-mail",
|
||||
"If enabled, the system will post accounting entries for inventory automatically","Si activé, le système enregistrera automatiquement les écritures comptables pour l'inventaire",
|
||||
Accounts Frozen Till Date,Comptes gelés jusqu'à la date,
|
||||
Accounts Frozen Till Date,Comptes gelés jusqu'au,
|
||||
Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below,Les écritures comptables sont gelées jusqu'à cette date. Personne ne peut créer ou modifier des entrées sauf les utilisateurs avec le rôle spécifié ci-dessous,
|
||||
Role Allowed to Set Frozen Accounts and Edit Frozen Entries,Rôle autorisé à définir des comptes gelés et à modifier les entrées gelées,
|
||||
Address used to determine Tax Category in transactions,Adresse utilisée pour déterminer la catégorie de taxe dans les transactions,
|
||||
@@ -9053,7 +9053,7 @@ Unit of Measure (UOM),Unité de mesure (UdM),
|
||||
Unit Of Measure (UOM),Unité de mesure (UdM),
|
||||
CRM Settings,Paramètres CRM,
|
||||
Do Not Explode,Ne pas décomposer,
|
||||
Quick Access, Accés rapides,
|
||||
Quick Access, Accès rapides,
|
||||
{} Available,{} Disponible.s,
|
||||
{} Pending,{} En attente.s,
|
||||
{} To Bill,{} à facturer,
|
||||
@@ -9109,4 +9109,120 @@ Receivable Accounts,Compte de débit
|
||||
Mention if a non-standard receivable account,Veuillez mentionner s'il s'agit d'un compte débiteur non standard
|
||||
Allow Purchase,Autoriser à l'achat
|
||||
Inventory Settings,Paramétrage de l'inventaire
|
||||
Component Type,Type de composant,
|
||||
Add Comment,Ajouter un Commentaire,
|
||||
More...,Plus...,
|
||||
Notes,Remarques,
|
||||
Payment Gateway,Passerelle de Paiement,
|
||||
Payment Gateway Name,Nom de la passerelle de paiement,
|
||||
Payments,Paiements,
|
||||
Plan Name,Nom du Plan,
|
||||
Portal,Portail,
|
||||
Scan Barcode,Scan Code Barre,
|
||||
Some information is missing,Certaines informations sont manquantes,
|
||||
Successful,Réussi,
|
||||
Tools,Outils,
|
||||
Use Sandbox,Utiliser Sandbox,
|
||||
Busy,Occupé,
|
||||
Completed By,Effectué par,
|
||||
Payment Failed,Le Paiement a Échoué,
|
||||
Column {0},Colonne {0},
|
||||
Field Mapping,Cartographie des champs,
|
||||
Not Specified,Non précisé,
|
||||
Update Type,Type de mise à jour,
|
||||
Dr,Dr,
|
||||
End Time,Heure de Fin,
|
||||
Fetching...,Aller chercher...,
|
||||
"It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.","Il semble qu'il y a un problème avec la configuration de Stripe sur le serveur. En cas d'erreur, le montant est remboursé sur votre compte.",
|
||||
Looks like someone sent you to an incomplete URL. Please ask them to look into it.,On dirait que quelqu'un vous a envoyé vers URL incomplète. Veuillez leur demander d’analyser l’erreur.,
|
||||
Master,Maître,
|
||||
Pay,Payer,
|
||||
You can also copy-paste this link in your browser,Vous pouvez également copier-coller ce lien dans votre navigateur,
|
||||
Verified By,Vérifié Par,
|
||||
Invalid naming series (. missing) for {0},Masque de numérotation non valide (. Manquante) pour {0},
|
||||
Phone Number,Numéro de téléphone,
|
||||
Account SID,Compte SID,
|
||||
Global Defaults,Valeurs par Défaut Globales,
|
||||
Is Mandatory,Est obligatoire,
|
||||
WhatsApp,WhatsApp,
|
||||
Make a call,Passer un coup de téléphone,
|
||||
No of Employees,Nb de salarié(e)s
|
||||
No. of Employees,Nb de salarié(e)s
|
||||
Annual Revenue,CA annuel
|
||||
Qualified By,Qualifié par
|
||||
Qualified on,Qualifié le
|
||||
Open Tasks,Tâche à faire ouverte
|
||||
No open task,Pas de Tâche à faire ouverte
|
||||
Open Events,Evénements ouvert
|
||||
No open event,Pas Evénements ouvert
|
||||
New Task,Nv. Tâche à faire
|
||||
No Notes,Pas de note
|
||||
New Note,Nouvelle Note
|
||||
Prospect Owner,Resp. du Prospect
|
||||
Deal Owner,Resp. de l'opportunité
|
||||
Stage,Etape
|
||||
Probability,Probabilité
|
||||
Closing,Clôture
|
||||
Allow Sales,Autoriser à la vente
|
||||
Approve,Approuver,
|
||||
Reject,Rejeter,
|
||||
'Account' in the Accounting section of Customer {0},'Compte' dans la section comptabilité du client {0},
|
||||
Accounting Entry Number,Numéro d'écriture comptable,
|
||||
Accounting Ledger,Grand livre,
|
||||
Accounts Closing,Clôture,
|
||||
Accounts Frozen Upto,Comptes gelés jusqu'au,
|
||||
Accounts Manager,Responsable comptable,
|
||||
Active Customers,Clients actifs,
|
||||
Against Account,Contrepartie,
|
||||
All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.,Tous les commentaires et les courriels seront copiés d'un document à un autre document nouvellement créé (Lead -> Opportunité -> Devis) dans l'ensemble des documents CRM.,
|
||||
Allow multi-currency invoices against single party account ,Autoriser les factures multi-devises en contrepartie d'un seul compte de tiers,
|
||||
Allow Sales Order Creation For Expired Quotation,Autoriser la création de commandes client pour les devis expirés,
|
||||
Allow Continuous Material Consumption,Autoriser la consommation continue de matériel,
|
||||
Allow Lead Duplication based on Emails,Autoriser la duplication des pistes sur la base des courriels,
|
||||
Asset Settings,Paramètres des actifs,
|
||||
Auto close Opportunity Replied after the no. of days mentioned above,Fermeture automatique de l'opportunité de réponse après le nombre de jours mentionné ci-dessus.,
|
||||
Auto Creation of Contact,Création automatique d'un contact,
|
||||
Automatically Fetch Payment Terms from Order,Récupération automatique des conditions de paiement de la commande,
|
||||
Bill for Rejected Quantity in Purchase Invoice,Facturation de la quantité rejetée dans la facture d'achat,
|
||||
Credit Limit Settings,Paramètres de la limite de crédit,
|
||||
Create Ledger Entries for Change Amount,Créer des écritures de grand livre pour modifier le montant,
|
||||
Customer Defaults,Valeurs par défaut des clients,
|
||||
Calculate Product Bundle Price based on Child Items' Rates,Calculer le prix des ensembles de produits en fonction des tarifs des articles enfants,
|
||||
Configure the action to stop the transaction or just warn if the same rate is not maintained.,Configurez une action pour stopper la transaction ou alertez simplement su le prix unitaie n'est pas maintenu.,
|
||||
Close Replied Opportunity After Days,Fermer l'opportunité répliquée après des jours,
|
||||
Carry Forward Communication and Comments,Reprendre les communications et commentaires,
|
||||
Default Down Payment Payable Account,Compte d'acompte fournisseur par défaut,
|
||||
Default Down Payment Receivable Account,Compte d'acompte client par défaut,
|
||||
Disable Last Purchase Rate,Désactiver le dernier prix d'achat,
|
||||
'Default {0} Account' in Company {1},'Compte {0} par défaut' dans la société {1},
|
||||
Enable Custom Cash Flow Format,Activation du format de flux de trésorerie personnalisé,
|
||||
Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field,Garanti que chaque facture d'achat est associée à un numéro de facture fournisseur unique,
|
||||
Enable Common Party Accounting,Activer la comptabilité des tiers communs,
|
||||
Enabling this will allow creation of multi-currency invoices against single party account in company currency,L'activation de cette option va permettre la création de factures multi-devises en contrepartie d'un seul compte de tiers en devise de la société,
|
||||
Enable Discount Accounting for Selling,Activation de la comptabilité d'escompte pour la vente,
|
||||
'Expected Start Date' can not be greater than 'Expected End Date','Date de Début Prévue' ne peut pas être postérieure à 'Date de Fin Prévue',
|
||||
Get Advances Received, Obtenir les paiements des avances,
|
||||
"If enabled, ledger entries will be posted for change amount in POS transactions","Si cette option est activée, des écritures de grand livre seront enregistrées pour le montant de la modification dans les transactions POS.",
|
||||
"If enabled, additional ledger entries will be made for discounts in a separate Discount Account","Si cette option est activée, des écritures de grand livre supplémentaires seront effectuées pour les remises dans un compte de remise séparé.",
|
||||
Item Price Settings,Paramètres du prix de l'article,
|
||||
Invoice and Billing,Facturation,
|
||||
Invoice Cancellation,Annulation de facture,
|
||||
Invoicing Features,Caractéristiques de la facturation,
|
||||
Journals,Journaux,
|
||||
"Learn about <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Common Party</a>","En savoir plus <a href=""https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier."">Tiers communs</a>",
|
||||
Naming Series and Price Defaults,Nom de série et Tarifs,
|
||||
Over Order Allowance (%),Tolérance de sur-commande (%),
|
||||
Payment Terms from orders will be fetched into the invoices as is,Les termes de paiement des commandes seront récupérées dans les factures telles quelles,
|
||||
Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.,"Percentage de commande autorisée en plus de la quantité prévue dans la commande ouverte. Par exemple: Si vous avez une commande avec une quantité de 100 unités et une tolérance de 10%, alors vous pourrez commander jusqu'à 110 unités.",
|
||||
Period Closing Settings,Paramètres de clôture de la période,
|
||||
Quick Access,Accès rapide,
|
||||
Report Setting,Réglage des rapports,
|
||||
Record all transactions against an accounting journal,Comptabiliser toutes les transactions dans un journal comptable,
|
||||
Rows with Same Account heads will be merged on Ledger,Les lignes associées aux mêmes comptes comptables seront fusionnées dans le grand livre,
|
||||
Role Allowed to Over Bill ,Rôle autorisé à sur-facturer,
|
||||
Role allowed to bypass Credit Limit,Rôle autorisé à contourner la limite de crédit,
|
||||
Role Allowed to Override Stop Action,Rôle autorisé à outrepasser l'action Stop,
|
||||
Show Balances in Chart Of Accounts,Afficher les soldes dans le plan comptable,
|
||||
Sales Update Frequency in Company and Project,Fréquence de mise à jour des ventes dans la société et le projet,
|
||||
Transaction Settings,Paramètres des transactions,
|
||||
Subcontracting Settings,Paramètres de sous-traitance,
|
||||
Users with this role are allowed to over bill above the allowance percentage,Les utilisateurs avec ce rôle sont autorisés à sur-facturer au delà du pourcentage de tolérance,
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -62,7 +62,7 @@ def retry_failed_transactions(failed_docs: list | None):
|
||||
task(log.transaction_name, log.from_doctype, log.to_doctype)
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_state")
|
||||
update_log(log.name, "Failed", 1, str(frappe.get_traceback()))
|
||||
update_log(log.name, "Failed", 1, str(frappe.get_traceback(with_context=True)))
|
||||
else:
|
||||
update_log(log.name, "Success", 1)
|
||||
|
||||
@@ -86,7 +86,7 @@ def job(deserialized_data, from_doctype, to_doctype):
|
||||
fail_count += 1
|
||||
create_log(
|
||||
doc_name,
|
||||
str(frappe.get_traceback()),
|
||||
str(frappe.get_traceback(with_context=True)),
|
||||
from_doctype,
|
||||
to_doctype,
|
||||
status="Failed",
|
||||
|
||||
Reference in New Issue
Block a user