Merge branch 'develop' into early-payment-loss

This commit is contained in:
Marica 2023-03-28 15:01:28 +05:30 committed by GitHub
commit dae40dfbb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 1244 additions and 2291 deletions

View File

@ -0,0 +1,8 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Account Closing Balance", {
// refresh(frm) {
// },
// });

View File

@ -0,0 +1,164 @@
{
"actions": [],
"creation": "2023-02-21 15:20:59.586811",
"default_view": "List",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"closing_date",
"account",
"cost_center",
"debit",
"credit",
"account_currency",
"debit_in_account_currency",
"credit_in_account_currency",
"project",
"company",
"finance_book",
"period_closing_voucher",
"is_period_closing_voucher_entry"
],
"fields": [
{
"fieldname": "closing_date",
"fieldtype": "Date",
"in_filter": 1,
"in_list_view": 1,
"label": "Closing Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"search_index": 1
},
{
"fieldname": "account",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account",
"oldfieldname": "account",
"oldfieldtype": "Link",
"options": "Account",
"search_index": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Cost Center",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "debit",
"fieldtype": "Currency",
"label": "Debit Amount",
"oldfieldname": "debit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "credit",
"fieldtype": "Currency",
"label": "Credit Amount",
"oldfieldname": "credit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency"
},
{
"fieldname": "debit_in_account_currency",
"fieldtype": "Currency",
"label": "Debit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "credit_in_account_currency",
"fieldtype": "Currency",
"label": "Credit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"search_index": 1
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "period_closing_voucher",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Period Closing Voucher",
"options": "Period Closing Voucher",
"search_index": 1
},
{
"default": "0",
"fieldname": "is_period_closing_voucher_entry",
"fieldtype": "Check",
"label": "Is Period Closing Voucher Entry"
}
],
"icon": "fa fa-list",
"in_create": 1,
"links": [],
"modified": "2023-03-06 08:56:36.393237",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Closing Balance",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User"
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager"
},
{
"export": 1,
"read": 1,
"report": 1,
"role": "Auditor"
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@ -0,0 +1,127 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.utils import cint, cstr
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
class AccountClosingBalance(Document):
pass
def make_closing_entries(closing_entries, voucher_name):
accounting_dimensions = get_accounting_dimensions()
company = closing_entries[0].get("company")
closing_date = closing_entries[0].get("closing_date")
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
)
combined_entries = closing_entries + previous_closing_entries
merged_entries = aggregate_with_last_account_closing_balance(
combined_entries, accounting_dimensions
)
for key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance")
cle.update(value)
cle.update(value["dimensions"])
cle.update(
{
"period_closing_voucher": voucher_name,
"closing_date": closing_date,
}
)
cle.submit()
def aggregate_with_last_account_closing_balance(entries, accounting_dimensions):
merged_entries = {}
for entry in entries:
key, key_values = generate_key(entry, accounting_dimensions)
merged_entries.setdefault(
key,
{
"debit": 0,
"credit": 0,
"debit_in_account_currency": 0,
"credit_in_account_currency": 0,
},
)
merged_entries[key]["dimensions"] = key_values
merged_entries[key]["debit"] += entry.get("debit")
merged_entries[key]["credit"] += entry.get("credit")
merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
return merged_entries
def generate_key(entry, accounting_dimensions):
key = [
cstr(entry.get("account")),
cstr(entry.get("account_currency")),
cstr(entry.get("cost_center")),
cstr(entry.get("project")),
cstr(entry.get("finance_book")),
cint(entry.get("is_period_closing_voucher_entry")),
]
key_values = {
"company": cstr(entry.get("company")),
"account": cstr(entry.get("account")),
"account_currency": cstr(entry.get("account_currency")),
"cost_center": cstr(entry.get("cost_center")),
"project": cstr(entry.get("project")),
"finance_book": cstr(entry.get("finance_book")),
"is_period_closing_voucher_entry": cint(entry.get("is_period_closing_voucher_entry")),
}
for dimension in accounting_dimensions:
key.append(cstr(entry.get(dimension)))
key_values[dimension] = cstr(entry.get(dimension))
return tuple(key), key_values
def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = []
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
fields=["name"],
order_by="posting_date desc",
limit=1,
)
if last_period_closing_voucher:
account_closing_balance = frappe.qb.DocType("Account Closing Balance")
query = frappe.qb.from_(account_closing_balance).select(
account_closing_balance.company,
account_closing_balance.account,
account_closing_balance.account_currency,
account_closing_balance.debit,
account_closing_balance.credit,
account_closing_balance.debit_in_account_currency,
account_closing_balance.credit_in_account_currency,
account_closing_balance.cost_center,
account_closing_balance.project,
account_closing_balance.finance_book,
account_closing_balance.is_period_closing_voucher_entry,
)
for dimension in accounting_dimensions:
query = query.select(account_closing_balance[dimension])
query = query.where(
account_closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
)
entries = query.run(as_dict=1)
return entries

View File

@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestAccountClosingBalance(FrappeTestCase):
pass

View File

@ -18,6 +18,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
// Set default filter dates
today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account');
},
@ -32,6 +36,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
refresh: function (frm) {
frm.disable_save();
frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
);
@ -72,10 +77,12 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
})
});
},
after_save: function (frm) {
frm.trigger("make_reconciliation_tool");
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
frm.trigger("make_reconciliation_tool");
});
frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
},
bank_account: function (frm) {
@ -89,7 +96,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
r.account,
"account_currency",
(r) => {
frm.currency = r.account_currency;
frm.doc.account_currency = r.account_currency;
frm.trigger("render_chart");
}
);
@ -162,9 +169,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
"reconciliation_tool_cards"
).$wrapper,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance,
currency: frm.currency,
currency: frm.doc.account_currency,
}
);
},

View File

