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

chore: release v14
This commit is contained in:
Deepesh Garg
2024-01-03 11:29:59 +05:30
committed by GitHub
38 changed files with 1537 additions and 858 deletions

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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"]);
});

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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"]);
});
});

View File

@@ -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
}
]
}

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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
}
]
}

View File

@@ -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:

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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",

View File

@@ -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]:

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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"):

View File

@@ -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');

View File

@@ -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);
}

View File

@@ -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();
},

View File

@@ -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",

View File

@@ -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"),

View File

@@ -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")

View File

@@ -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,

View File

@@ -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 ""

View File

@@ -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

View File

@@ -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")

View File

@@ -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):

View File

@@ -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 &quot;Bilan&quot; {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 danalyser lerreur.,
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.

View File

@@ -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",