mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-05 22:48:27 +00:00
Merge pull request #31812 from frappe/version-13-hotfix
chore: weekly version 13 release
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
|
||||
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -222,9 +222,6 @@ class POSInvoice(SalesInvoice):
|
||||
allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||
|
||||
for d in self.get("items"):
|
||||
is_service_item = not (frappe.db.get_value("Item", d.get("item_code"), "is_stock_item"))
|
||||
if is_service_item:
|
||||
return
|
||||
if d.serial_no:
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
|
||||
@@ -2173,13 +2173,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||
source_document_warehouse_field = "target_warehouse"
|
||||
target_document_warehouse_field = "from_warehouse"
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
else:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
|
||||
source_document_warehouse_field = "from_warehouse"
|
||||
target_document_warehouse_field = "target_warehouse"
|
||||
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
received_items = {}
|
||||
|
||||
validate_inter_company_transaction(source_doc, doctype)
|
||||
details = get_inter_company_details(source_doc, doctype)
|
||||
|
||||
@@ -44,14 +44,14 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"parent_field": "parent_invoice",
|
||||
"initial_depth": 3,
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
||||
column._options = "Sales Invoice";
|
||||
} else {
|
||||
column._options = "Item";
|
||||
}
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
|
||||
if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
|
||||
@@ -14,9 +14,9 @@ def execute(filters=None):
|
||||
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
columns = get_columns(filters)
|
||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
||||
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
supplier_map = get_supplier_pan_map()
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
print(journal_entry_party_map)
|
||||
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
|
||||
@@ -113,7 +113,7 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
def get_link(self):
|
||||
# RFQ link for supplier portal
|
||||
return get_url("/rfq/" + self.name)
|
||||
return get_url("/app/request-for-quotation/" + self.name)
|
||||
|
||||
def update_supplier_part_no(self, supplier):
|
||||
self.vendor = supplier
|
||||
|
||||
@@ -301,7 +301,8 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
||||
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
|
||||
@@ -18,8 +18,9 @@ from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Employee"
|
||||
conditions = []
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
fields = get_fields(doctype, ["name", "employee_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabEmployee`
|
||||
@@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
doctype = "Lead"
|
||||
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabLead`
|
||||
@@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Customer"
|
||||
conditions = []
|
||||
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
|
||||
|
||||
@@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "customer_name", "customer_group", "territory"]
|
||||
|
||||
fields = get_fields("Customer", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||
|
||||
return frappe.db.sql(
|
||||
@@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Supplier"
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
|
||||
if supp_master_name == "Supplier Name":
|
||||
@@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "supplier_name", "supplier_group"]
|
||||
|
||||
fields = get_fields("Supplier", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {field} from `tabSupplier`
|
||||
@@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
|
||||
def get_accounts(with_account_type_filter):
|
||||
@@ -197,13 +202,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
doctype = "Item"
|
||||
conditions = []
|
||||
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
# Get searchfields from meta and use in Item Link field query
|
||||
meta = frappe.get_meta("Item", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
# these are handled separately
|
||||
@@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
filters.pop("supplier", None)
|
||||
|
||||
description_cond = ""
|
||||
if frappe.db.count("Item", cache=True) < 50000:
|
||||
if frappe.db.count(doctype, cache=True) < 50000:
|
||||
# scan description only if items are less than 50000
|
||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||
return frappe.db.sql(
|
||||
@@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "BOM"
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
fields = get_fields(doctype, ["name", "item"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields}
|
||||
@@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Project"
|
||||
cond = ""
|
||||
if filters and filters.get("customer"):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
@@ -338,9 +346,9 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
frappe.db.escape(filters.get("customer"))
|
||||
)
|
||||
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
fields = get_fields(doctype, ["name", "project_name"])
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabProject`
|
||||
@@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
doctype = "Delivery Note"
|
||||
fields = get_fields(doctype, ["name", "customer", "posting_date"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
@@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Batch"
|
||||
cond = ""
|
||||
if filters.get("posting_date"):
|
||||
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
|
||||
@@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("is_return"):
|
||||
having_clause = ""
|
||||
|
||||
meta = frappe.get_meta("Batch", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
search_columns = ""
|
||||
@@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
filter_list = []
|
||||
|
||||
if isinstance(filters, dict):
|
||||
@@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
|
||||
|
||||
return frappe.desk.reportview.execute(
|
||||
"Account",
|
||||
doctype,
|
||||
filters=filter_list,
|
||||
fields=["name", "parent_account"],
|
||||
limit_start=start,
|
||||
@@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
# Should be used when item code is passed in filters.
|
||||
doctype = "Warehouse"
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
|
||||
@@ -589,6 +589,7 @@ accounting_dimension_doctypes = [
|
||||
"Shipping Rule",
|
||||
"Landed Cost Item",
|
||||
"Asset Value Adjustment",
|
||||
"Asset Repair",
|
||||
"Loyalty Program",
|
||||
"Fee Schedule",
|
||||
"Fee Structure",
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.permissions import (
|
||||
remove_user_permission,
|
||||
set_user_permission_if_allowed,
|
||||
)
|
||||
from frappe.utils import add_years, cstr, getdate, today, validate_email_address
|
||||
from frappe.utils import add_days, add_years, cstr, getdate, today, validate_email_address
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
from erpnext.utilities.transaction_base import delete_events
|
||||
@@ -64,6 +64,8 @@ class Employee(NestedSet):
|
||||
if existing_user_id:
|
||||
remove_user_permission("Employee", self.name, existing_user_id)
|
||||
|
||||
self.update_to_date_in_work_history()
|
||||
|
||||
def after_rename(self, old, new, merge):
|
||||
self.db_set("employee", new)
|
||||
|
||||
@@ -166,6 +168,18 @@ class Employee(NestedSet):
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Expense Approver")
|
||||
|
||||
def update_to_date_in_work_history(self):
|
||||
if not self.internal_work_history:
|
||||
return
|
||||
|
||||
for idx, row in enumerate(self.internal_work_history):
|
||||
if not row.from_date or idx == 0:
|
||||
continue
|
||||
|
||||
self.internal_work_history[idx - 1].to_date = add_days(row.from_date, -1)
|
||||
|
||||
self.internal_work_history[-1].to_date = None
|
||||
|
||||
def validate_date(self):
|
||||
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
|
||||
throw(_("Date of Birth cannot be greater than today."))
|
||||
|
||||
@@ -80,12 +80,14 @@ class TestEmployeeTransfer(unittest.TestCase):
|
||||
department = ["Accounts - TC", "Management - TC"]
|
||||
designation = ["Accountant", "Manager"]
|
||||
dt = [getdate("01-10-2021"), getdate()]
|
||||
to_date = [add_days(dt[1], -1), None]
|
||||
|
||||
employee = frappe.get_doc("Employee", employee)
|
||||
for data in employee.internal_work_history:
|
||||
self.assertEqual(data.department, department[count])
|
||||
self.assertEqual(data.designation, designation[count])
|
||||
self.assertEqual(data.from_date, dt[count])
|
||||
self.assertEqual(data.to_date, to_date[count])
|
||||
count = count + 1
|
||||
|
||||
transfer.cancel()
|
||||
@@ -95,6 +97,7 @@ class TestEmployeeTransfer(unittest.TestCase):
|
||||
self.assertEqual(data.designation, designation[0])
|
||||
self.assertEqual(data.department, department[0])
|
||||
self.assertEqual(data.from_date, dt[0])
|
||||
self.assertEqual(data.to_date, None)
|
||||
|
||||
|
||||
def create_company():
|
||||
|
||||
@@ -254,9 +254,11 @@ frappe.ui.form.on("Expense Claim", {
|
||||
}, __("View"));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
|
||||
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
|
||||
&& frappe.model.can_create("Payment Entry")) {
|
||||
if (
|
||||
frm.doc.docstatus === 1
|
||||
&& frm.doc.status !== "Paid"
|
||||
&& frappe.model.can_create("Payment Entry")
|
||||
) {
|
||||
frm.add_custom_button(__('Payment'),
|
||||
function() { frm.events.make_payment_entry(frm); }, __('Create'));
|
||||
}
|
||||
|
||||
@@ -305,8 +305,9 @@ class ExpenseClaim(AccountsController):
|
||||
|
||||
if self.total_advance_amount:
|
||||
precision = self.precision("total_advance_amount")
|
||||
amount_with_taxes = flt(self.total_sanctioned_amount, precision) + flt(
|
||||
self.total_taxes_and_charges, precision
|
||||
amount_with_taxes = flt(
|
||||
(flt(self.total_sanctioned_amount, precision) + flt(self.total_taxes_and_charges, precision)),
|
||||
precision,
|
||||
)
|
||||
|
||||
if flt(self.total_advance_amount, precision) > amount_with_taxes:
|
||||
|
||||
@@ -224,6 +224,7 @@ def delete_employee_work_history(details, employee, date):
|
||||
filters["from_date"] = date
|
||||
if filters:
|
||||
frappe.db.delete("Employee Internal Work History", filters)
|
||||
employee.reload()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -99,7 +99,7 @@ class LoanBalanceAdjustment(AccountsController):
|
||||
loan_account = frappe.db.get_value("Loan", self.loan, "loan_account")
|
||||
remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan)
|
||||
if self.reference_number:
|
||||
remarks += "with reference no. {}".format(self.reference_number)
|
||||
remarks += " with reference no. {}".format(self.reference_number)
|
||||
|
||||
loan_entry = {
|
||||
"account": loan_account,
|
||||
|
||||
@@ -163,11 +163,11 @@
|
||||
},
|
||||
{
|
||||
"fetch_from": "against_loan.disbursement_account",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "disbursement_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Disbursement Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
@@ -185,7 +185,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-17 18:23:44.157598",
|
||||
"modified": "2022-08-04 17:16:04.922444",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Disbursement",
|
||||
|
||||
@@ -209,6 +209,9 @@ def get_disbursal_amount(loan, on_current_security_price=0):
|
||||
"loan_amount",
|
||||
"disbursed_amount",
|
||||
"total_payment",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"total_principal_paid",
|
||||
"total_interest_payable",
|
||||
"status",
|
||||
|
||||
@@ -147,6 +147,9 @@ def make_accrual_interest_entry_for_demand_loans(
|
||||
"name",
|
||||
"total_payment",
|
||||
"total_amount_paid",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"loan_account",
|
||||
"interest_income_account",
|
||||
"loan_amount",
|
||||
|
||||
@@ -281,11 +281,11 @@
|
||||
},
|
||||
{
|
||||
"fetch_from": "against_loan.payment_account",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Repayment Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_36",
|
||||
@@ -311,7 +311,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-18 19:10:07.742298",
|
||||
"modified": "2022-08-04 17:13:51.964203",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
@@ -353,4 +353,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ class LoanRepayment(AccountsController):
|
||||
"status",
|
||||
"is_secured_loan",
|
||||
"total_payment",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"loan_amount",
|
||||
"disbursed_amount",
|
||||
"total_interest_payable",
|
||||
@@ -399,7 +402,7 @@ class LoanRepayment(AccountsController):
|
||||
remarks = "Repayment against loan " + self.against_loan
|
||||
|
||||
if self.reference_number:
|
||||
remarks += "with reference no. {}".format(self.reference_number)
|
||||
remarks += " with reference no. {}".format(self.reference_number)
|
||||
|
||||
if self.repay_from_salary:
|
||||
payment_account = self.payroll_payable_account
|
||||
|
||||
@@ -58,6 +58,9 @@ class LoanSecurityUnpledge(Document):
|
||||
self.loan,
|
||||
[
|
||||
"total_payment",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"total_principal_paid",
|
||||
"loan_amount",
|
||||
"total_interest_payable",
|
||||
|
||||
@@ -9,6 +9,9 @@ from frappe.utils import cint, flt, getdate
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
|
||||
get_pending_principal_amount,
|
||||
)
|
||||
|
||||
|
||||
class LoanWriteOff(AccountsController):
|
||||
@@ -22,16 +25,26 @@ class LoanWriteOff(AccountsController):
|
||||
|
||||
def validate_write_off_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value(
|
||||
|
||||
loan_details = frappe.get_value(
|
||||
"Loan",
|
||||
self.loan,
|
||||
["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"],
|
||||
[
|
||||
"total_payment",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"total_principal_paid",
|
||||
"loan_amount",
|
||||
"total_interest_payable",
|
||||
"written_off_amount",
|
||||
"disbursed_amount",
|
||||
"status",
|
||||
],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
pending_principal_amount = flt(
|
||||
flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount),
|
||||
precision,
|
||||
)
|
||||
pending_principal_amount = flt(get_pending_principal_amount(loan_details), precision)
|
||||
|
||||
if self.write_off_amount > pending_principal_amount:
|
||||
frappe.throw(_("Write off amount cannot be greater than pending principal amount"))
|
||||
|
||||
@@ -372,3 +372,4 @@ erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
|
||||
erpnext.patches.v13_0.reset_corrupt_defaults
|
||||
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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
|
||||
|
||||
for d in accounting_dimensions:
|
||||
doctype = "Asset Repair"
|
||||
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)
|
||||
@@ -622,7 +622,7 @@ class SalarySlip(TransactionBase):
|
||||
self.add_tax_components(payroll_period)
|
||||
|
||||
def add_structure_components(self, component_type):
|
||||
data = self.get_data_for_eval()
|
||||
data, default_data = self.get_data_for_eval()
|
||||
timesheet_component = frappe.db.get_value(
|
||||
"Salary Structure", self.salary_structure, "salary_component"
|
||||
)
|
||||
@@ -633,11 +633,12 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
amount = self.eval_condition_and_formula(struct_row, data)
|
||||
if (
|
||||
amount
|
||||
or (struct_row.amount_based_on_formula and amount is not None)
|
||||
and struct_row.statistical_component == 0
|
||||
):
|
||||
self.update_component_row(struct_row, amount, component_type, data=data)
|
||||
amount or (struct_row.amount_based_on_formula and amount is not None)
|
||||
) and struct_row.statistical_component == 0:
|
||||
default_amount = self.eval_condition_and_formula(struct_row, default_data)
|
||||
self.update_component_row(
|
||||
struct_row, amount, component_type, data=data, default_amount=default_amount
|
||||
)
|
||||
|
||||
def get_data_for_eval(self):
|
||||
"""Returns data for evaluating formula"""
|
||||
@@ -681,11 +682,15 @@ class SalarySlip(TransactionBase):
|
||||
for sc in salary_components:
|
||||
data.setdefault(sc.salary_component_abbr, 0)
|
||||
|
||||
# shallow copy of data to store default amounts (without payment days) for tax calculation
|
||||
default_data = data.copy()
|
||||
|
||||
for key in ("earnings", "deductions"):
|
||||
for d in self.get(key):
|
||||
default_data[d.abbr] = d.default_amount
|
||||
data[d.abbr] = d.amount
|
||||
|
||||
return data
|
||||
return data, default_data
|
||||
|
||||
def eval_condition_and_formula(self, d, data):
|
||||
try:
|
||||
@@ -791,7 +796,14 @@ class SalarySlip(TransactionBase):
|
||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||
|
||||
def update_component_row(
|
||||
self, component_data, amount, component_type, additional_salary=None, is_recurring=0, data=None
|
||||
self,
|
||||
component_data,
|
||||
amount,
|
||||
component_type,
|
||||
additional_salary=None,
|
||||
is_recurring=0,
|
||||
data=None,
|
||||
default_amount=None,
|
||||
):
|
||||
component_row = None
|
||||
for d in self.get(component_type):
|
||||
@@ -852,7 +864,7 @@ class SalarySlip(TransactionBase):
|
||||
additional_salary.deduct_full_tax_on_selected_payroll_date
|
||||
)
|
||||
else:
|
||||
component_row.default_amount = amount
|
||||
component_row.default_amount = default_amount or amount
|
||||
component_row.additional_amount = 0
|
||||
component_row.deduct_full_tax_on_selected_payroll_date = (
|
||||
component_data.deduct_full_tax_on_selected_payroll_date
|
||||
@@ -1285,7 +1297,7 @@ class SalarySlip(TransactionBase):
|
||||
)[0].total_amount
|
||||
|
||||
def calculate_tax_by_tax_slab(self, annual_taxable_earning, tax_slab):
|
||||
data = self.get_data_for_eval()
|
||||
data, default_data = self.get_data_for_eval()
|
||||
data.update({"annual_taxable_earning": annual_taxable_earning})
|
||||
tax_amount = 0
|
||||
for slab in tax_slab.slabs:
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
@@ -35,13 +35,12 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_detail
|
||||
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||
|
||||
|
||||
class TestSalarySlip(unittest.TestCase):
|
||||
class TestSalarySlip(FrappeTestCase):
|
||||
def setUp(self):
|
||||
setup_test()
|
||||
frappe.flags.pop("via_payroll_entry", None)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
@@ -372,13 +371,19 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
self.assertEqual(salary_slip.payment_days, days_in_month - no_of_holidays - 1)
|
||||
|
||||
# gross pay calculation based on attendance (payment days)
|
||||
gross_pay = 78100 - (
|
||||
(78000 / (days_in_month - no_of_holidays))
|
||||
* flt(salary_slip.leave_without_pay + salary_slip.absent_days)
|
||||
# component calculation based on attendance (payment days)
|
||||
amount, precision = None, None
|
||||
|
||||
for row in salary_slip.earnings:
|
||||
if row.salary_component == "Basic Salary":
|
||||
amount = row.amount
|
||||
precision = row.precision("amount")
|
||||
break
|
||||
expected_amount = flt(
|
||||
(50000 * salary_slip.payment_days / salary_slip.total_working_days), precision
|
||||
)
|
||||
|
||||
self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
|
||||
self.assertEqual(amount, expected_amount)
|
||||
|
||||
@change_settings("Payroll Settings", {"payroll_based_on": "Attendance"})
|
||||
def test_component_amount_dependent_on_another_payment_days_based_component(self):
|
||||
@@ -919,6 +924,41 @@ class TestSalarySlip(unittest.TestCase):
|
||||
# undelete fixture data
|
||||
frappe.db.rollback()
|
||||
|
||||
@change_settings(
|
||||
"Payroll Settings",
|
||||
{
|
||||
"payroll_based_on": "Attendance",
|
||||
"consider_unmarked_attendance_as": "Present",
|
||||
"include_holidays_in_total_working_days": True,
|
||||
},
|
||||
)
|
||||
def test_default_amount(self):
|
||||
# Special Allowance (SA) uses another component Basic (BS) in it's formula : BD * .5
|
||||
# Basic has "Depends on Payment Days" enabled
|
||||
# Test default amount for SA is based on default amount for BS (irrespective of PD)
|
||||
# Test amount for SA is based on amount for BS (based on PD)
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
month_start_date = get_first_day(nowdate())
|
||||
joining_date = add_days(month_start_date, 3)
|
||||
employee = make_employee("test_tax_for_mid_joinee@salary.com", date_of_joining=joining_date)
|
||||
|
||||
salary_structure = make_salary_structure(
|
||||
"Stucture to test tax",
|
||||
"Monthly",
|
||||
test_tax=True,
|
||||
from_date=joining_date,
|
||||
employee=employee,
|
||||
)
|
||||
|
||||
ss = make_salary_slip(salary_structure.name, employee=employee)
|
||||
|
||||
# default amount for SA (special allowance = BS*0.5) should be based on default amount for basic
|
||||
self.assertEqual(ss.earnings[2].default_amount, 25000)
|
||||
self.assertEqual(
|
||||
ss.earnings[2].amount, flt(ss.earnings[0].amount * 0.5, ss.earnings[0].precision("amount"))
|
||||
)
|
||||
|
||||
def test_tax_for_recurring_additional_salary(self):
|
||||
frappe.db.sql("""delete from `tabPayroll Period`""")
|
||||
frappe.db.sql("""delete from `tabSalary Component`""")
|
||||
@@ -981,6 +1021,16 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_do_not_show_statistical_component_in_slip(self):
|
||||
make_employee("test_statistical_component@salary.com")
|
||||
new_ss = make_employee_salary_slip(
|
||||
"test_statistical_component@salary.com",
|
||||
"Monthly",
|
||||
"Test Payment Based On Attendence",
|
||||
)
|
||||
components = [row.salary_component for row in new_ss.get("earnings")]
|
||||
self.assertNotIn("Statistical Component", components)
|
||||
|
||||
def make_activity_for_employee(self):
|
||||
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
|
||||
activity_type.billing_rate = 50
|
||||
@@ -1038,7 +1088,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, po
|
||||
def make_salary_component(salary_components, test_tax, company_list=None):
|
||||
for salary_component in salary_components:
|
||||
if frappe.db.exists("Salary Component", salary_component["salary_component"]):
|
||||
continue
|
||||
frappe.delete_doc("Salary Component", salary_component["salary_component"], force=True)
|
||||
|
||||
if test_tax:
|
||||
if salary_component["type"] == "Earning":
|
||||
@@ -1122,6 +1172,13 @@ def make_earning_salary_component(
|
||||
"depends_on_payment_days": 0,
|
||||
},
|
||||
{"salary_component": "Leave Encashment", "abbr": "LE", "type": "Earning"},
|
||||
{
|
||||
"salary_component": "Statistical Component",
|
||||
"abbr": "SC",
|
||||
"type": "Earning",
|
||||
"statistical_component": 1,
|
||||
"amount": 500,
|
||||
},
|
||||
]
|
||||
if include_flexi_benefits:
|
||||
data.extend(
|
||||
@@ -1419,6 +1476,10 @@ def setup_test():
|
||||
"Salary Slip",
|
||||
"Attendance",
|
||||
"Additional Salary",
|
||||
"Employee Tax Exemption Declaration",
|
||||
"Employee Tax Exemption Proof Submission",
|
||||
"Employee Benefit Claim",
|
||||
"Salary Structure Assignment",
|
||||
]:
|
||||
frappe.db.sql("delete from `tab%s`" % dt)
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ def make_salary_structure(
|
||||
currency=erpnext.get_default_currency(),
|
||||
payroll_period=None,
|
||||
include_flexi_benefits=False,
|
||||
base=None,
|
||||
):
|
||||
if test_tax:
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
|
||||
@@ -200,6 +201,7 @@ def make_salary_structure(
|
||||
company=company,
|
||||
currency=currency,
|
||||
payroll_period=payroll_period,
|
||||
base=base,
|
||||
)
|
||||
|
||||
return salary_structure_doc
|
||||
|
||||
@@ -23,24 +23,31 @@ def _execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
columns = get_columns()
|
||||
output_gst_accounts = get_output_gst_accounts(filters.company)
|
||||
|
||||
company_currency = erpnext.get_company_currency(filters.company)
|
||||
item_list = get_items(filters)
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
itemised_tax, tax_columns = get_tax_accounts(
|
||||
item_list, columns, company_currency, output_gst_accounts
|
||||
)
|
||||
|
||||
data = []
|
||||
added_item = []
|
||||
for d in item_list:
|
||||
if (d.parent, d.item_code) not in added_item:
|
||||
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty, d.tax_rate or 0]
|
||||
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
|
||||
total_tax = 0
|
||||
tax_rate = 0
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
||||
if item_tax.get("is_gst_tax"):
|
||||
tax_rate += flt(item_tax.get("tax_rate", 0))
|
||||
total_tax += flt(item_tax.get("tax_amount", 0))
|
||||
|
||||
row += [d.base_net_amount + total_tax]
|
||||
row += [d.base_net_amount]
|
||||
row += [tax_rate]
|
||||
row += [d.taxable_value + total_tax]
|
||||
row += [d.taxable_value]
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
|
||||
row += [item_tax.get("tax_amount", 0)]
|
||||
@@ -51,6 +58,40 @@ def _execute(filters=None):
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_output_gst_accounts(company):
|
||||
|
||||
accounts = frappe.qb.DocType("Account")
|
||||
gst_accounts = frappe.qb.DocType("GST Account")
|
||||
|
||||
accounts_query = (
|
||||
frappe.qb.from_(accounts)
|
||||
.select(accounts.name)
|
||||
.where((accounts.account_type == "Tax") & (accounts.root_type == "Liability"))
|
||||
)
|
||||
|
||||
gst_accounts_query = (
|
||||
frappe.qb.from_(gst_accounts)
|
||||
.select(
|
||||
gst_accounts.cgst_account,
|
||||
gst_accounts.sgst_account,
|
||||
gst_accounts.igst_account,
|
||||
gst_accounts.utgst_account,
|
||||
gst_accounts.cess_account,
|
||||
)
|
||||
.where((gst_accounts.is_reverse_charge_account == 0) & (gst_accounts.company == company))
|
||||
)
|
||||
|
||||
gst_accounts_list = [
|
||||
account for sublist in gst_accounts_query.run() for account in sublist if account
|
||||
]
|
||||
|
||||
tax_accounts_list = [account[0] for account in accounts_query.run() if account]
|
||||
|
||||
output_tax_list = [account for account in gst_accounts_list if account in tax_accounts_list]
|
||||
|
||||
return output_tax_list
|
||||
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
@@ -99,44 +140,24 @@ def get_items(filters):
|
||||
match_conditions = " and {0} ".format(match_conditions)
|
||||
|
||||
items = frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
`tabSales Invoice Item`.gst_hsn_code,
|
||||
`tabSales Invoice Item`.stock_uom,
|
||||
sum(
|
||||
`tabSales Invoice Item`.stock_qty
|
||||
) as stock_qty,
|
||||
sum(
|
||||
`tabSales Invoice Item`.base_net_amount
|
||||
) as base_net_amount,
|
||||
sum(
|
||||
`tabSales Invoice Item`.base_price_list_rate
|
||||
) as base_price_list_rate,
|
||||
|
||||
sum(`tabSales Invoice Item`.stock_qty) AS stock_qty,
|
||||
sum(`tabSales Invoice Item`.taxable_value) AS taxable_value,
|
||||
sum(`tabSales Invoice Item`.base_price_list_rate) AS base_price_list_rate,
|
||||
`tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice Item`.item_code,
|
||||
`tabGST HSN Code`.description,
|
||||
json_extract(
|
||||
`tabSales Taxes and Charges`.item_wise_tax_detail,
|
||||
concat(
|
||||
'$."', `tabSales Invoice Item`.item_code,
|
||||
'"[0]'
|
||||
)
|
||||
) * count(
|
||||
distinct `tabSales Taxes and Charges`.name
|
||||
) as tax_rate
|
||||
`tabGST HSN Code`.description
|
||||
FROM
|
||||
`tabSales Invoice`
|
||||
INNER JOIN
|
||||
`tabSales Invoice Item` ON `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||
INNER JOIN
|
||||
`tabGST HSN Code` ON `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name % s % s
|
||||
LEFT JOIN
|
||||
`tabSales Taxes and Charges` ON `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name
|
||||
INNER JOIN `tabSales Invoice Item` ON `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||
INNER JOIN `tabGST HSN Code` ON `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name
|
||||
WHERE
|
||||
`tabSales Invoice`.docstatus = 1
|
||||
AND
|
||||
`tabSales Invoice Item`.gst_hsn_code is not NULL
|
||||
AND `tabSales Invoice Item`.gst_hsn_code IS NOT NULL
|
||||
{conditions}
|
||||
GROUP BY
|
||||
`tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice Item`.item_code,
|
||||
@@ -145,8 +166,9 @@ def get_items(filters):
|
||||
ORDER BY
|
||||
`tabSales Invoice Item`.gst_hsn_code,
|
||||
`tabSales Invoice Item`.uom
|
||||
"""
|
||||
% (conditions, match_conditions),
|
||||
""".format(
|
||||
conditions=conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -158,6 +180,7 @@ def get_tax_accounts(
|
||||
item_list,
|
||||
columns,
|
||||
company_currency,
|
||||
output_gst_accounts,
|
||||
doctype="Sales Invoice",
|
||||
tax_doctype="Sales Taxes and Charges",
|
||||
):
|
||||
@@ -197,7 +220,7 @@ def get_tax_accounts(
|
||||
|
||||
for parent, account_head, item_wise_tax_detail, tax_amount in tax_details:
|
||||
|
||||
if account_head not in tax_columns and tax_amount:
|
||||
if account_head in output_gst_accounts and account_head not in tax_columns and tax_amount:
|
||||
# as description is text editor earlier and markup can break the column convention in reports
|
||||
tax_columns.append(account_head)
|
||||
|
||||
@@ -210,29 +233,40 @@ def get_tax_accounts(
|
||||
continue
|
||||
itemised_tax.setdefault(item_code, frappe._dict())
|
||||
if isinstance(tax_data, list):
|
||||
tax_rate = 0
|
||||
is_gst_tax = 0
|
||||
if account_head in output_gst_accounts:
|
||||
is_gst_tax = 1
|
||||
tax_rate = tax_data[0]
|
||||
tax_amount = tax_data[1]
|
||||
else:
|
||||
tax_rate = 0
|
||||
tax_amount = 0
|
||||
|
||||
for d in item_row_map.get(parent, {}).get(item_code, []):
|
||||
item_tax_amount = tax_amount
|
||||
if item_tax_amount:
|
||||
itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict(
|
||||
{"tax_amount": flt(item_tax_amount, tax_amount_precision)}
|
||||
{
|
||||
"tax_rate": flt(tax_rate, 2),
|
||||
"is_gst_tax": is_gst_tax,
|
||||
"tax_amount": flt(item_tax_amount, tax_amount_precision),
|
||||
}
|
||||
)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
tax_columns.sort()
|
||||
for account_head in tax_columns:
|
||||
columns.append(
|
||||
{
|
||||
"label": account_head,
|
||||
"fieldname": frappe.scrub(account_head),
|
||||
"fieldtype": "Float",
|
||||
"width": 110,
|
||||
}
|
||||
)
|
||||
if account_head in output_gst_accounts:
|
||||
columns.append(
|
||||
{
|
||||
"label": account_head,
|
||||
"fieldname": frappe.scrub(account_head),
|
||||
"fieldtype": "Float",
|
||||
"width": 110,
|
||||
}
|
||||
)
|
||||
|
||||
return itemised_tax, tax_columns
|
||||
|
||||
|
||||
@@ -161,13 +161,14 @@ erpnext.PointOfSale.Payment = class {
|
||||
|
||||
frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => {
|
||||
const contact = frm.doc.contact_mobile;
|
||||
if (!this.request_for_payment_field) return;
|
||||
const request_button = $(this.request_for_payment_field.$input[0]);
|
||||
if (contact) {
|
||||
request_button.removeClass('btn-default').addClass('btn-primary');
|
||||
} else {
|
||||
request_button.removeClass('btn-primary').addClass('btn-default');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
|
||||
if (frm.doc.coupon_code && !frm.applying_pos_coupon_code) {
|
||||
|
||||
@@ -143,10 +143,6 @@ def get_item_for_list_in_html(context):
|
||||
if (context.get("website_image") or "").startswith("files/"):
|
||||
context["website_image"] = "/" + quote(context["website_image"])
|
||||
|
||||
context["show_availability_status"] = cint(
|
||||
frappe.db.get_single_value("E Commerce Settings", "show_availability_status")
|
||||
)
|
||||
|
||||
products_template = "templates/includes/products_as_list.html"
|
||||
|
||||
return frappe.get_template(products_template).render(context)
|
||||
|
||||
@@ -6,8 +6,6 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.dashboard import cache_source
|
||||
|
||||
from erpnext.stock.utils import get_stock_value_from_bin
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@cache_source
|
||||
@@ -30,26 +28,24 @@ def get(
|
||||
warehouse_filters.append(["company", "=", filters.get("company")])
|
||||
|
||||
warehouses = frappe.get_list(
|
||||
"Warehouse", fields=["name"], filters=warehouse_filters, order_by="name"
|
||||
"Warehouse", pluck="name", filters=warehouse_filters, order_by="name"
|
||||
)
|
||||
|
||||
for wh in warehouses:
|
||||
balance = get_stock_value_from_bin(warehouse=wh.name)
|
||||
wh["balance"] = balance[0][0]
|
||||
|
||||
warehouses = [x for x in warehouses if not (x.get("balance") == None)]
|
||||
warehouses = frappe.get_list(
|
||||
"Bin",
|
||||
fields=["warehouse", "sum(stock_value) stock_value"],
|
||||
filters={"warehouse": ["IN", warehouses], "stock_value": [">", 0]},
|
||||
group_by="warehouse",
|
||||
order_by="stock_value DESC",
|
||||
limit_page_length=10,
|
||||
)
|
||||
|
||||
if not warehouses:
|
||||
return []
|
||||
|
||||
sorted_warehouse_map = sorted(warehouses, key=lambda i: i["balance"], reverse=True)
|
||||
|
||||
if len(sorted_warehouse_map) > 10:
|
||||
sorted_warehouse_map = sorted_warehouse_map[:10]
|
||||
|
||||
for warehouse in sorted_warehouse_map:
|
||||
labels.append(_(warehouse.get("name")))
|
||||
datapoints.append(warehouse.get("balance"))
|
||||
for warehouse in warehouses:
|
||||
labels.append(_(warehouse.get("warehouse")))
|
||||
datapoints.append(warehouse.get("stock_value"))
|
||||
|
||||
return {
|
||||
"labels": labels,
|
||||
|
||||
@@ -635,6 +635,24 @@ class update_entries_after(object):
|
||||
voucher_detail_no=sle.voucher_detail_no,
|
||||
sle=sle,
|
||||
)
|
||||
|
||||
elif (
|
||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||
and sle.actual_qty > 0
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
):
|
||||
sle_details = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": sle.voucher_type,
|
||||
"voucher_no": sle.voucher_no,
|
||||
"dependant_sle_voucher_detail_no": sle.voucher_detail_no,
|
||||
},
|
||||
["stock_value_difference", "actual_qty"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
rate = abs(sle_details.stock_value_difference / sle.actual_qty)
|
||||
else:
|
||||
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||
rate_field = "valuation_rate"
|
||||
|
||||
Reference in New Issue
Block a user