@ -14,6 +14,7 @@
"to_reference_date",
"filter_by_reference_date",
"column_break_2",
"account_currency",
"account_opening_balance",
"bank_statement_closing_balance",
"section_break_1",
@ -59,7 +60,7 @@
"fieldname": "account_opening_balance",
"fieldtype": "Currency",
"label": "Account Opening Balance",
"options": "Currency",
"options": "account_currency",
"read_only": 1
},
{
@ -67,7 +68,7 @@
"fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency",
"label": "Closing Balance",
"options": "Currency"
"options": "account_currency"
},
{
"fieldname": "section_break_1",
@ -104,13 +105,20 @@
"fieldname": "filter_by_reference_date",
"fieldtype": "Check",
"label": "Filter by Reference Date"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Account Currency",
"options": "Currency"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-01-13 13:00:02.022919",
"modified": "2023-03-07 11:02:24.535714",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",

View File

@ -4,12 +4,13 @@
import frappe
from frappe import _
from frappe.utils import flt
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
@ -20,9 +21,17 @@ class PeriodClosingVoucher(AccountsController):
def on_submit(self):
self.db_set("gle_processing_status", "In Progress")
self.make_gl_entries()
get_opening_entries = False
if not frappe.db.exists(
"Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}
):
get_opening_entries = True
self.make_gl_entries(get_opening_entries=get_opening_entries)
def on_cancel(self):
self.validate_future_closing_vouchers()
self.db_set("gle_processing_status", "In Progress")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
gle_count = frappe.db.count(
@ -42,6 +51,25 @@ class PeriodClosingVoucher(AccountsController):
else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
self.delete_closing_entries()
def validate_future_closing_vouchers(self):
if frappe.db.exists(
"Period Closing Voucher",
{"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company},
):
frappe.throw(
_(
"You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first"
)
)
def delete_closing_entries(self):
closing_balance = frappe.qb.DocType("Account Closing Balance")
frappe.qb.from_(closing_balance).delete().where(
closing_balance.period_closing_voucher == self.name
).run()
def validate_account_head(self):
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
@ -56,8 +84,6 @@ class PeriodClosingVoucher(AccountsController):
frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
def validate_posting_date(self):
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
validate_fiscal_year(
self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
)
@ -66,6 +92,8 @@ class PeriodClosingVoucher(AccountsController):
self.posting_date, self.fiscal_year, company=self.company
)[1]
self.check_if_previous_year_closed()
pce = frappe.db.sql(
"""select name from `tabPeriod Closing Voucher`
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
@ -78,28 +106,64 @@ class PeriodClosingVoucher(AccountsController):
)
)
def make_gl_entries(self):
def check_if_previous_year_closed(self):
last_year_closing = add_days(self.year_start_date, -1)
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
if previous_fiscal_year and not frappe.db.exists(
"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
):
return
if previous_fiscal_year and not frappe.db.exists(
"Period Closing Voucher",
{"posting_date": ("<=", last_year_closing), "docstatus": 1, "company": self.company},
):
frappe.throw(_("Previous Year is not closed, please close it first"))
def make_gl_entries(self, get_opening_entries=False):
gl_entries = self.get_gl_entries()
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
if gl_entries:
if len(gl_entries) > 5000:
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
frappe.enqueue(
process_gl_entries,
gl_entries=gl_entries,
closing_entries=closing_entries,
voucher_name=self.name,
queue="long",
)
frappe.msgprint(
_("The GL Entries will be processed in the background, it can take a few minutes."),
alert=True,
)
else:
process_gl_entries(gl_entries)
process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
def get_grouped_gl_entries(self, get_opening_entries=False):
closing_entries = []
for acc in self.get_balances_based_on_dimensions(
group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries
):
closing_entries.append(self.get_closing_entries(acc))
return closing_entries
def get_gl_entries(self):
gl_entries = []
# pl account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
for acc in self.get_balances_based_on_dimensions(
group_by_account=True, report_type="Profit and Loss"
):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_pl_account(acc))
# closing liability account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
for acc in self.get_balances_based_on_dimensions(
group_by_account=False, report_type="Profit and Loss"
):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_closing_account(acc))
@ -108,6 +172,8 @@ class PeriodClosingVoucher(AccountsController):
def get_gle_for_pl_account(self, acc):
gl_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
@ -120,6 +186,7 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
"is_period_closing_voucher_entry": 1,
},
item=acc,
)
@ -129,6 +196,8 @@ class PeriodClosingVoucher(AccountsController):
def get_gle_for_closing_account(self, acc):
gl_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"account": self.closing_account_head,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
@ -141,12 +210,36 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
"is_period_closing_voucher_entry": 1,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
def get_closing_entries(self, acc):
closing_entry = self.get_gl_dict(
{
"company": self.company,
"closing_date": self.posting_date,
"period_closing_voucher": self.name,
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": flt(acc.debit_in_account_currency),
"debit": flt(acc.debit),
"credit_in_account_currency": flt(acc.credit_in_account_currency),
"credit": flt(acc.credit),
},
item=acc,
)
for dimension in self.accounting_dimensions:
closing_entry.update({dimension: acc.get(dimension)})
return closing_entry
def update_default_dimensions(self, gl_entry, acc):
if not self.accounting_dimensions:
self.accounting_dimensions = get_accounting_dimensions()
@ -154,47 +247,88 @@ class PeriodClosingVoucher(AccountsController):
for dimension in self.accounting_dimensions:
gl_entry.update({dimension: acc.get(dimension)})
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
def get_balances_based_on_dimensions(
self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
):
"""Get balance for dimension-wise pl accounts"""
dimension_fields = ["t1.cost_center", "t1.finance_book"]
qb_dimension_fields = ["cost_center", "finance_book", "project"]
self.accounting_dimensions = get_accounting_dimensions()
for dimension in self.accounting_dimensions:
dimension_fields.append("t1.{0}".format(dimension))
qb_dimension_fields.append(dimension)
if group_by_account:
dimension_fields.append("t1.account")
qb_dimension_fields.append("account")
return frappe.db.sql(
"""
select
t2.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,
account_filters = {
"company": self.company,
"is_group": 0,
}
if report_type:
account_filters.update({"report_type": report_type})
accounts = frappe.get_all("Account", filters=account_filters, pluck="name")
gl_entry = frappe.qb.DocType("GL Entry")
query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency)
if not for_aggregation:
query = query.select(
(Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_(
"bal_in_account_currency"
),
(Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"),
)
else:
query = query.select(
(Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"),
(Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"),
(Sum(gl_entry.debit)).as_("debit"),
(Sum(gl_entry.credit)).as_("credit"),
)
for dimension in qb_dimension_fields:
query = query.select(gl_entry[dimension])
query = query.where(
(gl_entry.company == self.company)
& (gl_entry.is_cancelled == 0)
& (gl_entry.account.isin(accounts))
)
if get_opening_entries:
query = query.where(
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
| gl_entry.is_opening
== "Yes"
)
else:
query = query.where(
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
& gl_entry.is_opening
== "No"
)
def process_gl_entries(gl_entries):
if for_aggregation:
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
for dimension in qb_dimension_fields:
query = query.groupby(gl_entry[dimension])
return query.run(as_dict=1)
def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
from erpnext.accounts.general_ledger import make_gl_entries
try:
make_gl_entries(gl_entries, merge_entries=False)
make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
)

View File

@ -16,16 +16,17 @@ from erpnext.accounts.utils import get_fiscal_year, now
class TestPeriodClosingVoucher(unittest.TestCase):
def test_closing_entry(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
posting_date=now(),
save=False,
)
jv1.company = company
@ -33,18 +34,18 @@ class TestPeriodClosingVoucher(unittest.TestCase):
jv1.submit()
jv2 = make_journal_entry(
posting_date="2021-03-15",
amount=600,
account1="Cost of Goods Sold - TPC",
account2="Cash - TPC",
cost_center=cost_center,
posting_date=now(),
save=False,
)
jv2.company = company
jv2.save()
jv2.submit()
pcv = self.make_period_closing_voucher()
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
surplus_account = pcv.closing_account_head
expected_gle = (
@ -65,6 +66,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
def test_cost_center_wise_posting(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
@ -81,6 +83,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
create_sales_invoice(
company=company,
@ -91,9 +94,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
pcv = self.make_period_closing_voucher(submit=False)
pcv = self.make_period_closing_voucher(posting_date="2021-03-31", submit=False)
pcv.save()
pcv.submit()
surplus_account = pcv.closing_account_head
@ -128,12 +132,13 @@ class TestPeriodClosingVoucher(unittest.TestCase):
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
si = create_sales_invoice(
create_sales_invoice(
company=company,
income_account="Sales - TPC",
expense_account="Cost of Goods Sold - TPC",
@ -142,6 +147,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
debit_to="Debtors - TPC",
currency="USD",
customer="_Test Customer USD",
posting_date="2021-03-15",
)
jv = make_journal_entry(
@ -149,14 +155,14 @@ class TestPeriodClosingVoucher(unittest.TestCase):
account2="Sales - TPC",
amount=400,
cost_center=cost_center,
posting_date=now(),
posting_date="2021-03-15",
)
jv.company = company
jv.finance_book = create_finance_book().name
jv.save()
jv.submit()
pcv = self.make_period_closing_voucher()
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
surplus_account = pcv.closing_account_head
expected_gle = (
@ -177,14 +183,130 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertSequenceEqual(pcv_gle, expected_gle)
def make_period_closing_voucher(self, submit=True):
def test_gl_entries_restrictions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
company = create_company()
cost_center = create_cost_center("Test Cost Center 1")
self.make_period_closing_voucher(posting_date="2021-03-31")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center,
save=False,
)
jv1.company = company
jv1.save()
self.assertRaises(frappe.ValidationError, jv1.submit)
def test_closing_balance_with_dimensions(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
company = create_company()
cost_center1 = create_cost_center("Test Cost Center 1")
cost_center2 = create_cost_center("Test Cost Center 2")
jv1 = make_journal_entry(
posting_date="2021-03-15",
amount=400,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center1,
save=False,
)
jv1.company = company
jv1.save()
jv1.submit()
jv2 = make_journal_entry(
posting_date="2021-03-15",
amount=200,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
save=False,
)
jv2.company = company
jv2.save()
jv2.submit()
pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31")
closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center1,
"period_closing_voucher": pcv1.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
self.assertEqual(closing_balance.credit, 400)
self.assertEqual(closing_balance.credit_in_account_currency, 400)
jv3 = make_journal_entry(
posting_date="2022-03-15",
amount=300,
account1="Cash - TPC",
account2="Sales - TPC",
cost_center=cost_center2,
save=False,
)
jv3.company = company
jv3.save()
jv3.submit()
pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31")
cc1_closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center1,
"period_closing_voucher": pcv2.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
cc2_closing_balance = frappe.db.get_value(
"Account Closing Balance",
{
"account": "Sales - TPC",
"cost_center": cost_center2,
"period_closing_voucher": pcv2.name,
"is_period_closing_voucher_entry": 0,
},
["credit", "credit_in_account_currency"],
as_dict=1,
)
self.assertEqual(cc1_closing_balance.credit, 400)
self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400)
self.assertEqual(cc2_closing_balance.credit, 500)
self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
def make_period_closing_voucher(self, posting_date=None, submit=True):
surplus_account = create_account()
cost_center = create_cost_center("Test Cost Center 1")
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": today(),
"posting_date": today(),
"transaction_date": posting_date or today(),
"posting_date": posting_date or today(),
"company": "Test PCV Company",
"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
"cost_center": cost_center,

View File

@ -112,7 +112,8 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
party_type: "Customer",
account: this.frm.doc.debit_to,
price_list: this.frm.doc.selling_price_list,
pos_profile: pos_profile
pos_profile: pos_profile,
company_address: this.frm.doc.company_address
}, () => {
this.apply_pricing_rule();
});

View File

@ -300,6 +300,9 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
if gl_map[0]["voucher_type"] != "Period Closing Voucher":
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map:
make_entry(entry, adv_adj, update_outstanding, from_repost)
@ -519,6 +522,9 @@ def make_reverse_gl_entries(
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
@ -566,6 +572,28 @@ def check_freezing_date(posting_date, adv_adj=False):
)
def validate_against_pcv(is_opening, posting_date, company):
if is_opening and frappe.db.exists(
"Period Closing Voucher", {"docstatus": 1, "company": company}
):
frappe.throw(
_("Opening Entry can not be created after Period Closing Voucher is created."),
title=_("Invalid Opening Entry"),
)
last_pcv_date = frappe.db.get_value(
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
)
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
message = _("Books have been closed till the period ending on {0}").format(
formatdate(last_pcv_date)
)
message += "</br >"
message += _("You cannot create/amend any accounting entries till this date.")
frappe.throw(message, title=_("Period Closed"))
def set_as_cancel(voucher_type, voucher_no):
"""
Set is_cancelled=1 in all original gl entries for the voucher

View File

@ -25,6 +25,8 @@ def execute(filters=None):
company=filters.company,
)
filters.period_start_date = period_list[0]["year_start_date"]
currency = filters.presentation_currency or frappe.get_cached_value(
"Company", filters.company, "default_currency"
)
@ -96,7 +98,7 @@ def execute(filters=None):
chart = get_chart_data(filters, columns, asset, liability, equity)
report_summary = get_report_summary(
period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency, filters
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
)
return columns, data, message, chart, report_summary
@ -174,7 +176,6 @@ def get_report_summary(
liability,
equity,
provisional_profit_loss,
total_credit,
currency,
filters,
consolidated=False,

View File

@ -118,7 +118,6 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
liability,
equity,
provisional_profit_loss,
total_credit,
company_currency,
filters,
True,

View File

@ -418,46 +418,47 @@ def set_gl_entries_by_account(
ignore_closing_entries=False,
):
"""Returns a dict like { "account": [gl entries], ... }"""
gl_entries = []
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
accounts = frappe.db.sql_list(
"""select name from `tabAccount`
where lft >= %s and rgt <= %s and company = %s""",
(root_lft, root_rgt, company),
accounts_list = frappe.db.get_all(
"Account",
filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
pluck="name",
)
if accounts:
additional_conditions += " and account in ({})".format(
", ".join(frappe.db.escape(d) for d in accounts)
)
ignore_opening_entries = False
if accounts_list:
# For balance sheet
if not from_date:
from_date = filters["period_start_date"]
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
fields=["posting_date", "name"],
order_by="posting_date desc",
limit=1,
)
if last_period_closing_voucher:
gl_entries += get_accounting_entries(
"Account Closing Balance",
from_date,
to_date,
accounts_list,
filters,
ignore_closing_entries,
last_period_closing_voucher[0].name,
)
from_date = add_days(last_period_closing_voucher[0].posting_date, 1)
ignore_opening_entries = True
gl_filters = {
"company": company,
"from_date": from_date,
"to_date": to_date,
"finance_book": cstr(filters.get("finance_book")),
}
if filters.get("include_default_book_entries"):
gl_filters["company_fb"] = frappe.get_cached_value("Company", company, "default_finance_book")
for key, value in filters.items():
if value:
gl_filters.update({key: value})
gl_entries = frappe.db.sql(
"""
select posting_date, account, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
and is_cancelled = 0""".format(
additional_conditions=additional_conditions
),
gl_filters,
as_dict=True,
gl_entries += get_accounting_entries(
"GL Entry",
from_date,
to_date,
accounts_list,
filters,
ignore_closing_entries,
ignore_opening_entries=ignore_opening_entries,
)
if filters and filters.get("presentation_currency"):
@ -469,34 +470,82 @@ def set_gl_entries_by_account(
return gl_entries_by_account
def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions = []
def get_accounting_entries(
doctype,
from_date,
to_date,
accounts,
filters,
ignore_closing_entries,
period_closing_voucher=None,
ignore_opening_entries=False,
):
gl_entry = frappe.qb.DocType(doctype)
query = (
frappe.qb.from_(gl_entry)
.select(
gl_entry.account,
gl_entry.debit,
gl_entry.credit,
gl_entry.debit_in_account_currency,
gl_entry.credit_in_account_currency,
gl_entry.account_currency,
)
.where(gl_entry.company == filters.company)
)
if doctype == "GL Entry":
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
query = query.where(gl_entry.is_cancelled == 0)
query = query.where(gl_entry.posting_date <= to_date)
if ignore_opening_entries:
query = query.where(gl_entry.is_opening == "No")
else:
query = query.select(gl_entry.closing_date.as_("posting_date"))
query = query.where(gl_entry.period_closing_voucher == period_closing_voucher)
query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)
query = query.where(gl_entry.account.isin(accounts))
entries = query.run(as_dict=True)
return entries
def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters):
gl_entry = frappe.qb.DocType(doctype)
accounting_dimensions = get_accounting_dimensions(as_list=False)
if ignore_closing_entries:
additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'")
if doctype == "GL Entry":
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
else:
query = query.where(gl_entry.is_period_closing_voucher_entry == 0)
if from_date:
additional_conditions.append("posting_date >= %(from_date)s")
if from_date and doctype == "GL Entry":
query = query.where(gl_entry.posting_date >= from_date)
if filters:
if filters.get("project"):
if not isinstance(filters.get("project"), list):
filters.project = frappe.parse_json(filters.get("project"))
additional_conditions.append("project in %(project)s")
query = query.where(gl_entry.project.isin(filters.project))
if filters.get("cost_center"):
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
additional_conditions.append("cost_center in %(cost_center)s")
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
if filters.get("include_default_book_entries"):
additional_conditions.append(
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
query = query.where(
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
| (gl_entry.finance_book.isnull())
)
else:
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
query = query.where(
(gl_entry.finance_book.isin([cstr(filters.company_fb), ""])) | (gl_entry.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:
@ -505,11 +554,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, filters.get(dimension.fieldname)
)
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
else:
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
query = query.where(gl_entry[dimension.fieldname].isin(filters[dimension.fieldname]))
return query
def get_cost_centers_with_children(cost_centers):

View File

@ -37,6 +37,29 @@ function get_filters() {
});
}
},
{
"fieldname":"party_type",
"label": __("Party Type"),
"fieldtype": "Link",
"options": "Party Type",
"default": "",
on_change: function() {
frappe.query_report.set_filter_value('party', "");
}
},
{
"fieldname":"party",
"label": __("Party"),
"fieldtype": "MultiSelectList",
get_data: function(txt) {
if (!frappe.query_report.filters) return;
let party_type = frappe.query_report.get_filter_value('party_type');
if (!party_type) return;
return frappe.db.get_link_options(party_type, txt);
},
},
{
"fieldname":"voucher_no",
"label": __("Voucher No"),
@ -49,6 +72,20 @@ function get_filters() {
"fieldtype": "Data",
"width": 100,
},
{
"fieldname":"include_account_currency",
"label": __("Include Account Currency"),
"fieldtype": "Check",
"width": 100,
},
{
"fieldname":"group_party",
"label": __("Group by Party"),
"fieldtype": "Check",
"width": 100,
},
]
return filters;

View File

@ -17,34 +17,26 @@ class PaymentLedger(object):
self.ple = qb.DocType("Payment Ledger Entry")
def init_voucher_dict(self):
if self.voucher_amount:
s = set()
# build a set of unique vouchers
# for each ple, using group_by_key to create a key and assign it to +/- list
for ple in self.voucher_amount:
key = (ple.voucher_type, ple.voucher_no, ple.party)
s.add(key)
group_by_key = None
if not self.filters.group_party:
group_by_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
else:
group_by_key = (ple.party_type, ple.party)
# for each unique vouchers, initialize +/- list
for key in s:
self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
# for each ple, using against voucher and amount, assign it to +/- list
# group by against voucher
for ple in self.voucher_amount:
against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
target = None
if self.voucher_dict.get(against_key):
if ple.amount > 0:
target = self.voucher_dict.get(against_key).increase
else:
target = self.voucher_dict.get(against_key).decrease
if ple.amount > 0:
target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("increase", [])
else:
target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("decrease", [])
# this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
# need to somehow include the stray entries as well.
if target is not None:
entry = frappe._dict(
company=ple.company,
posting_date=ple.posting_date,
account=ple.account,
party_type=ple.party_type,
party=ple.party,
@ -66,10 +58,10 @@ class PaymentLedger(object):
for value in self.voucher_dict.values():
voucher_data = []
if value.increase != []:
voucher_data.extend(value.increase)
if value.decrease != []:
voucher_data.extend(value.decrease)
if value.get("increase"):
voucher_data.extend(value.get("increase"))
if value.get("decrease"):
voucher_data.extend(value.get("decrease"))
if voucher_data:
# balance row
@ -117,6 +109,12 @@ class PaymentLedger(object):
if self.filters.against_voucher_no:
self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
if self.filters.party_type:
self.conditions.append(self.ple.party_type == self.filters.party_type)
if self.filters.party:
self.conditions.append(self.ple.party.isin(self.filters.party))
def get_data(self):
ple = self.ple
@ -134,7 +132,13 @@ class PaymentLedger(object):
def get_columns(self):
options = None
self.columns.append(
dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
dict(
label=_("Posting Date"),
fieldname="posting_date",
fieldtype="Date",
options=options,
width="100",
)
)
self.columns.append(
@ -160,7 +164,11 @@ class PaymentLedger(object):
)
self.columns.append(
dict(
label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
label=_("Voucher No"),
fieldname="voucher_no",
fieldtype="Dynamic Link",
options="voucher_type",
width="100",
)
)
self.columns.append(
@ -176,8 +184,8 @@ class PaymentLedger(object):
dict(
label=_("Against Voucher No"),
fieldname="against_voucher_no",
fieldtype="data",
options=options,
fieldtype="Dynamic Link",
options="against_voucher_type",
width="100",
)
)
@ -209,7 +217,7 @@ class PaymentLedger(object):
self.get_columns()
self.get_data()
# initialize dictionary and group using against voucher
# initialize dictionary and group using key
self.init_voucher_dict()
# convert dictionary to list and add balance rows

View File

@ -1,451 +0,0 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// Contributed by Case Solved and sponsored by Nulight Studios
/* eslint-disable */
frappe.provide('frappe.query_reports');
frappe.query_reports["Tax Detail"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("company"),
reqd: 1
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.month_start(frappe.datetime.get_today()),
reqd: 1,
width: "60px"
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.month_end(frappe.datetime.get_today()),
reqd: 1,
width: "60px"
},
{
fieldname: "report_name",
label: __("Report Name"),
fieldtype: "Read Only",
default: frappe.query_report.report_name,
hidden: 1,
reqd: 1
},
{
fieldname: "mode",
label: __("Mode"),
fieldtype: "Read Only",
default: "edit",
hidden: 1,
reqd: 1
}
],
onload: function onload(report) {
// Remove Add Column and Save from menu
report.page.add_inner_button(__("New Report"), () => new_report(), __("Custom Report"));
report.page.add_inner_button(__("Load Report"), () => load_report(), __("Custom Report"));
hide_filters(report);
}
};
function hide_filters(report) {
report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
if (field.dataset.fieldtype == "Read Only") {
field.classList.add("hidden");
}
});
}
erpnext.TaxDetail = class TaxDetail {
constructor() {
this.patch();
this.load_report();
}
// Monkey patch the QueryReport class
patch() {
this.qr = frappe.query_report;
this.super = {
refresh_report: this.qr.refresh_report,
show_footer_message: this.qr.show_footer_message
}
this.qr.refresh_report = () => this.refresh_report();
this.qr.show_footer_message = () => this.show_footer_message();
}
show_footer_message() {
// The last thing to run after datatable_render in refresh()
this.super.show_footer_message.apply(this.qr);
if (this.qr.report_name !== 'Tax Detail') {
this.show_help();
if (this.loading) {
this.set_section('');
} else {
this.reload_component('');
}
}
this.loading = false;
}
refresh_report() {
// Infrequent report build (onload), load filters & data
// super function runs a refresh() serially
// already run within frappe.run_serially
this.loading = true;
this.super.refresh_report.apply(this.qr);
if (this.qr.report_name !== 'Tax Detail') {
frappe.call({
method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
args: {name: this.qr.report_name}
}).then((r) => {
const data = JSON.parse(r.message[this.qr.report_name]['json']);
this.create_controls();
this.sections = data.sections || {};
this.controls['show_detail'].set_input(data.show_detail);
});
}
}
load_report() {
// One-off report build like titles, menu, etc
// Run when this object is created which happens in qr.load_report
this.qr.menu_items = this.get_menu_items();
}
get_menu_items() {
// Replace Save action
let new_items = [];
const save = __('Save');
for (let item of this.qr.menu_items) {
if (item.label === save) {
new_items.push({
label: save,
action: () => this.save_report(),
standard: false
});
} else {
new_items.push(item);
}
}
return new_items;
}
save_report() {
this.check_datatable();
if (this.qr.report_name !== 'Tax Detail') {
frappe.call({
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
args: {
reference_report: 'Tax Detail',
report_name: this.qr.report_name,
data: {
columns: this.qr.get_visible_columns(),
sections: this.sections,
show_detail: this.controls['show_detail'].get_input_value()
}
},
freeze: true
}).then((r) => {
this.set_section('');
});
}
}
check_datatable() {
if (!this.qr.datatable) {
frappe.throw(__('Please change the date range to load data first'));
}
}
set_section(name) {
// Sets the given section name and then reloads the data
if (name && !this.sections[name]) {
this.sections[name] = {};
}
let options = Object.keys(this.sections);
options.unshift('');
this.controls['section_name'].$wrapper.find("select").empty().add_options(options);
const org_mode = this.qr.get_filter_value('mode');
let refresh = false;
if (name) {
this.controls['section_name'].set_input(name);
this.qr.set_filter_value('mode', 'edit');
if (org_mode === 'run') {
refresh = true;
}
} else {
this.controls['section_name'].set_input('');
this.qr.set_filter_value('mode', 'run');
if (org_mode === 'edit') {
refresh = true;
}
}
if (refresh) {
this.qr.refresh();
}
this.reload_component('');
}
reload_component(component_name) {
const section_name = this.controls['section_name'].get_input_value();
if (section_name) {
const section = this.sections[section_name];
const component_names = Object.keys(section);
component_names.unshift('');
this.controls['component'].$wrapper.find("select").empty().add_options(component_names);
this.controls['component'].set_input(component_name);
if (component_name) {
this.controls['component_type'].set_input(section[component_name].type);
}
} else {
this.controls['component'].$wrapper.find("select").empty();
this.controls['component'].set_input('');
}
this.set_table_filters();
}
set_table_filters() {
let filters = {};
const section_name = this.controls['section_name'].get_input_value();
const component_name = this.controls['component'].get_input_value();
if (section_name && component_name) {
const component_type = this.sections[section_name][component_name].type;
if (component_type === 'filter') {
filters = this.sections[section_name][component_name]['filters'];
}
}
this.setAppliedFilters(filters);
}
setAppliedFilters(filters) {
if (this.qr.datatable) {
Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
let idx = input.dataset.colIndex;
if (filters[idx]) {
input.value = filters[idx];
} else {
input.value = null;
}
});
this.qr.datatable.columnmanager.applyFilter(filters);
}
}
delete(name, type) {
if (type === 'section') {
delete this.sections[name];
const new_section = Object.keys(this.sections)[0] || '';
this.set_section(new_section);
}
if (type === 'component') {
const cur_section = this.controls['section_name'].get_input_value();
delete this.sections[cur_section][name];
this.reload_component('');
}
}
create_controls() {
let controls = {};
// SELECT in data.js
controls['section_name'] = this.qr.page.add_field({
label: __('Section'),
fieldtype: 'Select',
fieldname: 'section_name',
change: (e) => {
this.set_section(this.controls['section_name'].get_input_value());
}
});
// BUTTON in button.js
controls['new_section'] = this.qr.page.add_field({
label: __('New Section'),
fieldtype: 'Button',
fieldname: 'new_section',
click: () => {
frappe.prompt({
label: __('Section Name'),
fieldname: 'name',
fieldtype: 'Data'
}, (values) => {
this.set_section(values.name);
});
}
});
controls['delete_section'] = this.qr.page.add_field({
label: __('Delete Section'),
fieldtype: 'Button',
fieldname: 'delete_section',
click: () => {
let cur_section = this.controls['section_name'].get_input_value();
if (cur_section) {
frappe.confirm(__('Are you sure you want to delete section') + ' ' + cur_section + '?',
() => {this.delete(cur_section, 'section')});
}
}
});
controls['component'] = this.qr.page.add_field({
label: __('Component'),
fieldtype: 'Select',
fieldname: 'component',
change: (e) => {
this.reload_component(this.controls['component'].get_input_value());
}
});
controls['component_type'] = this.qr.page.add_field({
label: __('Component Type'),
fieldtype: 'Select',
fieldname: 'component_type',
default: 'filter',
options: [
{label: __('Filtered Row Subtotal'), value: 'filter'},
{label: __('Section Subtotal'), value: 'section'}
]
});
controls['add_component'] = this.qr.page.add_field({
label: __('Add Component'),
fieldtype: 'Button',
fieldname: 'add_component',
click: () => {
this.check_datatable();
let section_name = this.controls['section_name'].get_input_value();
if (section_name) {
const component_type = this.controls['component_type'].get_input_value();
let idx = 0;
const names = Object.keys(this.sections[section_name]);
if (names.length > 0) {
const idxs = names.map((key) => parseInt(key.match(/\d+$/)) || 0);
idx = Math.max(...idxs) + 1;
}
const filters = this.qr.datatable.columnmanager.getAppliedFilters();
if (component_type === 'filter') {
const name = 'Filter' + idx.toString();
let data = {
type: component_type,
filters: filters
}
this.sections[section_name][name] = data;
this.reload_component(name);
} else if (component_type === 'section') {
if (filters && Object.keys(filters).length !== 0) {
frappe.show_alert({
message: __('Column filters ignored'),
indicator: 'yellow'
});
}
let data = {
type: component_type
}
frappe.prompt({
label: __('Section'),
fieldname: 'section',
fieldtype: 'Select',
options: Object.keys(this.sections)
}, (values) => {
this.sections[section_name][values.section] = data;
this.reload_component(values.section);
});
} else {
frappe.throw(__('Please select the Component Type first'));
}
} else {
frappe.throw(__('Please select the Section first'));
}
}
});
controls['delete_component'] = this.qr.page.add_field({
label: __('Delete Component'),
fieldtype: 'Button',
fieldname: 'delete_component',
click: () => {
const component = this.controls['component'].get_input_value();
if (component) {
frappe.confirm(__('Are you sure you want to delete component') + ' ' + component + '?',
() => {this.delete(component, 'component')});
}
}
});
controls['save'] = this.qr.page.add_field({
label: __('Save & Run'),
fieldtype: 'Button',
fieldname: 'save',
click: () => {
this.save_report();
}
});
controls['show_detail'] = this.qr.page.add_field({
label: __('Show Detail'),
fieldtype: 'Check',
fieldname: 'show_detail',
default: 1
});
this.controls = controls;
}
show_help() {
const help = __('Your custom report is built from General Ledger Entries within the date range. You can add multiple sections to the report using the New Section button. Each component added to a section adds a subset of the data into the specified section. Beware of duplicated data rows. The Filtered Row component type saves the datatable column filters to specify the added data. The Section component type refers to the data in a previously defined section, but it cannot refer to its parent section. The Amount column is summed to give the section subtotal. Use the Show Detail box to see the data rows included in each section in the final report. Once finished, hit Save & Run. Report contributed by');
this.qr.$report_footer.append('<div class="col-md-12"><strong>' + __('Help') + `: </strong>${help}<a href="https://www.casesolved.co.uk"> Case Solved</a></div>`);
}
}
if (!window.taxdetail) {
window.taxdetail = new erpnext.TaxDetail();
}
function get_reports(cb) {
frappe.call({
method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
freeze: true
}).then((r) => {
cb(r.message);
})
}
function new_report() {
const dialog = new frappe.ui.Dialog({
title: __('New Report'),
fields: [
{
fieldname: 'report_name',
label: __('Report Name'),
fieldtype: 'Data',
default: 'VAT Return'
}
],
primary_action_label: __('Create'),
primary_action: function new_report_pa(values) {
frappe.call({
method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
args: {
reference_report: 'Tax Detail',
report_name: values.report_name,
data: {
columns: [],
sections: {},
show_detail: 1
}
},
freeze: true
}).then((r) => {
frappe.set_route('query-report', values.report_name);
});
dialog.hide();
}
});
dialog.show();
}
function load_report() {
get_reports(function load_report_cb(reports) {
const dialog = new frappe.ui.Dialog({
title: __('Load Report'),
fields: [
{
fieldname: 'report_name',
label: __('Report Name'),
fieldtype: 'Select',
options: Object.keys(reports)
}
],
primary_action_label: __('Load'),
primary_action: function load_report_pa(values) {
dialog.hide();
frappe.set_route('query-report', values.report_name);
}
});
dialog.show();
});
}

View File

@ -1,32 +0,0 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-02-19 16:44:21.175113",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2021-02-19 16:44:21.175113",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Detail",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GL Entry",
"report_name": "Tax Detail",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
}

View File

@ -1,325 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# Contributed by Case Solved and sponsored by Nulight Studios
import json
import frappe
from frappe import _
# NOTE: Payroll is implemented using Journal Entries which are included as GL Entries
# field lists in multiple doctypes will be coalesced
required_sql_fields = {
("GL Entry", 1): ["posting_date"],
("Account",): ["root_type", "account_type"],
("GL Entry", 2): ["account", "voucher_type", "voucher_no", "debit", "credit"],
("Purchase Invoice Item", "Sales Invoice Item"): [
"base_net_amount",
"item_tax_rate",
"item_tax_template",
"item_group",
"item_name",
],
("Purchase Invoice", "Sales Invoice"): ["taxes_and_charges", "tax_category"],
}
def execute(filters=None):
if not filters:
return [], []
fieldlist = required_sql_fields
fieldstr = get_fieldstr(fieldlist)
gl_entries = frappe.db.sql(
"""
select {fieldstr}
from `tabGL Entry` ge
inner join `tabAccount` a on
ge.account=a.name and ge.company=a.company
left join `tabSales Invoice` si on
ge.company=si.company and ge.voucher_type='Sales Invoice' and ge.voucher_no=si.name
left join `tabSales Invoice Item` sii on
a.root_type='Income' and si.name=sii.parent
left join `tabPurchase Invoice` pi on
ge.company=pi.company and ge.voucher_type='Purchase Invoice' and ge.voucher_no=pi.name
left join `tabPurchase Invoice Item` pii on
a.root_type='Expense' and pi.name=pii.parent
where
ge.company=%(company)s and
ge.posting_date>=%(from_date)s and
ge.posting_date<=%(to_date)s
order by ge.posting_date, ge.voucher_no
""".format(
fieldstr=fieldstr
),
filters,
as_dict=1,
)
report_data = modify_report_data(gl_entries)
summary = None
if filters["mode"] == "run" and filters["report_name"] != "Tax Detail":
report_data, summary = run_report(filters["report_name"], report_data)
# return columns, data, message, chart, report_summary
return get_columns(fieldlist), report_data, None, None, summary
def run_report(report_name, data):
"Applies the sections and filters saved in the custom report"
report_config = json.loads(frappe.get_doc("Report", report_name).json)
# Columns indexed from 1 wrt colno
columns = report_config.get("columns")
sections = report_config.get("sections", {})
show_detail = report_config.get("show_detail", 1)
report = {}
new_data = []
summary = []
for section_name, section in sections.items():
report[section_name] = {"rows": [], "subtotal": 0.0}
for component_name, component in section.items():
if component["type"] == "filter":
for row in data:
matched = True
for colno, filter_string in component["filters"].items():
filter_field = columns[int(colno) - 1]["fieldname"]
if not filter_match(row[filter_field], filter_string):
matched = False
break
if matched:
report[section_name]["rows"] += [row]
report[section_name]["subtotal"] += row["amount"]
if component["type"] == "section":
if component_name == section_name:
frappe.throw(_("A report component cannot refer to its parent section") + ": " + section_name)
try:
report[section_name]["rows"] += report[component_name]["rows"]
report[section_name]["subtotal"] += report[component_name]["subtotal"]
except KeyError:
frappe.throw(
_("A report component can only refer to an earlier section") + ": " + section_name
)
if show_detail:
new_data += report[section_name]["rows"]
new_data += [{"voucher_no": section_name, "amount": report[section_name]["subtotal"]}]
summary += [
{"label": section_name, "datatype": "Currency", "value": report[section_name]["subtotal"]}
]
if show_detail:
new_data += [{}]
return new_data or data, summary or None
def filter_match(value, string):
"Approximation to datatable filters"
import datetime
if string == "":
return True
if value is None:
value = -999999999999999
elif isinstance(value, datetime.date):
return True
if isinstance(value, str):
value = value.lower()
string = string.lower()
if string[0] == "<":
return True if string[1:].strip() else False
elif string[0] == ">":
return False if string[1:].strip() else True
elif string[0] == "=":
return string[1:] in value if string[1:] else False
elif string[0:2] == "!=":
return string[2:] not in value
elif len(string.split(":")) == 2:
pre, post = string.split(":")
return True if not pre.strip() and post.strip() in value else False
else:
return string in value
else:
if string[0] in ["<", ">", "="]:
operator = string[0]
if operator == "=":
operator = "=="
string = string[1:].strip()
elif string[0:2] == "!=":
operator = "!="
string = string[2:].strip()
elif len(string.split(":")) == 2:
pre, post = string.split(":")
try:
return True if float(pre) <= value and float(post) >= value else False
except ValueError:
return False if pre.strip() else True
else:
return string in str(value)
try:
num = float(string) if string.strip() else 0
return frappe.safe_eval(f"{value} {operator} {num}")
except ValueError:
if operator == "<":
return True
return False
def abbrev(dt):
return "".join(l[0].lower() for l in dt.split(" ")) + "."
def doclist(dt, dfs):
return [abbrev(dt) + f for f in dfs]
def as_split(fields):
for field in fields:
split = field.split(" as ")
yield (split[0], split[1] if len(split) > 1 else split[0])
def coalesce(doctypes, fields):
coalesce = []
for name, new_name in as_split(fields):
sharedfields = ", ".join(abbrev(dt) + name for dt in doctypes)
coalesce += [f"coalesce({sharedfields}) as {new_name}"]
return coalesce
def get_fieldstr(fieldlist):
fields = []
for doctypes, docfields in fieldlist.items():
if len(doctypes) == 1 or isinstance(doctypes[1], int):
fields += doclist(doctypes[0], docfields)
else:
fields += coalesce(doctypes, docfields)
return ", ".join(fields)
def get_columns(fieldlist):
columns = {}
for doctypes, docfields in fieldlist.items():
fieldmap = {name: new_name for name, new_name in as_split(docfields)}
for doctype in doctypes:
if isinstance(doctype, int):
break
meta = frappe.get_meta(doctype)
# get column field metadata from the db
fieldmeta = {}
for field in meta.get("fields"):
if field.fieldname in fieldmap.keys():
new_name = fieldmap[field.fieldname]
fieldmeta[new_name] = {
"label": _(field.label),
"fieldname": new_name,
"fieldtype": field.fieldtype,
"options": field.options,
}
# edit the columns to match the modified data
for field in fieldmap.values():
col = modify_report_columns(doctype, field, fieldmeta[field])
if col:
columns[col["fieldname"]] = col
# use of a dict ensures duplicate columns are removed
return list(columns.values())
def modify_report_columns(doctype, field, column):
"Because data is rearranged into other columns"
if doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
if field in ["item_tax_rate", "base_net_amount"]:
return None
if doctype == "GL Entry":
if field in ["debit", "credit"]:
column.update({"label": _("Amount"), "fieldname": "amount"})
elif field == "voucher_type":
column.update({"fieldtype": "Data", "options": ""})
if field == "taxes_and_charges":
column.update({"label": _("Taxes and Charges Template")})
return column
def modify_report_data(data):
import json
new_data = []
for line in data:
if line.debit:
line.amount = -line.debit
else:
line.amount = line.credit
# Remove Invoice GL Tax Entries and generate Tax entries from the invoice lines
if "Invoice" in line.voucher_type:
if line.account_type not in ("Tax", "Round Off"):
new_data += [line]
if line.item_tax_rate:
tax_rates = json.loads(line.item_tax_rate)
for account, rate in tax_rates.items():
tax_line = line.copy()
tax_line.account_type = "Tax"
tax_line.account = account
if line.voucher_type == "Sales Invoice":
line.amount = line.base_net_amount
tax_line.amount = line.base_net_amount * (rate / 100)
if line.voucher_type == "Purchase Invoice":
line.amount = -line.base_net_amount
tax_line.amount = -line.base_net_amount * (rate / 100)
new_data += [tax_line]
else:
new_data += [line]
return new_data
# JS client utilities
custom_report_dict = {
"ref_doctype": "GL Entry",
"report_type": "Custom Report",
"reference_report": "Tax Detail",
}
@frappe.whitelist()
def get_custom_reports(name=None):
filters = custom_report_dict.copy()
if name:
filters["name"] = name
reports = frappe.get_list("Report", filters=filters, fields=["name", "json"], as_list=False)
reports_dict = {rep.pop("name"): rep for rep in reports}
# Prevent custom reports with the same name
reports_dict["Tax Detail"] = {"json": None}
return reports_dict
@frappe.whitelist()
def save_custom_report(reference_report, report_name, data):
if reference_report != "Tax Detail":
frappe.throw(_("The wrong report is referenced."))
if report_name == "Tax Detail":
frappe.throw(_("The parent report cannot be overwritten."))
doc = {
"doctype": "Report",
"report_name": report_name,
"is_standard": "No",
"module": "Accounts",
"json": data,
}
doc.update(custom_report_dict)
try:
newdoc = frappe.get_doc(doc)
newdoc.insert()
frappe.msgprint(_("Report created successfully"))
except frappe.exceptions.DuplicateEntryError:
dbdoc = frappe.get_doc("Report", report_name)
dbdoc.update(doc)
dbdoc.save()
frappe.msgprint(_("Report updated successfully"))
return report_name

View File

@ -1,840 +0,0 @@
[
{
"account_manager": null,
"accounts": [],
"companies": [],
"credit_limits": [],
"customer_details": null,
"customer_group": "All Customer Groups",
"customer_name": "_Test Customer",
"customer_pos_id": null,
"customer_primary_address": null,
"customer_primary_contact": null,
"customer_type": "Company",
"default_bank_account": null,
"default_commission_rate": 0.0,
"default_currency": null,
"default_price_list": null,
"default_sales_partner": null,
"disabled": 0,
"dn_required": 0,
"docstatus": 0,
"doctype": "Customer",
"email_id": null,
"gender": null,
"image": null,
"industry": null,
"is_frozen": 0,
"is_internal_customer": 0,
"language": "en",
"lead_name": null,
"loyalty_program": null,
"loyalty_program_tier": null,
"market_segment": null,
"mobile_no": null,
"modified": "2021-02-15 05:18:03.624724",
"name": "_Test Customer",
"naming_series": "CUST-.YYYY.-",
"pan": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"payment_terms": null,
"primary_address": null,
"represents_company": "",
"sales_team": [],
"salutation": null,
"so_required": 0,
"tax_category": null,
"tax_id": null,
"tax_withholding_category": null,
"territory": "All Territories",
"website": null
},{
"accounts": [],
"allow_purchase_invoice_creation_without_purchase_order": 0,
"allow_purchase_invoice_creation_without_purchase_receipt": 0,
"companies": [],
"country": "United Kingdom",
"default_bank_account": null,
"default_currency": null,
"default_price_list": null,
"disabled": 0,
"docstatus": 0,
"doctype": "Supplier",
"hold_type": "",
"image": null,
"is_frozen": 0,
"is_internal_supplier": 0,
"is_transporter": 0,
"language": "en",
"modified": "2021-03-31 16:47:10.109316",
"name": "_Test Supplier",
"naming_series": "SUP-.YYYY.-",
"on_hold": 0,
"pan": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"payment_terms": null,
"prevent_pos": 0,
"prevent_rfqs": 0,
"release_date": null,
"represents_company": null,
"supplier_details": null,
"supplier_group": "Raw Material",
"supplier_name": "_Test Supplier",
"supplier_type": "Company",
"tax_category": null,
"tax_id": null,
"tax_withholding_category": null,
"warn_pos": 0,
"warn_rfqs": 0,
"website": null
},{
"account_currency": "GBP",
"account_name": "Debtors",
"account_number": "",
"account_type": "Receivable",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 58,
"modified": "2021-03-26 04:44:19.955468",
"name": "Debtors - _T",
"old_parent": null,
"parent": null,
"parent_account": "Application of Funds (Assets) - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Balance Sheet",
"rgt": 59,
"root_type": "Asset",
"tax_rate": 0.0
},{
"account_currency": "GBP",
"account_name": "Sales",
"account_number": "",
"account_type": "Income Account",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 291,
"modified": "2021-03-26 04:50:21.697703",
"name": "Sales - _T",
"old_parent": null,
"parent": null,
"parent_account": "Income - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Profit and Loss",
"rgt": 292,
"root_type": "Income",
"tax_rate": 0.0
},{
"account_currency": "GBP",
"account_name": "VAT on Sales",
"account_number": "",
"account_type": "Tax",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 317,
"modified": "2021-03-26 04:50:21.697703",
"name": "VAT on Sales - _T",
"old_parent": null,
"parent": null,
"parent_account": "Source of Funds (Liabilities) - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Balance Sheet",
"rgt": 318,
"root_type": "Liability",
"tax_rate": 0.0
},{
"account_currency": "GBP",
"account_name": "Cost of Goods Sold",
"account_number": "",
"account_type": "Cost of Goods Sold",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 171,
"modified": "2021-03-26 04:44:19.994857",
"name": "Cost of Goods Sold - _T",
"old_parent": null,
"parent": null,
"parent_account": "Expenses - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Profit and Loss",
"rgt": 172,
"root_type": "Expense",
"tax_rate": 0.0
},{
"account_currency": "GBP",
"account_name": "VAT on Purchases",
"account_number": "",
"account_type": "Tax",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 80,
"modified": "2021-03-26 04:44:19.961983",
"name": "VAT on Purchases - _T",
"old_parent": null,
"parent": null,
"parent_account": "Application of Funds (Assets) - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Balance Sheet",
"rgt": 81,
"root_type": "Asset",
"tax_rate": 0.0
},{
"account_currency": "GBP",
"account_name": "Creditors",
"account_number": "",
"account_type": "Payable",
"balance_must_be": "",
"company": "_T",
"disabled": 0,
"docstatus": 0,
"doctype": "Account",
"freeze_account": "No",
"include_in_gross": 0,
"inter_company_account": 0,
"is_group": 0,
"lft": 302,
"modified": "2021-03-26 04:50:21.697703",
"name": "Creditors - _T",
"old_parent": null,
"parent": null,
"parent_account": "Source of Funds (Liabilities) - _T",
"parentfield": null,
"parenttype": null,
"report_type": "Balance Sheet",
"rgt": 303,
"root_type": "Liability",
"tax_rate": 0.0
},{
"additional_discount_percentage": 0.0,
"address_display": null,
"adjust_advance_taxes": 0,
"advances": [],
"against_expense_account": "Cost of Goods Sold - _T",
"allocate_advances_automatically": 0,
"amended_from": null,
"apply_discount_on": "Grand Total",
"apply_tds": 0,
"auto_repeat": null,
"base_discount_amount": 0.0,
"base_grand_total": 511.68,
"base_in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
"base_net_total": 426.4,
"base_paid_amount": 0.0,
"base_rounded_total": 511.68,
"base_rounding_adjustment": 0.0,
"base_taxes_and_charges_added": 85.28,
"base_taxes_and_charges_deducted": 0.0,
"base_total": 426.4,
"base_total_taxes_and_charges": 85.28,
"base_write_off_amount": 0.0,
"bill_date": null,
"bill_no": null,
"billing_address": null,
"billing_address_display": null,
"buying_price_list": "Standard Buying",
"cash_bank_account": null,
"clearance_date": null,
"company": "_T",
"contact_display": null,
"contact_email": null,
"contact_mobile": null,
"contact_person": null,
"conversion_rate": 1.0,
"cost_center": null,
"credit_to": "Creditors - _T",
"currency": "GBP",
"disable_rounded_total": 0,
"discount_amount": 0.0,
"docstatus": 0,
"doctype": "Purchase Invoice",
"due_date": null,
"from_date": null,
"grand_total": 511.68,
"group_same_items": 0,
"hold_comment": null,
"ignore_pricing_rule": 0,
"in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
"inter_company_invoice_reference": null,
"is_internal_supplier": 0,
"is_opening": "No",
"is_paid": 0,
"is_return": 0,
"is_subcontracted": 0,
"items": [
{
"allow_zero_valuation_rate": 0,
"amount": 426.4,
"asset_category": null,
"asset_location": null,
"base_amount": 426.4,
"base_net_amount": 426.4,
"base_net_rate": 5.33,
"base_price_list_rate": 5.33,
"base_rate": 5.33,
"base_rate_with_margin": 0.0,
"batch_no": null,
"bom": null,
"brand": null,
"conversion_factor": 0.0,
"cost_center": "Main - _T",
"deferred_expense_account": null,
"description": "<div class=\"ql-editor read-mode\"><p>Fluid to make widgets</p></div>",
"discount_amount": 0.0,
"discount_percentage": 0.0,
"enable_deferred_expense": 0,
"expense_account": "Cost of Goods Sold - _T",
"from_warehouse": null,
"image": null,
"include_exploded_items": 0,
"is_fixed_asset": 0,
"is_free_item": 0,
"item_code": null,
"item_group": null,
"item_name": "Widget Fluid 1Litre",
"item_tax_amount": 0.0,
"item_tax_rate": "{\"VAT on Purchases - _T\": 20.0}",
"item_tax_template": null,
"landed_cost_voucher_amount": 0.0,
"manufacturer": null,
"manufacturer_part_no": null,
"margin_rate_or_amount": 0.0,
"margin_type": "",
"net_amount": 426.4,
"net_rate": 5.33,
"page_break": 0,
"parent": null,
"parentfield": "items",
"parenttype": "Purchase Invoice",
"po_detail": null,
"pr_detail": null,
"price_list_rate": 5.33,
"pricing_rules": null,
"project": null,
"purchase_invoice_item": null,
"purchase_order": null,
"purchase_receipt": null,
"qty": 80.0,
"quality_inspection": null,
"rate": 5.33,
"rate_with_margin": 0.0,
"received_qty": 0.0,
"rejected_qty": 0.0,
"rejected_serial_no": null,
"rejected_warehouse": null,
"rm_supp_cost": 0.0,
"sales_invoice_item": null,
"serial_no": null,
"service_end_date": null,
"service_start_date": null,
"service_stop_date": null,
"stock_qty": 0.0,
"stock_uom": "Nos",
"stock_uom_rate": 0.0,
"total_weight": 0.0,
"uom": "Nos",
"valuation_rate": 0.0,
"warehouse": null,
"weight_per_unit": 0.0,
"weight_uom": null
}
],
"language": "en",
"letter_head": null,
"mode_of_payment": null,
"modified": "2021-04-03 03:33:09.180453",
"name": null,
"naming_series": "ACC-PINV-.YYYY.-",
"net_total": 426.4,
"on_hold": 0,
"other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Purchases</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Widget Fluid 1Litre</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 426.40\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 85.28\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
"outstanding_amount": 511.68,
"paid_amount": 0.0,
"parent": null,
"parentfield": null,
"parenttype": null,
"party_account_currency": "GBP",
"payment_schedule": [],
"payment_terms_template": null,
"plc_conversion_rate": 1.0,
"posting_date": null,
"posting_time": "16:59:56.789522",
"price_list_currency": "GBP",
"pricing_rules": [],
"project": null,
"rejected_warehouse": null,
"release_date": null,
"remarks": "No Remarks",
"represents_company": null,
"return_against": null,
"rounded_total": 511.68,
"rounding_adjustment": 0.0,
"scan_barcode": null,
"select_print_heading": null,
"set_from_warehouse": null,
"set_posting_time": 0,
"set_warehouse": null,
"shipping_address": null,
"shipping_address_display": "",
"shipping_rule": null,
"status": "Unpaid",
"supplied_items": [],
"supplier": "_Test Supplier",
"supplier_address": null,
"supplier_name": "_Test Supplier",
"supplier_warehouse": "Stores - _T",
"tax_category": null,
"tax_id": null,
"tax_withholding_category": null,
"taxes": [
{
"account_head": "VAT on Purchases - _T",
"add_deduct_tax": "Add",
"base_tax_amount": 85.28,
"base_tax_amount_after_discount_amount": 85.28,
"base_total": 511.68,
"category": "Total",
"charge_type": "On Net Total",
"cost_center": "Main - _T",
"description": "VAT on Purchases",
"included_in_print_rate": 0,
"item_wise_tax_detail": "{\"Widget Fluid 1Litre\":[20.0,85.28]}",
"parent": null,
"parentfield": "taxes",
"parenttype": "Purchase Invoice",
"rate": 0.0,
"row_id": null,
"tax_amount": 85.28,
"tax_amount_after_discount_amount": 85.28,
"total": 511.68
}
],
"taxes_and_charges": null,
"taxes_and_charges_added": 85.28,
"taxes_and_charges_deducted": 0.0,
"tc_name": null,
"terms": null,
"title": "_Purchase Invoice",
"to_date": null,
"total": 426.4,
"total_advance": 0.0,
"total_net_weight": 0.0,
"total_qty": 80.0,
"total_taxes_and_charges": 85.28,
"unrealized_profit_loss_account": null,
"update_stock": 0,
"write_off_account": null,
"write_off_amount": 0.0,
"write_off_cost_center": null
},{
"account_for_change_amount": null,
"additional_discount_percentage": 0.0,
"address_display": null,
"advances": [],
"against_income_account": "Sales - _T",
"allocate_advances_automatically": 0,
"amended_from": null,
"apply_discount_on": "Grand Total",
"auto_repeat": null,
"base_change_amount": 0.0,
"base_discount_amount": 0.0,
"base_grand_total": 868.25,
"base_in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
"base_net_total": 825.0,
"base_paid_amount": 0.0,
"base_rounded_total": 868.25,
"base_rounding_adjustment": 0.0,
"base_total": 825.0,
"base_total_taxes_and_charges": 43.25,
"base_write_off_amount": 0.0,
"c_form_applicable": "No",
"c_form_no": null,
"campaign": null,
"cash_bank_account": null,
"change_amount": 0.0,
"commission_rate": 0.0,
"company": "_T",
"company_address": null,
"company_address_display": null,
"company_tax_id": null,
"contact_display": null,
"contact_email": null,
"contact_mobile": null,
"contact_person": null,
"conversion_rate": 1.0,
"cost_center": null,
"currency": "GBP",
"customer": "_Test Customer",
"customer_address": null,
"customer_group": "All Customer Groups",
"customer_name": "_Test Customer",
"debit_to": "Debtors - _T",
"discount_amount": 0.0,
"docstatus": 0,
"doctype": "Sales Invoice",
"due_date": null,
"from_date": null,
"grand_total": 868.25,
"group_same_items": 0,
"ignore_pricing_rule": 0,
"in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
"inter_company_invoice_reference": null,
"is_consolidated": 0,
"is_discounted": 0,
"is_internal_customer": 0,
"is_opening": "No",
"is_pos": 0,
"is_return": 0,
"items": [
{
"actual_batch_qty": 0.0,
"actual_qty": 0.0,
"allow_zero_valuation_rate": 0,
"amount": 200.0,
"asset": null,
"barcode": null,
"base_amount": 200.0,
"base_net_amount": 200.0,
"base_net_rate": 50.0,
"base_price_list_rate": 0.0,
"base_rate": 50.0,
"base_rate_with_margin": 0.0,
"batch_no": null,
"brand": null,
"conversion_factor": 1.0,
"cost_center": "Main - _T",
"customer_item_code": null,
"deferred_revenue_account": null,
"delivered_by_supplier": 0,
"delivered_qty": 0.0,
"delivery_note": null,
"description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
"discount_amount": 0.0,
"discount_percentage": 0.0,
"dn_detail": null,
"enable_deferred_revenue": 0,
"expense_account": null,
"finance_book": null,
"image": null,
"income_account": "Sales - _T",
"incoming_rate": 0.0,
"is_fixed_asset": 0,
"is_free_item": 0,
"item_code": null,
"item_group": null,
"item_name": "Dunlop tyres",
"item_tax_rate": "{\"VAT on Sales - _T\": 20.0}",
"item_tax_template": null,
"margin_rate_or_amount": 0.0,
"margin_type": "",
"net_amount": 200.0,
"net_rate": 50.0,
"page_break": 0,
"parent": null,
"parentfield": "items",
"parenttype": "Sales Invoice",
"price_list_rate": 0.0,
"pricing_rules": null,
"project": null,
"qty": 4.0,
"quality_inspection": null,
"rate": 50.0,
"rate_with_margin": 0.0,
"sales_invoice_item": null,
"sales_order": null,
"serial_no": null,
"service_end_date": null,
"service_start_date": null,
"service_stop_date": null,
"so_detail": null,
"stock_qty": 4.0,
"stock_uom": "Nos",
"stock_uom_rate": 50.0,
"target_warehouse": null,
"total_weight": 0.0,
"uom": "Nos",
"warehouse": null,
"weight_per_unit": 0.0,
"weight_uom": null
},
{
"actual_batch_qty": 0.0,
"actual_qty": 0.0,
"allow_zero_valuation_rate": 0,
"amount": 65.0,
"asset": null,
"barcode": null,
"base_amount": 65.0,
"base_net_amount": 65.0,
"base_net_rate": 65.0,
"base_price_list_rate": 0.0,
"base_rate": 65.0,
"base_rate_with_margin": 0.0,
"batch_no": null,
"brand": null,
"conversion_factor": 1.0,
"cost_center": "Main - _T",
"customer_item_code": null,
"deferred_revenue_account": null,
"delivered_by_supplier": 0,
"delivered_qty": 0.0,
"delivery_note": null,
"description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
"discount_amount": 0.0,
"discount_percentage": 0.0,
"dn_detail": null,
"enable_deferred_revenue": 0,
"expense_account": null,
"finance_book": null,
"image": null,
"income_account": "Sales - _T",
"incoming_rate": 0.0,
"is_fixed_asset": 0,
"is_free_item": 0,
"item_code": "",
"item_group": null,
"item_name": "Continental tyres",
"item_tax_rate": "{\"VAT on Sales - _T\": 5.0}",
"item_tax_template": null,
"margin_rate_or_amount": 0.0,
"margin_type": "",
"net_amount": 65.0,
"net_rate": 65.0,
"page_break": 0,
"parent": null,
"parentfield": "items",
"parenttype": "Sales Invoice",
"price_list_rate": 0.0,
"pricing_rules": null,
"project": null,
"qty": 1.0,
"quality_inspection": null,
"rate": 65.0,
"rate_with_margin": 0.0,
"sales_invoice_item": null,
"sales_order": null,
"serial_no": null,
"service_end_date": null,
"service_start_date": null,
"service_stop_date": null,
"so_detail": null,
"stock_qty": 1.0,
"stock_uom": null,
"stock_uom_rate": 65.0,
"target_warehouse": null,
"total_weight": 0.0,
"uom": "Nos",
"warehouse": null,
"weight_per_unit": 0.0,
"weight_uom": null
},
{
"actual_batch_qty": 0.0,
"actual_qty": 0.0,
"allow_zero_valuation_rate": 0,
"amount": 560.0,
"asset": null,
"barcode": null,
"base_amount": 560.0,
"base_net_amount": 560.0,
"base_net_rate": 70.0,
"base_price_list_rate": 0.0,
"base_rate": 70.0,
"base_rate_with_margin": 0.0,
"batch_no": null,
"brand": null,
"conversion_factor": 1.0,
"cost_center": "Main - _T",
"customer_item_code": null,
"deferred_revenue_account": null,
"delivered_by_supplier": 0,
"delivered_qty": 0.0,
"delivery_note": null,
"description": "<div class=\"ql-editor read-mode\"><p>New</p></div>",
"discount_amount": 0.0,
"discount_percentage": 0.0,
"dn_detail": null,
"enable_deferred_revenue": 0,
"expense_account": null,
"finance_book": null,
"image": null,
"income_account": "Sales - _T",
"incoming_rate": 0.0,
"is_fixed_asset": 0,
"is_free_item": 0,
"item_code": null,
"item_group": null,
"item_name": "Toyo tyres",
"item_tax_rate": "{\"VAT on Sales - _T\": 0.0}",
"item_tax_template": null,
"margin_rate_or_amount": 0.0,
"margin_type": "",
"net_amount": 560.0,
"net_rate": 70.0,
"page_break": 0,
"parent": null,
"parentfield": "items",
"parenttype": "Sales Invoice",
"price_list_rate": 0.0,
"pricing_rules": null,
"project": null,
"qty": 8.0,
"quality_inspection": null,
"rate": 70.0,
"rate_with_margin": 0.0,
"sales_invoice_item": null,
"sales_order": null,
"serial_no": null,
"service_end_date": null,
"service_start_date": null,
"service_stop_date": null,
"so_detail": null,
"stock_qty": 8.0,
"stock_uom": null,
"stock_uom_rate": 70.0,
"target_warehouse": null,
"total_weight": 0.0,
"uom": "Nos",
"warehouse": null,
"weight_per_unit": 0.0,
"weight_uom": null
}
],
"language": "en",
"letter_head": null,
"loyalty_amount": 0.0,
"loyalty_points": 0,
"loyalty_program": null,
"loyalty_redemption_account": null,
"loyalty_redemption_cost_center": null,
"modified": "2021-02-16 05:18:59.755144",
"name": null,
"naming_series": "ACC-SINV-.YYYY.-",
"net_total": 825.0,
"other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Sales</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Dunlop tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 200.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 40.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Continental tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 65.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(5.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 3.25\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Toyo tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 560.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(0.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 0.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
"outstanding_amount": 868.25,
"packed_items": [],
"paid_amount": 0.0,
"parent": null,
"parentfield": null,
"parenttype": null,
"party_account_currency": "GBP",
"payment_schedule": [],
"payment_terms_template": null,
"payments": [],
"plc_conversion_rate": 1.0,
"po_date": null,
"po_no": "",
"pos_profile": null,
"posting_date": null,
"posting_time": "5:19:02.994077",
"price_list_currency": "GBP",
"pricing_rules": [],
"project": null,
"redeem_loyalty_points": 0,
"remarks": "No Remarks",
"represents_company": "",
"return_against": null,
"rounded_total": 868.25,
"rounding_adjustment": 0.0,
"sales_partner": null,
"sales_team": [],
"scan_barcode": null,
"select_print_heading": null,
"selling_price_list": "Standard Selling",
"set_posting_time": 0,
"set_target_warehouse": null,
"set_warehouse": null,
"shipping_address": null,
"shipping_address_name": "",
"shipping_rule": null,
"source": null,
"status": "Overdue",
"tax_category": "",
"tax_id": null,
"taxes": [
{
"account_head": "VAT on Sales - _T",
"base_tax_amount": 43.25,
"base_tax_amount_after_discount_amount": 43.25,
"base_total": 868.25,
"charge_type": "On Net Total",
"cost_center": "Main - _T",
"description": "VAT on Sales",
"included_in_print_rate": 0,
"item_wise_tax_detail": "{\"Dunlop tyres\":[20.0,40.0],\"Continental tyres\":[5.0,3.25],\"Toyo tyres\":[0.0,0.0]}",
"parent": null,
"parentfield": "taxes",
"parenttype": "Sales Invoice",
"rate": 0.0,
"row_id": null,
"tax_amount": 43.25,
"tax_amount_after_discount_amount": 43.25,
"total": 868.25
}
],
"taxes_and_charges": null,
"tc_name": null,
"terms": null,
"territory": "All Territories",
"timesheets": [],
"title": "_Sales Invoice",
"to_date": null,
"total": 825.0,
"total_advance": 0.0,
"total_billing_amount": 0.0,
"total_commission": 0.0,
"total_net_weight": 0.0,
"total_qty": 13.0,
"total_taxes_and_charges": 43.25,
"unrealized_profit_loss_account": null,
"update_billed_amount_in_sales_order": 0,
"update_stock": 0,
"write_off_account": null,
"write_off_amount": 0.0,
"write_off_cost_center": null,
"write_off_outstanding_amount_automatically": 0
}
]

View File

@ -1,213 +0,0 @@
import datetime
import json
import os
import unittest
import frappe
from frappe.utils import (
add_to_date,
get_first_day,
get_last_day,
get_year_ending,
get_year_start,
getdate,
)
from .tax_detail import filter_match, save_custom_report
class TestTaxDetail(unittest.TestCase):
def load_testdocs(self):
from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
datapath, _ = os.path.splitext(os.path.realpath(__file__))
with open(datapath + ".json", "r") as fp:
docs = json.load(fp)
now = getdate()
self.from_date = get_first_day(now)
self.to_date = get_last_day(now)
try:
get_fiscal_year(now, company="_T")
except FiscalYearError:
docs = [
{
"companies": [
{
"company": "_T",
"parent": "_Test Fiscal",
"parentfield": "companies",
"parenttype": "Fiscal Year",
}
],
"doctype": "Fiscal Year",
"year": "_Test Fiscal",
"year_end_date": get_year_ending(now),
"year_start_date": get_year_start(now),
}
] + docs
docs = [
{
"abbr": "_T",
"company_name": "_T",
"country": "United Kingdom",
"default_currency": "GBP",
"doctype": "Company",
"name": "_T",
}
] + docs
for doc in docs:
try:
db_doc = frappe.get_doc(doc)
if "Invoice" in db_doc.doctype:
db_doc.due_date = add_to_date(now, days=1)
db_doc.insert()
# Create GL Entries:
db_doc.submit()
else:
db_doc.insert(ignore_if_duplicate=True)
except frappe.exceptions.DuplicateEntryError:
pass
def load_defcols(self):
self.company = frappe.get_doc("Company", "_T")
custom_report = frappe.get_doc("Report", "Tax Detail")
self.default_columns, _ = custom_report.run_query_report(
filters={
"from_date": "2021-03-01",
"to_date": "2021-03-31",
"company": self.company.name,
"mode": "run",
"report_name": "Tax Detail",
},
user=frappe.session.user,
)
def rm_testdocs(self):
"Remove the Company and all data"
from erpnext.setup.doctype.company.company import create_transaction_deletion_request
create_transaction_deletion_request(self.company.name)
def test_report(self):
self.load_testdocs()
self.load_defcols()
report_name = save_custom_report(
"Tax Detail",
"_Test Tax Detail",
json.dumps(
{
"columns": self.default_columns,
"sections": {
"Box1": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Sales"}}},
"Box2": {"Filter0": {"type": "filter", "filters": {"4": "Acquisition"}}},
"Box3": {"Box1": {"type": "section"}, "Box2": {"type": "section"}},
"Box4": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Purchases"}}},
"Box5": {"Box3": {"type": "section"}, "Box4": {"type": "section"}},
"Box6": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales"}}},
"Box7": {"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax"}}},
"Box8": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales", "12": "EU"}}},
"Box9": {
"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax", "12": "EU"}}
},
},
"show_detail": 1,
}
),
)
data = frappe.desk.query_report.run(
report_name,
filters={
"from_date": self.from_date,
"to_date": self.to_date,
"company": self.company.name,
"mode": "run",
"report_name": report_name,
},
user=frappe.session.user,
)
self.assertListEqual(data.get("columns"), self.default_columns)
expected = (
("Box1", 43.25),
("Box2", 0.0),
("Box3", 43.25),
("Box4", -85.28),
("Box5", -42.03),
("Box6", 825.0),
("Box7", -426.40),
("Box8", 0.0),
("Box9", 0.0),
)
exrow = iter(expected)
for row in data.get("result"):
if row.get("voucher_no") and not row.get("posting_date"):
label, value = next(exrow)
self.assertDictEqual(row, {"voucher_no": label, "amount": value})
self.assertListEqual(
data.get("report_summary"),
[{"label": label, "datatype": "Currency", "value": value} for label, value in expected],
)
self.rm_testdocs()
def test_filter_match(self):
# None - treated as -inf number except range
self.assertTrue(filter_match(None, "!="))
self.assertTrue(filter_match(None, "<"))
self.assertTrue(filter_match(None, "<jjj"))
self.assertTrue(filter_match(None, " : "))
self.assertTrue(filter_match(None, ":56"))
self.assertTrue(filter_match(None, ":de"))
self.assertFalse(filter_match(None, "3.4"))
self.assertFalse(filter_match(None, "="))
self.assertFalse(filter_match(None, "=3.4"))
self.assertFalse(filter_match(None, ">3.4"))
self.assertFalse(filter_match(None, " <"))
self.assertFalse(filter_match(None, "ew"))
self.assertFalse(filter_match(None, " "))
self.assertFalse(filter_match(None, " f :"))
# Numbers
self.assertTrue(filter_match(3.4, "3.4"))
self.assertTrue(filter_match(3.4, ".4"))
self.assertTrue(filter_match(3.4, "3"))
self.assertTrue(filter_match(-3.4, "< -3"))
self.assertTrue(filter_match(-3.4, "> -4"))
self.assertTrue(filter_match(3.4, "= 3.4 "))
self.assertTrue(filter_match(3.4, "!=4.5"))
self.assertTrue(filter_match(3.4, " 3 : 4 "))
self.assertTrue(filter_match(0.0, " : "))
self.assertFalse(filter_match(3.4, "=4.5"))
self.assertFalse(filter_match(3.4, " = 3.4 "))
self.assertFalse(filter_match(3.4, "!=3.4"))
self.assertFalse(filter_match(3.4, ">6"))
self.assertFalse(filter_match(3.4, "<-4.5"))
self.assertFalse(filter_match(3.4, "4.5"))
self.assertFalse(filter_match(3.4, "5:9"))
# Strings
self.assertTrue(filter_match("ACC-SINV-2021-00001", "SINV"))
self.assertTrue(filter_match("ACC-SINV-2021-00001", "sinv"))
self.assertTrue(filter_match("ACC-SINV-2021-00001", "-2021"))
self.assertTrue(filter_match(" ACC-SINV-2021-00001", " acc"))
self.assertTrue(filter_match("ACC-SINV-2021-00001", "=2021"))
self.assertTrue(filter_match("ACC-SINV-2021-00001", "!=zz"))
self.assertTrue(filter_match("ACC-SINV-2021-00001", "< zzz "))
self.assertTrue(filter_match("ACC-SINV-2021-00001", " : sinv "))
self.assertFalse(filter_match("ACC-SINV-2021-00001", " sinv :"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", " acc"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", "= 2021 "))
self.assertFalse(filter_match("ACC-SINV-2021-00001", "!=sinv"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", " >"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", ">aa"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", " <"))
self.assertFalse(filter_match("ACC-SINV-2021-00001", "< "))
self.assertFalse(filter_match("ACC-SINV-2021-00001", " ="))
self.assertFalse(filter_match("ACC-SINV-2021-00001", "="))
# Date - always match
self.assertTrue(filter_match(datetime.date(2021, 3, 19), " kdsjkldfs "))

View File

@ -4,7 +4,8 @@
import frappe
from frappe import _
from frappe.utils import cstr, flt, formatdate, getdate
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, cstr, flt, formatdate, getdate
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@ -137,45 +138,125 @@ def get_opening_balances(filters):
def get_rootwise_opening_balances(filters, report_type):
additional_conditions = ""
if not filters.show_unclosed_fy_pl_balances:
additional_conditions = (
" and posting_date >= %(year_start_date)s" if report_type == "Profit and Loss" else ""
)
gle = []
if not flt(filters.with_period_closing_entry):
additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
if filters.cost_center:
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
additional_conditions += """ and cost_center in (select name from `tabCost Center`
where lft >= %s and rgt <= %s)""" % (
lft,
rgt,
)
if filters.project:
additional_conditions += " and project = %(project)s"
if filters.get("include_default_book_entries"):
additional_conditions += (
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
)
else:
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
fields=["posting_date", "name"],
order_by="posting_date desc",
limit=1,
)
accounting_dimensions = get_accounting_dimensions(as_list=False)
query_filters = {
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
"report_type": report_type,
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
"company_fb": frappe.get_cached_value("Company", filters.company, "default_finance_book"),
}
if last_period_closing_voucher:
gle = get_opening_balance(
"Account Closing Balance",
filters,
report_type,
accounting_dimensions,
period_closing_voucher=last_period_closing_voucher[0].name,
)
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
add_days(filters.from_date, -1)
):
start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
gle += get_opening_balance(
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
)
else:
gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
opening = frappe._dict()
for d in gle:
opening.setdefault(
d.account,
{
"account": d.account,
"opening_debit": 0.0,
"opening_credit": 0.0,
},
)
opening[d.account]["opening_debit"] += flt(d.opening_debit)
opening[d.account]["opening_credit"] += flt(d.opening_credit)
return opening
def get_opening_balance(
doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
):
closing_balance = frappe.qb.DocType(doctype)
account = frappe.qb.DocType("Account")
opening_balance = (
frappe.qb.from_(closing_balance)
.select(
closing_balance.account,
Sum(closing_balance.debit).as_("opening_debit"),
Sum(closing_balance.credit).as_("opening_credit"),
)
.where(
(closing_balance.company == filters.company)
& (
closing_balance.account.isin(
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
)
)
)
.groupby(closing_balance.account)
)
if period_closing_voucher:
opening_balance = opening_balance.where(
closing_balance.period_closing_voucher == period_closing_voucher
)
else:
if start_date:
opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
if (
not filters.show_unclosed_fy_pl_balances
and report_type == "Profit and Loss"
and doctype == "GL Entry"
):
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
if not flt(filters.with_period_closing_entry):
if doctype == "Account Closing Balance":
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
else:
opening_balance = opening_balance.where(
closing_balance.voucher_type != "Period Closing Voucher"
)
if filters.cost_center:
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
cost_center = frappe.qb.DocType("Cost Center")
opening_balance = opening_balance.where(
closing_balance.cost_center.in_(
frappe.qb.from_(cost_center)
.select("name")
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
)
)
if filters.project:
opening_balance = opening_balance.where(closing_balance.project == filters.project)
if filters.get("include_default_book_entries"):
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:
@ -184,35 +265,17 @@ def get_rootwise_opening_balances(filters, report_type):
filters[dimension.fieldname] = get_dimension_with_children(
dimension.document_type, filters.get(dimension.fieldname)
)
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
opening_balance = opening_balance.where(
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
)
else:
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
opening_balance = opening_balance.where(
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
)
query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
gle = opening_balance.run(as_dict=1)
gle = frappe.db.sql(
"""
select
account, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
where
company=%(company)s
{additional_conditions}
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
and account in (select name from `tabAccount` where report_type=%(report_type)s)
and is_cancelled = 0
group by account""".format(
additional_conditions=additional_conditions
),
query_filters,
as_dict=True,
)
opening = frappe._dict()
for d in gle:
opening.setdefault(d.account, d)
return opening
return gle
def calculate_values(accounts, gl_entries_by_account, opening_balances):

View File

@ -30,10 +30,6 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
("Sales Register", {}),
("Sales Register", {"item_group": "All Item Groups"}),
("Purchase Register", {}),
(
"Tax Detail",
{"mode": "run", "report_name": "Tax Detail"},
),
]
OPTIONAL_FILTERS = {}

View File

@ -51,13 +51,25 @@ GL_REPOSTING_CHUNK = 100
@frappe.whitelist()
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
):
return get_fiscal_years(date, fiscal_year, label, verbose, company, as_dict=as_dict)[0]
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
)
if boolean:
return fiscal_years
else:
return fiscal_years[0]
def get_fiscal_years(
transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
transaction_date=None,
fiscal_year=None,
label="Date",
verbose=1,
company=None,
as_dict=False,
boolean=False,
):
fiscal_years = frappe.cache().hget("fiscal_years", company) or []
@ -121,8 +133,12 @@ def get_fiscal_years(
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
if boolean:
return False
if verbose == 1:
frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)

View File

@ -13,6 +13,7 @@
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
"is_hidden": 0,
"label": "Accounting",
"links": [
{
@ -493,17 +494,6 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Tax Detail",
"link_count": 0,
"link_to": "Tax Detail",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
@ -516,18 +506,6 @@
"only_for": "United Arab Emirates",
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "KSA VAT Report",
"link_count": 0,
"link_to": "KSA VAT",
"link_type": "Report",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -1029,17 +1007,6 @@
"only_for": "India",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "KSA VAT Setting",
"link_count": 0,
"link_to": "KSA VAT Setting",
"link_type": "DocType",
"onboard": 0,
"only_for": "Saudi Arabia",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -1093,7 +1060,7 @@
"type": "Link"
}
],
"modified": "2022-06-24 05:41:09.236458",
"modified": "2023-02-23 15:32:12.135355",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",

View File

@ -474,17 +474,21 @@ class Asset(AccountsController):
@erpnext.allow_regional
def get_depreciation_amount(self, depreciable_value, fb_row):
if fb_row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not self.flags.increase_in_asset_life:
depreciation_amount = (
flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
) / flt(fb_row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if self.flags.increase_in_asset_life:
depreciation_amount = (
flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
) / (date_diff(self.to_date, self.available_for_use_date) / 365)
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif self.flags.increase_in_asset_value_due_to_repair:
depreciation_amount = (
flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
) / flt(fb_row.total_number_of_depreciations)
# if the Depreciation Schedule is being prepared for the first time
else:
depreciation_amount = (
flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
) / flt(fb_row.total_number_of_depreciations)
else:
depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
@ -617,11 +621,22 @@ class Asset(AccountsController):
return 200.0 / args.get("total_number_of_depreciations")
if args.get("depreciation_method") == "Written Down Value":
if args.get("rate_of_depreciation") and on_validate:
if (
args.get("rate_of_depreciation")
and on_validate
and not self.flags.increase_in_asset_value_due_to_repair
):
return args.get("rate_of_depreciation")
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
if self.flags.increase_in_asset_value_due_to_repair:
value = flt(args.get("expected_value_after_useful_life")) / flt(
args.get("value_after_depreciation")
)
else:
value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
return flt((100 * (1 - depreciation_rate)), float_precision)
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):

View File

@ -460,6 +460,16 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
"Written Down Value",
"Double Declining Balance",
):
new_rate_of_depreciation = flt(
asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation")
)
row.rate_of_depreciation = new_rate_of_depreciation
new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation
new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)

View File

@ -43,53 +43,57 @@ class AssetRepair(AccountsController):
def before_submit(self):
self.check_repair_status()
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.increase_asset_value()
if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
if self.get("capitalize_repair_cost"):
self.make_gl_entries()
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
self.modify_depreciation_schedule()
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
notes = _(
"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
).format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save()
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
self.increase_asset_value()
if self.get("stock_consumption"):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
if self.get("capitalize_repair_cost"):
self.make_gl_entries()
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.modify_depreciation_schedule()
notes = _(
"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
).format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save()
def before_cancel(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.decrease_asset_value()
if self.get("stock_consumption"):
self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
self.db_set("stock_entry", None)
if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
self.revert_depreciation_schedule_on_cancellation()
self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save()
if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
self.decrease_asset_value()
if self.get("stock_consumption"):
self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
self.db_set("stock_entry", None)
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.revert_depreciation_schedule_on_cancellation()
notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
self.asset_doc.save()
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()

View File

@ -125,18 +125,9 @@ class Supplier(TransactionBase):
def on_trash(self):
if self.supplier_primary_contact:
frappe.db.sql(
"""
UPDATE `tabSupplier`
SET
supplier_primary_contact=null,
supplier_primary_address=null,
mobile_no=null,
email_id=null,
primary_address=null
WHERE name=%(name)s""",
{"name": self.name},
)
self.db_set("supplier_primary_contact", None)
if self.supplier_primary_address:
self.db_set("supplier_primary_address", None)
delete_contact_and_address("Supplier", self.name)

View File

@ -1,5 +1,5 @@
import frappe
from frappe.utils import cint
from frappe.utils import cint, flt
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
get_shopping_cart_settings,
@ -166,6 +166,27 @@ def get_next_attribute_and_values(item_code, selected_attributes):
else:
product_info = None
product_id = ""
website_warehouse = ""
if exact_match or filtered_items:
if exact_match and len(exact_match) == 1:
product_id = exact_match[0]
elif filtered_items_count == 1:
product_id = list(filtered_items)[0]
if product_id:
website_warehouse = frappe.get_cached_value(
"Website Item", {"item_code": product_id}, "website_warehouse"
)
available_qty = 0.0
if website_warehouse:
available_qty = flt(
frappe.db.get_value(
"Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty"
)
)
return {
"next_attribute": next_attribute,
"valid_options_for_attributes": valid_options_for_attributes,
@ -173,6 +194,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
"filtered_items": filtered_items if filtered_items_count < 10 else [],
"exact_match": exact_match,
"product_info": product_info,
"available_qty": available_qty,
}

View File

@ -275,7 +275,7 @@ has_website_permission = {
before_tests = "erpnext.setup.utils.before_tests"
standard_queries = {
"Customer": "erpnext.selling.doctype.customer.customer.get_customer_list",
"Customer": "erpnext.controllers.queries.customer_query",
}
doc_events = {
@ -510,6 +510,7 @@ accounting_dimension_doctypes = [
"Subcontracting Order Item",
"Subcontracting Receipt",
"Subcontracting Receipt Item",
"Account Closing Balance",
]
# get matching queries for Bank Reconciliation

View File

@ -83,7 +83,7 @@ frappe.ui.form.on('Job Card', {
// and if stock mvt for WIP is required
if (frm.doc.work_order) {
frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => {
if (result.skip_transfer === 1 || result.status == 'In Process') {
if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) {
frm.trigger("prepare_timer_buttons");
}
});

View File

@ -325,5 +325,8 @@ erpnext.patches.v14_0.set_pick_list_status
erpnext.patches.v13_0.update_docs_link
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
erpnext.patches.v14_0.update_closing_balances
# below migration patches should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
execute:frappe.delete_doc_if_exists("Report", "Tax Detail")

View File

@ -0,0 +1,31 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
doctype = "Account Closing Balance"
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@ -0,0 +1,33 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
make_closing_entries,
)
from erpnext.accounts.utils import get_fiscal_year
def execute():
company_wise_order = {}
get_opening_entries = True
for pcv in frappe.db.get_all(
"Period Closing Voucher",
fields=["company", "posting_date", "name"],
filters={"docstatus": 1},
order_by="posting_date",
):
company_wise_order.setdefault(pcv.company, [])
if pcv.posting_date not in company_wise_order[pcv.company]:
pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
pcv_doc.year_start_date = get_fiscal_year(
pcv.posting_date, pcv.fiscal_year, company=pcv.company
)[1]
gl_entries = pcv_doc.get_gl_entries()
closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
company_wise_order[pcv.company].append(pcv.posting_date)
get_opening_entries = False

View File

@ -391,14 +391,14 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
fieldname: "deposit",
fieldtype: "Currency",
label: "Deposit",
options: "currency",
options: "account_currency",
read_only: 1,
},
{
fieldname: "withdrawal",
fieldtype: "Currency",
label: "Withdrawal",
options: "currency",
options: "account_currency",
read_only: 1,
},
{
@ -416,18 +416,18 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
fieldname: "allocated_amount",
fieldtype: "Currency",
label: "Allocated Amount",
options: "Currency",
options: "account_currency",
read_only: 1,
},
{
fieldname: "unallocated_amount",
fieldtype: "Currency",
label: "Unallocated Amount",
options: "Currency",
options: "account_currency",
read_only: 1,
},
{
fieldname: "currency",
fieldname: "account_currency",
fieldtype: "Link",
label: "Currency",
options: "Currency",

View File

@ -135,7 +135,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
else {
// allow for '0' qty on Credit/Debit notes
let qty = item.qty || -1
let qty = item.qty || me.frm.doc.is_debit_note ? 1 : -1;
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
}

View File

@ -17,7 +17,7 @@
{{ frappe.avatar(notes[i].added_by) }}
</div>
<div class="col-xs-10">
<div class="mr-2 title font-weight-bold">
<div class="mr-2 title font-weight-bold ellipsis" title="{{ strip_html(notes[i].added_by) }}">
{{ strip_html(notes[i].added_by) }}
</div>
<div class="time small text-muted">

View File

@ -272,18 +272,9 @@ class Customer(TransactionBase):
def on_trash(self):
if self.customer_primary_contact:
frappe.db.sql(
"""
UPDATE `tabCustomer`
SET
customer_primary_contact=null,
customer_primary_address=null,
mobile_no=null,
email_id=null,
primary_address=null
WHERE name=%(name)s""",
{"name": self.name},
)
self.db_set("customer_primary_contact", None)
if self.customer_primary_address:
self.db_set("customer_primary_address", None)
delete_contact_and_address("Customer", self.name)
if self.lead_name:
@ -457,8 +448,14 @@ def get_nested_links(link_doctype, link_name, ignore_permissions=False):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from frappe.utils.deprecations import deprecation_warning
from erpnext.controllers.queries import get_fields
deprecation_warning(
"`get_customer_list` is deprecated and will be removed in version 15. Use `erpnext.controllers.queries.customer_query` instead."
)
fields = ["name", "customer_name", "customer_group", "territory"]
if frappe.db.get_default("cust_master_name") == "Customer Name":

View File

@ -304,6 +304,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
fieldname: "alternative_items",
fieldtype: "Table",
cannot_add_rows: true,
cannot_delete_rows: true,
in_place_edit: true,
reqd: 1,
data: this.data,
@ -330,7 +331,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
dialog.fields_dict.info.$wrapper.html(
`<p class="small text-muted">
<span class="indicator yellow"></span>
Alternative Items
${__("Alternative Items")}
</p>`
)
dialog.show();

View File

@ -55,7 +55,7 @@ def search_by_term(search_term, warehouse, price_list):
)
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
item_stock_qty = item_stock_qty // item.get("conversion_factor")
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
item.update({"actual_qty": item_stock_qty})
price = frappe.get_list(
@ -63,8 +63,9 @@ def search_by_term(search_term, warehouse, price_list):
filters={
"price_list": price_list,
"item_code": item_code,
"batch_no": batch_no,
},
fields=["uom", "stock_uom", "currency", "price_list_rate"],
fields=["uom", "stock_uom", "currency", "price_list_rate", "batch_no"],
)
def __sort(p):
@ -167,7 +168,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
item_price = frappe.get_all(
"Item Price",
fields=["price_list_rate", "currency", "uom"],
fields=["price_list_rate", "currency", "uom", "batch_no"],
filters={
"price_list": price_list,
"item_code": item.item_code,
@ -190,9 +191,9 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
"price_list_rate": price.get("price_list_rate"),
"currency": price.get("currency"),
"uom": price.uom or item.uom,
"batch_no": price.batch_no,
}
)
return {"items": result}

View File

@ -44,20 +44,30 @@ def get_data(filters, period_list, partner_doctype):
if not sales_users_data:
return
sales_users, item_groups = [], []
sales_users = []
sales_user_wise_item_groups = {}
for d in sales_users_data:
if d.parent not in sales_users:
sales_users.append(d.parent)
if d.item_group not in item_groups:
item_groups.append(d.item_group)
sales_user_wise_item_groups.setdefault(d.parent, [])
if d.item_group:
sales_user_wise_item_groups[d.parent].append(d.item_group)
date_field = "transaction_date" if filters.get("doctype") == "Sales Order" else "posting_date"
actual_data = get_actual_data(filters, item_groups, sales_users, date_field, sales_field)
actual_data = get_actual_data(filters, sales_users, date_field, sales_field)
return prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field)
return prepare_data(
filters,
sales_users_data,
sales_user_wise_item_groups,
actual_data,
date_field,
period_list,
sales_field,
)
def get_columns(filters, period_list, partner_doctype):
@ -142,7 +152,15 @@ def get_columns(filters, period_list, partner_doctype):
return columns
def prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field):
def prepare_data(
filters,
sales_users_data,
sales_user_wise_item_groups,
actual_data,
date_field,
period_list,
sales_field,
):
rows = {}
target_qty_amt_field = "target_qty" if filters.get("target_on") == "Quantity" else "target_amount"
@ -173,9 +191,9 @@ def prepare_data(filters, sales_users_data, actual_data, date_field, period_list
for r in actual_data:
if (
r.get(sales_field) == d.parent
and r.item_group == d.item_group
and period.from_date <= r.get(date_field)
and r.get(date_field) <= period.to_date
and (not sales_user_wise_item_groups.get(d.parent) or r.item_group == d.item_group)
):
details[p_key] += r.get(qty_or_amount_field, 0)
details[variance_key] = details.get(p_key) - details.get(target_key)
@ -186,7 +204,7 @@ def prepare_data(filters, sales_users_data, actual_data, date_field, period_list
return rows
def get_actual_data(filters, item_groups, sales_users_or_territory_data, date_field, sales_field):
def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field):
fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1)
dates = [fiscal_year.year_start_date, fiscal_year.year_end_date]
@ -213,7 +231,6 @@ def get_actual_data(filters, item_groups, sales_users_or_territory_data, date_fi
WHERE
`tab{child_doc}`.parent = `tab{parent_doc}`.name
and `tab{parent_doc}`.docstatus = 1 and {cond}
and `tab{child_doc}`.item_group in ({item_groups})
and `tab{parent_doc}`.{date_field} between %s and %s""".format(
cond=cond,
date_field=date_field,
@ -221,9 +238,8 @@ def get_actual_data(filters, item_groups, sales_users_or_territory_data, date_fi
child_table=child_table,
parent_doc=filters.get("doctype"),
child_doc=filters.get("doctype") + " Item",
item_groups=",".join(["%s"] * len(item_groups)),
),
tuple(sales_users_or_territory_data + item_groups + dates),
tuple(sales_users_or_territory_data + dates),
as_dict=1,
)

View File

@ -8,6 +8,4 @@ from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.it
def execute(filters=None):
data = []
return get_data_column(filters, "Sales Person")

View File

@ -186,14 +186,14 @@ class ItemConfigure {
this.dialog.$status_area.empty();
}
get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) {
get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, available_qty, settings }) {
const one_item = exact_match.length === 1
? exact_match[0]
: filtered_items_count === 1
? filtered_items[0]
: '';
const item_add_to_cart = one_item ? `
let item_add_to_cart = one_item ? `
<button data-item-code="${one_item}"
class="btn btn-primary btn-add-to-cart w-100"
data-action="btn_add_to_cart"
@ -218,6 +218,9 @@ class ItemConfigure {
? '(' + product_info.price.formatted_price_sales_uom + ')'
: ''
}
${available_qty === 0 ? '<span class="text-danger">(' + __('Out of Stock') + ')</span>' : ''}
</div></div>
<a href data-action="btn_clear_values" data-item-code="${one_item}">
${__('Clear Values')}
@ -233,6 +236,10 @@ class ItemConfigure {
</div>`;
/* eslint-disable indent */
if (!product_info?.allow_items_not_in_stock && available_qty === 0) {
item_add_to_cart = '';
}
return `
${item_found_status}
${item_add_to_cart}
@ -257,12 +264,15 @@ class ItemConfigure {
btn_clear_values() {
this.dialog.fields_list.forEach(f => {
f.df.options = f.df.options.map(option => {
option.disabled = false;
return option;
});
if (f.df?.options) {
f.df.options = f.df.options.map(option => {
option.disabled = false;
return option;
});
}
});
this.dialog.clear();
this.dialog.$status_area.empty();
this.on_attribute_selection();
}

View File

@ -3505,7 +3505,6 @@ Recipient,ontvanger,
Reviews,resensies,
Sender,sender,
Shop,Winkel,
Sign Up,Teken aan,
Subsidiary,filiaal,
There is some problem with the file url: {0},Daar is &#39;n probleem met die lêer url: {0},
There were errors while sending email. Please try again.,Daar was foute tydens die stuur van e-pos. Probeer asseblief weer.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,ተቀባይ,
Reviews,ግምገማዎች,
Sender,የላኪ,
Shop,ሱቅ,
Sign Up,ክፈት,
Subsidiary,ተጪማሪ,
There is some problem with the file url: {0},ፋይል ዩ አር ኤል ጋር አንድ ችግር አለ: {0},
There were errors while sending email. Please try again.,ኢሜይል በመላክ ላይ ሳለ ስህተቶች ነበሩ. እባክዎ ዳግም ይሞክሩ.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,مستلم,
Reviews,التعليقات,
Sender,مرسل,
Shop,تسوق,
Sign Up,سجل,
Subsidiary,شركة فرعية,
There is some problem with the file url: {0},هناك بعض المشاكل مع رابط الملف: {0},
There were errors while sending email. Please try again.,كانت هناك أخطاء أثناء إرسال البريد الإلكتروني. يرجى المحاولة مرة أخرى.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Получател,
Reviews,Отзиви,
Sender,Подател,
Shop,Магазин,
Sign Up,Регистрирай се,
Subsidiary,Филиал,
There is some problem with the file url: {0},Има някакъв проблем с адреса на файл: {0},
There were errors while sending email. Please try again.,"Имаше грешки при изпращане на имейл. Моля, опитайте отново.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,প্রাপক,
Reviews,পর্যালোচনা,
Sender,প্রেরকের,
Shop,দোকান,
Sign Up,নিবন্ধন করুন,
Subsidiary,সহায়ক,
There is some problem with the file url: {0},ফাইলের URL সঙ্গে কিছু সমস্যা আছে: {0},
There were errors while sending email. Please try again.,ইমেইল পাঠানোর সময় কিছু সমস্যা হয়েছে. অনুগ্রহ করে আবার চেষ্টা করুন.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Primalac,
Reviews,Recenzije,
Sender,Pošiljaoc,
Shop,Prodavnica,
Sign Up,Prijaviti se,
Subsidiary,Podružnica,
There is some problem with the file url: {0},Postoji neki problem sa URL datoteku: {0},
There were errors while sending email. Please try again.,Bilo je grešaka tijekom slanja e-pošte. Molimo pokušajte ponovno .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Receptor,
Reviews,Ressenyes,
Sender,Remitent,
Shop,Botiga,
Sign Up,Registra&#39;t,
Subsidiary,Filial,
There is some problem with the file url: {0},Hi ha una mica de problema amb la url de l&#39;arxiu: {0},
There were errors while sending email. Please try again.,"Hi ha hagut errors a l'enviar el correu electrònic. Si us plau, torna a intentar-ho.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Příjemce,
Reviews,Recenze,
Sender,Odesilatel,
Shop,Obchod,
Sign Up,Přihlásit se,
Subsidiary,Dceřiný,
There is some problem with the file url: {0},Tam je nějaký problém s URL souboru: {0},
There were errors while sending email. Please try again.,Narazili jsme na problémy při odesílání emailu. Prosím zkuste to znovu.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Modtager,
Reviews,Anmeldelser,
Sender,Afsender,
Shop,Butik,
Sign Up,Tilmelde,
Subsidiary,Datterselskab,
There is some problem with the file url: {0},Der er nogle problemer med filen url: {0},
There were errors while sending email. Please try again.,Der var fejl under afsendelse af e-mail. Prøv venligst igen.,

Can't render this file because it is too large.

View File

@ -2012,30 +2012,27 @@ Please identify/create Account (Ledger) for type - {0},Bitte identifizieren / er
Please login as another user to register on Marketplace,"Bitte melden Sie sich als anderer Benutzer an, um sich auf dem Marktplatz zu registrieren",
Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.,"Bitte sicher stellen, dass wirklich alle Transaktionen dieses Unternehmens gelöscht werden sollen. Die Stammdaten bleiben bestehen. Diese Aktion kann nicht rückgängig gemacht werden.",
Please mention Basic and HRA component in Company,Bitte erwähnen Sie die Basis- und HRA-Komponente in der Firma,
Please mention Round Off Account in Company,Bitte Abschlusskonto in Unternehmen vermerken,
Please mention Round Off Cost Center in Company,Bitte Abschlusskostenstelle in Unternehmen vermerken,
Please mention no of visits required,"Bitte bei ""Besuche erforderlich"" NEIN angeben",
Please mention the Lead Name in Lead {0},Bitte erwähnen Sie den Lead Name in Lead {0},
Please pull items from Delivery Note,Bitte Artikel vom Lieferschein nehmen,
Please mention Round Off Account in Company,Bitte ein Standardkonto Konto für Rundungsdifferenzen in Unternehmen einstellen,
Please mention Round Off Cost Center in Company,Bitte eine Kostenstelle für Rundungsdifferenzen in Unternehmen einstellen,
Please mention no of visits required,Bitte die Anzahl der benötigten Wartungsbesuche angeben,
Please pull items from Delivery Note,Bitte Artikel aus dem Lieferschein ziehen,
Please register the SIREN number in the company information file,Bitte registrieren Sie die SIREN-Nummer in der Unternehmensinformationsdatei,
Please remove this Invoice {0} from C-Form {1},Bitte diese Rechnung {0} vom Kontaktformular {1} entfernen,
Please save the patient first,Bitte speichern Sie den Patienten zuerst,
Please save the report again to rebuild or update,"Speichern Sie den Bericht erneut, um ihn neu zu erstellen oder zu aktualisieren",
"Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row","Bitte zugewiesenen Betrag, Rechnungsart und Rechnungsnummer in mindestens einer Zeile auswählen",
Please select Apply Discount On,"Bitte ""Rabatt anwenden auf"" auswählen",
Please select BOM against item {0},Bitte wählen Sie Stückliste gegen Artikel {0},
Please select BOM for Item in Row {0},Bitte Stückliste für Artikel in Zeile {0} auswählen,
Please select BOM in BOM field for Item {0},Bitte aus dem Stücklistenfeld eine Stückliste für Artikel {0} auswählen,
Please select Category first,Bitte zuerst Kategorie auswählen,
Please select Charge Type first,Bitte zuerst Chargentyp auswählen,
Please select Company,Bitte Unternehmen auswählen,
Please select BOM against item {0},Bitte eine Stückliste für Artikel {0} auswählen,
Please select BOM for Item in Row {0},Bitte eine Stückliste für den Artikel in Zeile {0} auswählen,
Please select BOM in BOM field for Item {0},Bitte im Stücklistenfeld eine Stückliste für Artikel {0} auswählen,
Please select Category first,Bitte zuerst eine Kategorie auswählen,
Please select Charge Type first,Bitte zuerst einen Chargentyp auswählen,
Please select Company,Bitte ein Unternehmen auswählen,
Please select Company and Designation,Bitte wählen Sie Unternehmen und Position,
Please select Company and Posting Date to getting entries,"Bitte wählen Sie Unternehmen und Buchungsdatum, um Einträge zu erhalten",
Please select Company first,Bitte zuerst Unternehmen auswählen,
Please select Completion Date for Completed Asset Maintenance Log,Bitte wählen Sie Fertigstellungsdatum für das abgeschlossene Wartungsprotokoll für den Vermögenswert,
Please select Completion Date for Completed Repair,Bitte wählen Sie das Abschlussdatum für die abgeschlossene Reparatur,
Please select Course,Bitte wählen Sie Kurs,
Please select Drug,Bitte wählen Sie Arzneimittel,
Please select Employee,Bitte wählen Sie Mitarbeiter,
Please select Existing Company for creating Chart of Accounts,Bitte wählen Sie Bestehende Unternehmen für die Erstellung von Konten,
Please select Healthcare Service,Bitte wählen Sie Gesundheitsdienst,
@ -3514,7 +3511,6 @@ Recipient,Empfänger,
Reviews,Bewertungen,
Sender,Absender,
Shop,Laden,
Sign Up,Anmelden,
Subsidiary,Tochtergesellschaft,
There is some problem with the file url: {0},Es gibt irgend ein Problem mit der Datei-URL: {0},
There were errors while sending email. Please try again.,Beim Versand der E-Mail ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.,
@ -5067,7 +5063,7 @@ Price List Rate (Company Currency),Preisliste (Unternehmenswährung),
Rate ,Preis,
Rate (Company Currency),Preis (Unternehmenswährung),
Amount (Company Currency),Betrag (Unternehmenswährung),
Is Free Item,Ist freies Einzelteil,
Is Free Item,Ist kostenlos,
Net Rate,Nettopreis,
Net Rate (Company Currency),Nettopreis (Unternehmenswährung),
Net Amount (Company Currency),Nettobetrag (Unternehmenswährung),
@ -7805,7 +7801,7 @@ Default Employee Advance Account,Standardkonto für Vorschüsse an Arbeitnehmer,
Default Cost of Goods Sold Account,Standard-Herstellkosten,
Default Income Account,Standard-Ertragskonto,
Default Deferred Revenue Account,Standardkonto für passive Rechnungsabgrenzung,
Default Deferred Expense Account,Standard-Rechnungsabgrenzungsposten,
Default Deferred Expense Account,Standardkonto für aktive Rechnungsabgrenzung,
Default Payroll Payable Account,Standardkonto für Verbindlichkeiten aus Lohn und Gehalt,
Default Expense Claim Payable Account,Standard-Expense Claim Zahlbares Konto,
Stock Settings,Lager-Einstellungen,
@ -8873,7 +8869,7 @@ Add Topic to Courses,Hinzufügen eines Themas zu Kursen,
This topic is already added to the existing courses,Dieses Thema wurde bereits zu den bestehenden Kursen hinzugefügt,
"If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order","Wenn Shopify keinen Kunden in der Bestellung hat, berücksichtigt das System beim Synchronisieren der Bestellungen den Standardkunden für die Bestellung",
The accounts are set by the system automatically but do confirm these defaults,"Die Konten werden vom System automatisch festgelegt, bestätigen jedoch diese Standardeinstellungen",
Default Round Off Account,Standard-Rundungskonto,
Default Round Off Account,Standardkonto für Rundungsdifferenzen,
Failed Import Log,Importprotokoll fehlgeschlagen,
Fixed Error Log,Fehlerprotokoll behoben,
Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts,Firma {0} existiert bereits. Durch Fortfahren werden das Unternehmen und der Kontenplan überschrieben,
@ -9918,3 +9914,7 @@ Delivered at Place Unloaded,Geliefert benannter Ort entladen,
Delivered Duty Paid,Geliefert verzollt,
Discount Validity,Frist für den Rabatt,
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
Select Alternative Items for Sales Order,Alternativpositionen für Auftragsbestätigung auswählen,
Select an item from each set to be used in the Sales Order.,"Wählen Sie aus den Alternativen jeweils einen Artikel aus, der in die Auftragsbestätigung übernommen werden soll.",
Is Alternative,Ist Alternative,
Alternative Items,Alternativpositionen,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Παραλήπτης,
Reviews,Κριτικές,
Sender,Αποστολέας,
Shop,Κατάστημα,
Sign Up,Εγγραφείτε,
Subsidiary,Θυγατρική,
There is some problem with the file url: {0},Υπάρχει κάποιο πρόβλημα με το url αρχείο: {0},
There were errors while sending email. Please try again.,Υπήρξαν σφάλματα κατά την αποστολή ηλεκτρονικού ταχυδρομείου. Παρακαλώ δοκιμάστε ξανά .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Beneficiario,
Reviews,Comentarios,
Sender,Remitente,
Shop,Tienda.,
Sign Up,Regístrate,
Subsidiary,Subsidiaria,
There is some problem with the file url: {0},Hay un poco de problema con la url del archivo: {0},
There were errors while sending email. Please try again.,"Ha ocurrido un error al enviar el correo electrónico. Por favor, inténtelo de nuevo.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Saaja,
Reviews,Ülevaated,
Sender,Lähetaja,
Shop,Kauplus,
Sign Up,Registreeri,
Subsidiary,Tütarettevõte,
There is some problem with the file url: {0},Seal on mõned probleem faili url: {0},
There were errors while sending email. Please try again.,Vigu samas saates email. Palun proovi uuesti.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,گیرنده,
Reviews,بررسی ها,
Sender,فرستنده,
Shop,فروشگاه,
Sign Up,ثبت نام,
Subsidiary,فرعی,
There is some problem with the file url: {0},بعضی از مشکل با آدرس فایل وجود دارد: {0},
There were errors while sending email. Please try again.,بودند خطاهای هنگام ارسال ایمیل وجود دارد. لطفا دوباره تلاش کنید.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,vastaanottaja,
Reviews,Arvostelut,
Sender,Lähettäjä,
Shop,Osta,
Sign Up,Kirjaudu,
Subsidiary,tytäryhtiö,
There is some problem with the file url: {0},Tiedosto-URL:issa {0} on ongelma,
There were errors while sending email. Please try again.,"Lähetettäessä sähköpostia oli virheitä, yrita uudelleen",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Destinataire,
Reviews,Avis,
Sender,Expéditeur,
Shop,Magasin,
Sign Up,S'inscrire,
Subsidiary,Filiale,
There is some problem with the file url: {0},Il y a un problème avec l'url du fichier : {0},
There were errors while sending email. Please try again.,Il y a eu des erreurs lors de l'envoi demails. Veuillez essayer à nouveau.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,પ્રાપ્તકર્તા,
Reviews,સમીક્ષાઓ,
Sender,પ્રેષક,
Shop,દુકાન,
Sign Up,સાઇન અપ કરો,
Subsidiary,સબસિડીયરી,
There is some problem with the file url: {0},ફાઈલ URL સાથે કેટલાક સમસ્યા છે: {0},
There were errors while sending email. Please try again.,ઇમેઇલ મોકલતી વખતે ભૂલો આવી હતી. ફરી પ્રયત્ન કરો.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,נמען,
Reviews,ביקורות,
Sender,שולח,
Shop,חנות,
Sign Up,הירשם,
Subsidiary,חברת בת,
There is some problem with the file url: {0},יש קצת בעיה עם כתובת אתר הקובץ: {0},
There were errors while sending email. Please try again.,היו שגיאות בעת שליחת דואר אלקטרוני. אנא נסה שוב.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,प्राप्तकर्ता,
Reviews,समीक्षा,
Sender,प्रेषक,
Shop,दुकान,
Sign Up,साइन अप करें,
Subsidiary,सहायक,
There is some problem with the file url: {0},फ़ाइल यूआरएल के साथ कुछ समस्या है: {0},
There were errors while sending email. Please try again.,ईमेल भेजने के दौरान त्रुटि . पुन: प्रयास करें .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Primalac,
Reviews,Recenzije,
Sender,Pošiljalac,
Shop,Dućan,
Sign Up,Prijavite se,
Subsidiary,Podružnica,
There is some problem with the file url: {0},Postoji neki problem s datotečnog URL: {0},
There were errors while sending email. Please try again.,Bilo je grešaka tijekom slanja e-pošte. Molimo pokušajte ponovno .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Címzett,
Reviews,Vélemények,
Sender,Küldő,
Shop,Bolt,
Sign Up,Regisztrálj,
Subsidiary,Leányvállalat,
There is some problem with the file url: {0},Van valami probléma a fájl URL-el: {0},
There were errors while sending email. Please try again.,"Email küldés közben hibák voltak. Kérjük, próbálja újra.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Penerima,
Reviews,Ulasan,
Sender,Pengirim,
Shop,Toko,
Sign Up,Daftar,
Subsidiary,Anak Perusahaan,
There is some problem with the file url: {0},Ada beberapa masalah dengan url berkas: {0},
There were errors while sending email. Please try again.,Ada kesalahan saat mengirim email. Silakan coba lagi.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,viðtakandi,
Reviews,Umsagnir,
Sender,sendanda,
Shop,Shop,
Sign Up,Skráðu þig,
Subsidiary,dótturfélag,
There is some problem with the file url: {0},Það er einhver vandamál með skrá url: {0},
There were errors while sending email. Please try again.,Það komu upp villur við að senda tölvupóst. Vinsamlegast reyndu aftur.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Destinatario,
Reviews,Recensioni,
Sender,Mittente,
Shop,Negozio,
Sign Up,Iscriviti,
Subsidiary,Sussidiario,
There is some problem with the file url: {0},C&#39;è qualche problema con il file url: {0},
There were errors while sending email. Please try again.,Ci sono stati errori durante l'invio email. Riprova.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,受信者,
Reviews,レビュー,
Sender,送信者,
Shop,,
Sign Up,サインアップ,
Subsidiary,子会社,
There is some problem with the file url: {0},ファイルURLに問題があります{0},
There were errors while sending email. Please try again.,メールの送信中にエラーが発生しました。もう一度お試しください。,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,អ្នកទទួល,
Reviews,ការពិនិត្យឡើងវិញ។,
Sender,អ្នកផ្ញើ,
Shop,ហាងលក់,
Sign Up,ចុះឈ្មោះ,
Subsidiary,ក្រុមហ៊ុនបុត្រសម្ព័ន្ធ,
There is some problem with the file url: {0},មានបញ្ហាមួយចំនួនជាមួយនឹងឯកសារ URL គឺ: {0},
There were errors while sending email. Please try again.,មានកំហុសខណៈពេលដែលការផ្ញើអ៊ីម៉ែលនោះទេ។ សូមព្យាយាមម្តងទៀត។,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,ಗ್ರಾಹಿ,
Reviews,ವಿಮರ್ಶೆಗಳು,
Sender,ಪ್ರೇಷಕ,
Shop,ಅಂಗಡಿ,
Sign Up,ಸೈನ್ ಅಪ್,
Subsidiary,ಸಹಕಾರಿ,
There is some problem with the file url: {0},ಕಡತ URL ನೊಂದಿಗೆ ಕೆಲವು ಸಮಸ್ಯೆಯಿದೆ: {0},
There were errors while sending email. Please try again.,ಇಮೇಲ್ ಕಳುಹಿಸುವಾಗ ದೋಷಗಳು ಇದ್ದವು. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,받는 사람,
Reviews,리뷰,
Sender,보낸 사람,
Shop,상점,
Sign Up,가입,
Subsidiary,자회사,
There is some problem with the file url: {0},파일 URL 몇 가지 문제가 있습니다 : {0},
There were errors while sending email. Please try again.,이메일을 보내는 동안 오류가 발생했습니다.재시도하십시오.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Girtevan,
Reviews,Nirxandin,
Sender,virrêkerî,
Shop,Dikan,
Sign Up,Tomar kirin,
Subsidiary,Şîrketa girêdayî,
There is some problem with the file url: {0},e hinek pirsgirêk bi url file hene: {0},
There were errors while sending email. Please try again.,bûn çewtî dema şandina email heye. Ji kerema xwe careke din biceribîne.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,ຜູ້ຮັບ,
Reviews,ການທົບທວນຄືນ,
Sender,ຜູ້ສົ່ງ,
Shop,ບໍລິການຜ່ານ,
Sign Up,ລົງທະບຽນ,
Subsidiary,ບໍລິສັດຍ່ອຍ,
There is some problem with the file url: {0},ມີບັນຫາບາງຢ່າງກັບ url ໄຟລ໌ແມ່ນ: {0},
There were errors while sending email. Please try again.,ມີຄວາມຜິດພາດໃນຂະນະທີ່ການສົ່ງອີເມວໄດ້. ກະລຸນາພະຍາຍາມອີກເທື່ອຫນຶ່ງ.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Gavėjas,
Reviews,Atsiliepimai,
Sender,Siuntėjas,
Shop,parduotuvė,
Sign Up,Registruotis,
Subsidiary,filialas,
There is some problem with the file url: {0},Yra kai kurie su URL failui problema: {0},
There were errors while sending email. Please try again.,"Nebuvo klaidos Nors siunčiant laišką. Prašau, pabandykite dar kartą.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Saņēmējs,
Reviews,Atsauksmes,
Sender,Nosūtītājs,
Shop,Veikals,
Sign Up,Pierakstīties,
Subsidiary,Filiāle,
There is some problem with the file url: {0},Ir dažas problēmas ar faila url: {0},
There were errors while sending email. Please try again.,"Bija kļūdas, nosūtot e-pastu. Lūdzu, mēģiniet vēlreiz.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Примачот,
Reviews,Прегледи,
Sender,Испраќачот,
Shop,Продавница,
Sign Up,Регистрирај се,
Subsidiary,Подружница,
There is some problem with the file url: {0},Има некој проблем со URL-то на фајл: {0},
There were errors while sending email. Please try again.,Имаше грешка при испраќање на е-мејл. Ве молиме обидете се повторно.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,സ്വീകർത്താവ്,
Reviews,അവലോകനങ്ങൾ,
Sender,അയച്ചയാളുടെ,
Shop,കട,
Sign Up,സൈൻ അപ്പ് ചെയ്യുക,
Subsidiary,സഹായകന്,
There is some problem with the file url: {0},ഫയൽ URL ഉള്ള ചില പ്രശ്നം ഉണ്ട്: {0},
There were errors while sending email. Please try again.,ഇമെയിൽ അയയ്ക്കുമ്പോൾ പിശകുകളുണ്ടായിരുന്നു. വീണ്ടും ശ്രമിക്കുക.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,प्राप्तकर्ता,
Reviews,पुनरावलोकने,
Sender,प्रेषक,
Shop,दुकान,
Sign Up,साइन अप करा,
Subsidiary,उपकंपनी,
There is some problem with the file url: {0},फाइल URL सह काही समस्या आहे: {0},
There were errors while sending email. Please try again.,ई-मेल पाठविताना त्रुटी होत्या. कृपया पुन्हा प्रयत्न करा.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Penerima,
Reviews,Ulasan,
Sender,Penghantar,
Shop,Kedai,
Sign Up,Daftar,
Subsidiary,Anak Syarikat,
There is some problem with the file url: {0},Terdapat beberapa masalah dengan url fail: {0},
There were errors while sending email. Please try again.,Terdapat ralat semasa menghantar e-mel. Sila cuba sekali lagi.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,လက်လံသူ,
Reviews,reviews,
Sender,ပေးပို့သူ,
Shop,ကုန်ဆိုင်,
Sign Up,အကောင့်ဖွင့်ရန်,
Subsidiary,ထောက်ခံသောကုမ္ပဏီ,
There is some problem with the file url: {0},ဖိုင်ကို url နှင့်အတူအချို့သောပြဿနာကိုလည်းရှိ၏: {0},
There were errors while sending email. Please try again.,အီးမေးလ်ပို့သည့်အနေဖြင့်အမှားများရှိကြ၏။ ထပ်ကြိုးစားပါ။,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Ontvanger,
Reviews,beoordelingen,
Sender,Afzender,
Shop,Winkelen,
Sign Up,Inschrijven,
Subsidiary,Dochteronderneming,
There is some problem with the file url: {0},Er is een probleem met het bestand url: {0},
There were errors while sending email. Please try again.,Er zijn fouten opgetreden tijdens het versturen van e-mail. Probeer het opnieuw .,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Mottaker,
Reviews,anmeldelser,
Sender,Avsender,
Shop,Butikk,
Sign Up,Melde deg på,
Subsidiary,Datterselskap,
There is some problem with the file url: {0},Det er noe problem med filen url: {0},
There were errors while sending email. Please try again.,"Det oppstod feil under sending epost. Vær så snill, prøv på nytt.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Adresat,
Reviews,Recenzje,
Sender,Nadawca,
Shop,Sklep,
Sign Up,Zapisz się,
Subsidiary,Pomocniczy,
There is some problem with the file url: {0},Jest jakiś problem z adresem URL pliku: {0},
There were errors while sending email. Please try again.,Wystąpiły błędy podczas wysyłki e-mail. Proszę spróbuj ponownie.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,دترلاسه کوونکي,
Reviews,بیاکتنې,
Sender,استوونکی,
Shop,شاپ,
Sign Up,ګډون کول,
Subsidiary,د متمم,
There is some problem with the file url: {0},شتون سره د دوتنې url ځينو ستونزه دا ده: {0},
There were errors while sending email. Please try again.,ایمیل استولو په داسې حال کې تېروتنې شوې دي. لطفا بیا هڅه وکړې.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Destinatário,
Reviews,Revisões,
Sender,Remetente,
Shop,Loja,
Sign Up,Inscrever-se,
Subsidiary,Subsidiário,
There is some problem with the file url: {0},Há algum problema com a url do arquivo: {0},
There were errors while sending email. Please try again.,Ocorreram erros durante o envio de email. Por favor tente novamente.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Destinatário,
Reviews,Rever,
Sender,Remetente,
Shop,Loja,
Sign Up,Inscrever-se,
Subsidiary,Subsidiário,
There is some problem with the file url: {0},Há algum problema com a url do arquivo: {0},
There were errors while sending email. Please try again.,"Ocorreram erros durante o envio de e-mail. Por favor, tente novamente.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Destinatar,
Reviews,opinii,
Sender,Expeditor,
Shop,Magazin,
Sign Up,Inscrie-te,
Subsidiary,Filială,
There is some problem with the file url: {0},Există unele probleme cu URL-ul fișierului: {0},
There were errors while sending email. Please try again.,Au fost erori în timp ce trimiterea de e-mail. Încercaţi din nou.,

Can't render this file because it is too large.

View File

@ -3503,7 +3503,6 @@ Recipient,Сторона-реципиент,
Reviews,Отзывы,
Sender,Отправитель,
Shop,Магазин,
Sign Up,Регистрация,
Subsidiary,Филиал,
There is some problem with the file url: {0},Существует некоторая проблема с файловой URL: {0},
There were errors while sending email. Please try again.,"При отправке электронной почты возникли ошибки. Пожалуйста, попробуйте ещё раз.",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Uwakiriye,
Reviews,Isubiramo,
Sender,Kohereza,
Shop,Amaduka,
Sign Up,Iyandikishe,
Subsidiary,Inkunga,
There is some problem with the file url: {0},Hano hari ikibazo na dosiye url: {0},
There were errors while sending email. Please try again.,Habayeho amakosa mugihe wohereje imeri. Nyamuneka gerageza.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,පලමු වරට පිරිනැමු,
Reviews,සමාලෝචන,
Sender,යවන්නාගේ,
Shop,වෙළඳසැල්,
Sign Up,ලියාපදිංචි වන්න,
Subsidiary,අනුබද්ධිත සමාගමක්,
There is some problem with the file url: {0},ගොනුව url එක සමග යම් ගැටළුවක් වේ: {0},
There were errors while sending email. Please try again.,ඊ-තැපැල් යැවීම අතර දෝෂ ඇතිවිය. කරුණාකර නැවත උත්සාහ කරන්න.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Príjemca,
Reviews,recenzia,
Sender,Odosielateľ,
Shop,Obchod,
Sign Up,Prihlásiť Se,
Subsidiary,Dceřiný,
There is some problem with the file url: {0},Tam je nejaký problém s URL súboru: {0},
There were errors while sending email. Please try again.,Narazili jsme na problémy při odesílání emailu. Prosím zkuste to znovu.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Prejemnik,
Reviews,Ocene,
Sender,Sender,
Shop,Trgovina,
Sign Up,Prijavite se,
Subsidiary,Hčerinska družba,
There is some problem with the file url: {0},Obstaja nekaj problem z datoteko url: {0},
There were errors while sending email. Please try again.,Tam so bile napake pri pošiljanju e-pošte. Prosim poskusi znova.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Marrës,
Reviews,Shqyrtime,
Sender,Dërgues,
Shop,Dyqan,
Sign Up,Regjistrohuni,
Subsidiary,Ndihmës,
There is some problem with the file url: {0},Ka disa probleme me file url: {0},
There were errors while sending email. Please try again.,Ka pasur gabime ndërsa dërguar një email. Ju lutemi provoni përsëri.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Прималац,
Reviews,Коментара,
Sender,Пошиљалац,
Shop,Продавница,
Sign Up,Пријави се,
Subsidiary,Подружница,
There is some problem with the file url: {0},Постоји неки проблем са Филе УРЛ: {0},
There were errors while sending email. Please try again.,"Были ошибки при отправке электронной почты . Пожалуйста, попробуйте еще раз .",

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Mottagare,
Reviews,recensioner,
Sender,Avsändare,
Shop,Shop,
Sign Up,Bli Medlem,
Subsidiary,Dotterbolag,
There is some problem with the file url: {0},Det finns vissa problem med filen url: {0},
There were errors while sending email. Please try again.,Det fanns fel när du skickar e-post. Var god försök igen.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,Mpokeaji,
Reviews,Maoni,
Sender,Sender,
Shop,Duka,
Sign Up,Ingia,
Subsidiary,Msaidizi,
There is some problem with the file url: {0},Kuna tatizo fulani na url ya faili: {0},
There were errors while sending email. Please try again.,Kulikuwa na makosa wakati wa kutuma barua pepe. Tafadhali jaribu tena.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,பெறுபவர்,
Reviews,விமர்சனங்கள்,
Sender,அனுப்புபவர்,
Shop,ஷாப்பிங்,
Sign Up,பதிவு செய்யவும்,
Subsidiary,உப,
There is some problem with the file url: {0},கோப்பு URL சில சிக்கல் உள்ளது: {0},
There were errors while sending email. Please try again.,மின்னஞ்சல் அனுப்பும் போது பிழைகள் இருந்தன . மீண்டும் முயற்சிக்கவும்.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,స్వీకర్త,
Reviews,సమీక్షలు,
Sender,పంపినవారు,
Shop,షాప్,
Sign Up,చేరడం,
Subsidiary,అనుబంధ,
There is some problem with the file url: {0},ఫైల్ URL కొన్ని సమస్య ఉంది: {0},
There were errors while sending email. Please try again.,ఇమెయిల్ పంపడం అయితే కొన్ని లోపాలు ఉన్నాయి. మళ్ళి ప్రయత్నించండి.,

Can't render this file because it is too large.

View File

@ -3505,7 +3505,6 @@ Recipient,ผู้รับ,
Reviews,ความคิดเห็น,
Sender,ผู้ส่ง,
Shop,ร้านค้า,
Sign Up,ลงชื่อ,
Subsidiary,บริษัท สาขา,
There is some problem with the file url: {0},มีปัญหากับ url ของไฟล์เป็น: {0},
There were errors while sending email. Please try again.,มีข้อผิดพลาด ในขณะที่ มี การส่งอีเมล์ โปรดลองอีกครั้ง,

Can't render this file because it is too large.

Some files were not shown because too many files have changed in this diff Show More