mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-19 04:59:18 +00:00
Merge pull request #35665 from frappe/version-13-hotfix
chore: release v13
This commit is contained in:
@@ -40,6 +40,7 @@
|
|||||||
"submit_journal_entries",
|
"submit_journal_entries",
|
||||||
"print_settings",
|
"print_settings",
|
||||||
"show_inclusive_tax_in_print",
|
"show_inclusive_tax_in_print",
|
||||||
|
"show_taxes_as_table_in_print",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"show_payment_schedule_in_print",
|
"show_payment_schedule_in_print",
|
||||||
"currency_exchange_section",
|
"currency_exchange_section",
|
||||||
@@ -293,6 +294,12 @@
|
|||||||
"fieldname": "book_tax_discount_loss",
|
"fieldname": "book_tax_discount_loss",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Book Tax Loss on Early Payment Discount"
|
"label": "Book Tax Loss on Early Payment Discount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "show_taxes_as_table_in_print",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Show Taxes as Table in Print"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -300,7 +307,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-14 17:22:03.680886",
|
"modified": "2023-06-13 18:47:46.430291",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -150,19 +150,57 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_allocated_amount(self):
|
def validate_allocated_amount(self):
|
||||||
for d in self.get("references"):
|
if self.payment_type == "Internal Transfer" or self.party_type in ("Donor"):
|
||||||
|
return
|
||||||
|
|
||||||
|
latest_references = get_outstanding_reference_documents(
|
||||||
|
{
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"company": self.company,
|
||||||
|
"party_type": self.party_type,
|
||||||
|
"payment_type": self.payment_type,
|
||||||
|
"party": self.party,
|
||||||
|
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Group latest_references by (voucher_type, voucher_no)
|
||||||
|
latest_lookup = {}
|
||||||
|
for d in latest_references:
|
||||||
|
d = frappe._dict(d)
|
||||||
|
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||||
|
|
||||||
|
for d in self.get("references").copy():
|
||||||
|
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||||
|
|
||||||
|
# The reference has already been fully paid
|
||||||
|
if not latest:
|
||||||
|
frappe.throw(
|
||||||
|
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
|
||||||
|
)
|
||||||
|
# The reference has already been partly paid
|
||||||
|
elif (
|
||||||
|
latest.outstanding_amount < latest.invoice_amount
|
||||||
|
and d.outstanding_amount != latest.outstanding_amount
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount."
|
||||||
|
).format(d.reference_doctype, d.reference_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
d.outstanding_amount = latest.outstanding_amount
|
||||||
|
|
||||||
|
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||||
|
|
||||||
if (flt(d.allocated_amount)) > 0:
|
if (flt(d.allocated_amount)) > 0:
|
||||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||||
frappe.throw(
|
frappe.throw(fail_message.format(d.idx))
|
||||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for negative outstanding invoices as well
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0:
|
if flt(d.allocated_amount) < 0:
|
||||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
frappe.throw(
|
frappe.throw(fail_message.format(d.idx))
|
||||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
def delink_advance_entry_references(self):
|
def delink_advance_entry_references(self):
|
||||||
for reference in self.references:
|
for reference in self.references:
|
||||||
@@ -391,7 +429,7 @@ class PaymentEntry(AccountsController):
|
|||||||
for k, v in no_oustanding_refs.items():
|
for k, v in no_oustanding_refs.items():
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
|
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
|
||||||
).format(
|
).format(
|
||||||
_(k),
|
_(k),
|
||||||
frappe.bold(", ".join(d.reference_name for d in v)),
|
frappe.bold(", ".join(d.reference_name for d in v)),
|
||||||
@@ -1457,7 +1495,7 @@ def get_orders_to_be_billed(
|
|||||||
if voucher_type:
|
if voucher_type:
|
||||||
doc = frappe.get_doc({"doctype": voucher_type})
|
doc = frappe.get_doc({"doctype": voucher_type})
|
||||||
condition = ""
|
condition = ""
|
||||||
if doc and hasattr(doc, "cost_center"):
|
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||||
condition = " and cost_center='%s'" % cost_center
|
condition = " and cost_center='%s'" % cost_center
|
||||||
|
|
||||||
orders = []
|
orders = []
|
||||||
@@ -1503,9 +1541,15 @@ def get_orders_to_be_billed(
|
|||||||
|
|
||||||
order_list = []
|
order_list = []
|
||||||
for d in orders:
|
for d in orders:
|
||||||
if not (
|
if (
|
||||||
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
|
filters
|
||||||
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
|
and filters.get("outstanding_amt_greater_than")
|
||||||
|
and filters.get("outstanding_amt_less_than")
|
||||||
|
and not (
|
||||||
|
flt(filters.get("outstanding_amt_greater_than"))
|
||||||
|
<= flt(d.outstanding_amount)
|
||||||
|
<= flt(filters.get("outstanding_amt_less_than"))
|
||||||
|
)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -999,6 +999,30 @@ class TestPaymentEntry(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue("is on hold" in str(err.exception).lower())
|
self.assertTrue("is on hold" in str(err.exception).lower())
|
||||||
|
|
||||||
|
def test_duplicate_payment_entry_allocate_amount(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe_draft.insert()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||||
|
|
||||||
|
def test_duplicate_payment_entry_partial_allocate_amount(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe_draft.insert()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe.received_amount = si.total / 2
|
||||||
|
pe.references[0].allocated_amount = si.total / 2
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, getdate
|
from frappe.utils import cint, flt, getdate
|
||||||
|
|
||||||
|
|
||||||
class TaxWithholdingCategory(Document):
|
class TaxWithholdingCategory(Document):
|
||||||
@@ -520,8 +520,13 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
|
|||||||
tds_amount = 0
|
tds_amount = 0
|
||||||
limit_consumed = frappe.db.get_value(
|
limit_consumed = frappe.db.get_value(
|
||||||
"Purchase Invoice",
|
"Purchase Invoice",
|
||||||
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
|
{
|
||||||
"sum(net_total)",
|
"supplier": ("in", parties),
|
||||||
|
"apply_tds": 1,
|
||||||
|
"docstatus": 1,
|
||||||
|
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
||||||
|
},
|
||||||
|
"sum(tax_withholding_net_total)",
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_valid_certificate(
|
if is_valid_certificate(
|
||||||
@@ -535,10 +540,10 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
|
|||||||
|
|
||||||
|
|
||||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||||
if current_amount < (certificate_limit - deducted_amount):
|
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
|
||||||
return current_amount * rate / 100
|
return current_amount * rate / 100
|
||||||
else:
|
else:
|
||||||
ltds_amount = certificate_limit - deducted_amount
|
ltds_amount = certificate_limit - flt(deducted_amount)
|
||||||
tds_amount = current_amount - ltds_amount
|
tds_amount = current_amount - ltds_amount
|
||||||
|
|
||||||
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
|
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
|
||||||
@@ -549,9 +554,9 @@ def is_valid_certificate(
|
|||||||
):
|
):
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
if (
|
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
|
||||||
getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
|
|
||||||
) and certificate_limit > deducted_amount:
|
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:49:58.133098",
|
"creation": "2016-04-08 14:49:58.133098",
|
||||||
"disabled": 0,
|
"disable_prepared_report": 0,
|
||||||
"docstatus": 0,
|
"disabled": 0,
|
||||||
"doctype": "Report",
|
"docstatus": 0,
|
||||||
"idx": 2,
|
"doctype": "Report",
|
||||||
"is_standard": "Yes",
|
"filters": [],
|
||||||
"modified": "2017-02-24 20:08:26.084484",
|
"idx": 2,
|
||||||
"modified_by": "Administrator",
|
"is_standard": "Yes",
|
||||||
"module": "Accounts",
|
"modified": "2023-06-06 09:00:07.435151",
|
||||||
"name": "Asset Depreciation Ledger",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "Accounts",
|
||||||
"ref_doctype": "Asset",
|
"name": "Asset Depreciation Ledger",
|
||||||
"report_name": "Asset Depreciation Ledger",
|
"owner": "Administrator",
|
||||||
"report_type": "Script Report",
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Asset",
|
||||||
|
"report_name": "Asset Depreciation Ledger",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:56:37.235981",
|
"creation": "2016-04-08 14:56:37.235981",
|
||||||
"disabled": 0,
|
"disable_prepared_report": 0,
|
||||||
"docstatus": 0,
|
"disabled": 0,
|
||||||
"doctype": "Report",
|
"docstatus": 0,
|
||||||
"idx": 2,
|
"doctype": "Report",
|
||||||
"is_standard": "Yes",
|
"filters": [],
|
||||||
"modified": "2017-02-24 20:08:18.660476",
|
"idx": 2,
|
||||||
"modified_by": "Administrator",
|
"is_standard": "Yes",
|
||||||
"module": "Accounts",
|
"modified": "2023-06-06 11:33:29.611277",
|
||||||
"name": "Asset Depreciations and Balances",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "Accounts",
|
||||||
"ref_doctype": "Asset",
|
"name": "Asset Depreciations and Balances",
|
||||||
"report_name": "Asset Depreciations and Balances",
|
"owner": "Administrator",
|
||||||
"report_type": "Script Report",
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Asset",
|
||||||
|
"report_name": "Asset Depreciations and Balances",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
|
|||||||
@@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||||
`tabSales Invoice`.is_internal_customer,
|
`tabSales Invoice`.is_internal_customer,
|
||||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||||
|
`tabSales Invoice Item`.project,
|
||||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||||
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
||||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
|||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
get_depreciation_accounts,
|
get_depreciation_accounts,
|
||||||
get_disposal_account_and_cost_center,
|
get_disposal_account_and_cost_center,
|
||||||
|
is_first_day_of_the_month,
|
||||||
is_last_day_of_the_month,
|
is_last_day_of_the_month,
|
||||||
)
|
)
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
@@ -364,8 +365,14 @@ class Asset(AccountsController):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# For first row
|
# For first row
|
||||||
if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation:
|
if (
|
||||||
from_date = add_days(self.available_for_use_date, -1)
|
n == 0
|
||||||
|
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||||
|
and not self.opening_accumulated_depreciation
|
||||||
|
):
|
||||||
|
from_date = add_days(
|
||||||
|
self.available_for_use_date, -1
|
||||||
|
) # needed to calc depr amount for available_for_use_date too
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(
|
depreciation_amount, days, months = self.get_pro_rata_amt(
|
||||||
finance_book,
|
finance_book,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
@@ -374,10 +381,18 @@ class Asset(AccountsController):
|
|||||||
has_wdv_or_dd_non_yearly_pro_rata,
|
has_wdv_or_dd_non_yearly_pro_rata,
|
||||||
)
|
)
|
||||||
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
||||||
from_date = add_months(
|
if not is_first_day_of_the_month(getdate(self.available_for_use_date)):
|
||||||
getdate(self.available_for_use_date),
|
from_date = get_last_day(
|
||||||
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
|
add_months(
|
||||||
)
|
getdate(self.available_for_use_date),
|
||||||
|
((self.number_of_depreciations_booked - 1) * finance_book.frequency_of_depreciation),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from_date = add_months(
|
||||||
|
getdate(add_days(self.available_for_use_date, -1)),
|
||||||
|
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
|
||||||
|
)
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(
|
depreciation_amount, days, months = self.get_pro_rata_amt(
|
||||||
finance_book,
|
finance_book,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from frappe.utils import (
|
|||||||
add_months,
|
add_months,
|
||||||
cint,
|
cint,
|
||||||
flt,
|
flt,
|
||||||
|
get_first_day,
|
||||||
get_last_day,
|
get_last_day,
|
||||||
get_link_to_form,
|
get_link_to_form,
|
||||||
getdate,
|
getdate,
|
||||||
@@ -543,3 +544,9 @@ def is_last_day_of_the_month(date):
|
|||||||
last_day_of_the_month = get_last_day(date)
|
last_day_of_the_month = get_last_day(date)
|
||||||
|
|
||||||
return getdate(last_day_of_the_month) == getdate(date)
|
return getdate(last_day_of_the_month) == getdate(date)
|
||||||
|
|
||||||
|
|
||||||
|
def is_first_day_of_the_month(date):
|
||||||
|
first_day_of_the_month = get_first_day(date)
|
||||||
|
|
||||||
|
return getdate(first_day_of_the_month) == getdate(date)
|
||||||
|
|||||||
@@ -686,14 +686,14 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
number_of_depreciations_booked=1,
|
number_of_depreciations_booked=1,
|
||||||
opening_accumulated_depreciation=50000,
|
opening_accumulated_depreciation=50000,
|
||||||
expected_value_after_useful_life=10000,
|
expected_value_after_useful_life=10000,
|
||||||
depreciation_start_date="2030-12-31",
|
depreciation_start_date="2031-12-31",
|
||||||
total_number_of_depreciations=3,
|
total_number_of_depreciations=3,
|
||||||
frequency_of_depreciation=12,
|
frequency_of_depreciation=12,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
|
|
||||||
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
|
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
|
||||||
|
|
||||||
schedules = [
|
schedules = [
|
||||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||||
|
|||||||
@@ -891,6 +891,9 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
return is_inclusive
|
return is_inclusive
|
||||||
|
|
||||||
|
def should_show_taxes_as_table_in_print(self):
|
||||||
|
return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print"))
|
||||||
|
|
||||||
def validate_advance_entries(self):
|
def validate_advance_entries(self):
|
||||||
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
||||||
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
|
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
|
||||||
|
|||||||
@@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings):
|
|||||||
doc.print_templates.update(
|
doc.print_templates.update(
|
||||||
{
|
{
|
||||||
"total": "templates/print_formats/includes/total.html",
|
"total": "templates/print_formats/includes/total.html",
|
||||||
"taxes": "templates/print_formats/includes/taxes.html",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not doc.should_show_taxes_as_table_in_print():
|
||||||
|
doc.print_templates.update(
|
||||||
|
{
|
||||||
|
"taxes": "templates/print_formats/includes/taxes.html",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_columns(display_columns, compact_fields):
|
def format_columns(display_columns, compact_fields):
|
||||||
compact_fields = compact_fields + ["image", "item_code", "item_name"]
|
compact_fields = compact_fields + ["image", "item_code", "item_name"]
|
||||||
|
|||||||
@@ -78,9 +78,10 @@ erpnext.ProductList = class {
|
|||||||
let title_html = `<div style="display: flex; margin-left: -15px;">`;
|
let title_html = `<div style="display: flex; margin-left: -15px;">`;
|
||||||
title_html += `
|
title_html += `
|
||||||
<div class="col-8" style="margin-right: -15px;">
|
<div class="col-8" style="margin-right: -15px;">
|
||||||
<a class="" href="/${ item.route || '#' }"
|
<a href="/${ item.route || '#' }">
|
||||||
style="color: var(--gray-800); font-weight: 500;">
|
<div class="product-title">
|
||||||
${ title }
|
${ title }
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -201,4 +202,4 @@ erpnext.ProductList = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ from math import ceil
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import date_diff, flt, formatdate, get_last_day, get_link_to_form, getdate
|
from frappe.utils import (
|
||||||
|
comma_and,
|
||||||
|
date_diff,
|
||||||
|
flt,
|
||||||
|
formatdate,
|
||||||
|
get_last_day,
|
||||||
|
get_link_to_form,
|
||||||
|
getdate,
|
||||||
|
)
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
|
|
||||||
@@ -207,7 +215,6 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_assignment_for_multiple_employees(employees, data):
|
def create_assignment_for_multiple_employees(employees, data):
|
||||||
|
|
||||||
if isinstance(employees, string_types):
|
if isinstance(employees, string_types):
|
||||||
employees = json.loads(employees)
|
employees = json.loads(employees)
|
||||||
|
|
||||||
@@ -215,6 +222,8 @@ def create_assignment_for_multiple_employees(employees, data):
|
|||||||
data = frappe._dict(json.loads(data))
|
data = frappe._dict(json.loads(data))
|
||||||
|
|
||||||
docs_name = []
|
docs_name = []
|
||||||
|
failed = []
|
||||||
|
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
assignment = frappe.new_doc("Leave Policy Assignment")
|
assignment = frappe.new_doc("Leave Policy Assignment")
|
||||||
assignment.employee = employee
|
assignment.employee = employee
|
||||||
@@ -225,18 +234,45 @@ def create_assignment_for_multiple_employees(employees, data):
|
|||||||
assignment.leave_period = data.leave_period or None
|
assignment.leave_period = data.leave_period or None
|
||||||
assignment.carry_forward = data.carry_forward
|
assignment.carry_forward = data.carry_forward
|
||||||
assignment.save()
|
assignment.save()
|
||||||
try:
|
|
||||||
assignment.submit()
|
|
||||||
except frappe.exceptions.ValidationError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
frappe.db.commit()
|
savepoint = "before_assignment_submission"
|
||||||
|
|
||||||
|
try:
|
||||||
|
frappe.db.savepoint(savepoint)
|
||||||
|
assignment.submit()
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback(save_point=savepoint)
|
||||||
|
frappe.log_error(title=f"Leave Policy Assignment submission failed for {assignment.name}")
|
||||||
|
failed.append(assignment.name)
|
||||||
|
|
||||||
docs_name.append(assignment.name)
|
docs_name.append(assignment.name)
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
show_assignment_submission_status(failed)
|
||||||
|
|
||||||
return docs_name
|
return docs_name
|
||||||
|
|
||||||
|
|
||||||
|
def show_assignment_submission_status(failed):
|
||||||
|
frappe.clear_messages()
|
||||||
|
assignment_list = [get_link_to_form("Leave Policy Assignment", entry) for entry in failed]
|
||||||
|
|
||||||
|
msg = _("Failed to submit some leave policy assignments:")
|
||||||
|
msg += " " + comma_and(assignment_list, False) + "<hr>"
|
||||||
|
msg += (
|
||||||
|
_("Check {0} for more details")
|
||||||
|
.format("<a href='/app/List/Error Log?reference_doctype=Leave Policy Assignment'>{0}</a>")
|
||||||
|
.format(_("Error Log"))
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
msg,
|
||||||
|
indicator="red",
|
||||||
|
title=_("Submission Failed"),
|
||||||
|
is_minimizable=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_leave_type_details():
|
def get_leave_type_details():
|
||||||
leave_type_details = frappe._dict()
|
leave_type_details = frappe._dict()
|
||||||
leave_types = frappe.get_all(
|
leave_types = frappe.get_all(
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
|||||||
get_query() {
|
get_query() {
|
||||||
let filters = {"is_active": 1};
|
let filters = {"is_active": 1};
|
||||||
if (cur_dialog.fields_dict.company.value)
|
if (cur_dialog.fields_dict.company.value)
|
||||||
filters["company"] = cur_dialog.fields_dict.company.value;
|
filters["company"] = cur_dialog?.fields_dict?.company?.value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: filters
|
filters: filters
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.query_builder.functions import Count
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -15,31 +16,34 @@ def get_children(parent=None, company=None, exclude_node=None):
|
|||||||
if exclude_node:
|
if exclude_node:
|
||||||
filters.append(["name", "!=", exclude_node])
|
filters.append(["name", "!=", exclude_node])
|
||||||
|
|
||||||
employees = frappe.get_list(
|
employees = frappe.get_all(
|
||||||
"Employee",
|
"Employee",
|
||||||
fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"],
|
fields=[
|
||||||
|
"employee_name as name",
|
||||||
|
"name as id",
|
||||||
|
"lft",
|
||||||
|
"rgt",
|
||||||
|
"reports_to",
|
||||||
|
"image",
|
||||||
|
"designation as title",
|
||||||
|
],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
order_by="name",
|
order_by="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")})
|
employee.connections = get_connections(employee.id, employee.lft, employee.rgt)
|
||||||
employee.connections = get_connections(employee.id)
|
employee.expandable = bool(employee.connections)
|
||||||
employee.expandable = 1 if is_expandable else 0
|
|
||||||
|
|
||||||
return employees
|
return employees
|
||||||
|
|
||||||
|
|
||||||
def get_connections(employee):
|
def get_connections(employee: str, lft: int, rgt: int) -> int:
|
||||||
num_connections = 0
|
Employee = frappe.qb.DocType("Employee")
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(Employee)
|
||||||
|
.select(Count(Employee.name))
|
||||||
|
.where((Employee.lft > lft) & (Employee.rgt < rgt))
|
||||||
|
).run()
|
||||||
|
|
||||||
nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]])
|
return query[0][0]
|
||||||
num_connections += len(nodes_to_expand)
|
|
||||||
|
|
||||||
while nodes_to_expand:
|
|
||||||
parent = nodes_to_expand.pop(0)
|
|
||||||
descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]])
|
|
||||||
num_connections += len(descendants)
|
|
||||||
nodes_to_expand.extend(descendants)
|
|
||||||
|
|
||||||
return num_connections
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||||
|
from erpnext.hr.page.organizational_chart.organizational_chart import get_children
|
||||||
|
|
||||||
|
|
||||||
|
class TestOrganizationalChart(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.company = create_company("Test Org Chart").name
|
||||||
|
frappe.db.delete("Employee", {"company": self.company})
|
||||||
|
|
||||||
|
def test_get_children(self):
|
||||||
|
company = create_company("Test Org Chart").name
|
||||||
|
emp1 = make_employee("testemp1@mail.com", company=self.company)
|
||||||
|
emp2 = make_employee("testemp2@mail.com", company=self.company, reports_to=emp1)
|
||||||
|
emp3 = make_employee("testemp3@mail.com", company=self.company, reports_to=emp1)
|
||||||
|
make_employee("testemp4@mail.com", company=self.company, reports_to=emp2)
|
||||||
|
|
||||||
|
# root node
|
||||||
|
children = get_children(company=self.company)
|
||||||
|
self.assertEqual(len(children), 1)
|
||||||
|
self.assertEqual(children[0].id, emp1)
|
||||||
|
self.assertEqual(children[0].connections, 3)
|
||||||
|
|
||||||
|
# root's children
|
||||||
|
children = get_children(parent=emp1, company=self.company)
|
||||||
|
self.assertEqual(len(children), 2)
|
||||||
|
self.assertEqual(children[0].id, emp2)
|
||||||
|
self.assertEqual(children[0].connections, 1)
|
||||||
|
self.assertEqual(children[1].id, emp3)
|
||||||
|
self.assertEqual(children[1].connections, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def create_company(name):
|
||||||
|
if frappe.db.exists("Company", name):
|
||||||
|
return frappe.get_doc("Company", name)
|
||||||
|
|
||||||
|
company = frappe.new_doc("Company")
|
||||||
|
company.update(
|
||||||
|
{
|
||||||
|
"company_name": name,
|
||||||
|
"default_currency": "USD",
|
||||||
|
"country": "United States",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return company.insert()
|
||||||
@@ -764,11 +764,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
precision("base_grand_total")
|
precision("base_grand_total")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.frm.doc.payments.find(pay => {
|
if(!this.frm.doc.is_return){
|
||||||
if (pay.default) {
|
this.frm.doc.payments.find(payment => {
|
||||||
pay.amount = total_amount_to_pay;
|
if (payment.default) {
|
||||||
}
|
payment.amount = total_amount_to_pay;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
this.frm.refresh_fields();
|
this.frm.refresh_fields();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -96,14 +96,14 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D
|
|||||||
range1 = range2 = range3 = above_range3 = 0.0
|
range1 = range2 = range3 = above_range3 = 0.0
|
||||||
|
|
||||||
for item in fifo_queue:
|
for item in fifo_queue:
|
||||||
age = date_diff(to_date, item[1])
|
age = flt(date_diff(to_date, item[1]))
|
||||||
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
|
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
|
||||||
|
|
||||||
if age <= filters.range1:
|
if age <= flt(filters.range1):
|
||||||
range1 = flt(range1 + qty, precision)
|
range1 = flt(range1 + qty, precision)
|
||||||
elif age <= filters.range2:
|
elif age <= flt(filters.range2):
|
||||||
range2 = flt(range2 + qty, precision)
|
range2 = flt(range2 + qty, precision)
|
||||||
elif age <= filters.range3:
|
elif age <= flt(filters.range3):
|
||||||
range3 = flt(range3 + qty, precision)
|
range3 = flt(range3 + qty, precision)
|
||||||
else:
|
else:
|
||||||
above_range3 = flt(above_range3 + qty, precision)
|
above_range3 = flt(above_range3 + qty, precision)
|
||||||
|
|||||||
Reference in New Issue
Block a user