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

chore: release v14
This commit is contained in:
ruthra kumar
2024-12-04 10:04:44 +05:30
committed by GitHub
29 changed files with 463 additions and 133 deletions

View File

@@ -165,6 +165,10 @@ frappe.ui.form.on('Payment Entry', {
filters: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
company: doc.company,
status: ["!=", "Paid"],
outstanding_amount: [">", 0], // for compatibility with old data
docstatus: 1,
},
};
});

View File

@@ -2605,6 +2605,7 @@ def get_open_payment_requests_for_references(references=None):
.where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs)))
.where(PR.status != "Paid")
.where(PR.docstatus == 1)
.where(PR.outstanding_amount > 0) # to avoid old PRs with 0 outstanding amount
.orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc)
).run(as_dict=True)

View File

@@ -848,12 +848,7 @@ def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len,
open_payment_requests = frappe.get_list(
"Payment Request",
filters={
**filters,
"status": ["!=", "Paid"],
"outstanding_amount": ["!=", 0], # for compatibility with old data
"docstatus": 1,
},
filters=filters,
fields=["name", "grand_total", "outstanding_amount"],
order_by="transaction_date ASC,creation ASC",
)

View File

@@ -0,0 +1,14 @@
from frappe import _
def get_data():
return {
"fieldname": "payment_request",
"internal_links": {
"Payment Entry": ["references", "payment_request"],
"Payment Order": ["references", "payment_order"],
},
"transactions": [
{"label": _("Payment"), "items": ["Payment Entry", "Payment Order"]},
],
}

View File

@@ -1,19 +1,18 @@
const INDICATORS = {
"Partially Paid": "orange",
Cancelled: "red",
Draft: "gray",
Failed: "red",
Initiated: "green",
Paid: "blue",
Requested: "green",
};
frappe.listview_settings["Payment Request"] = {
add_fields: ["status"],
get_indicator: function (doc) {
if (doc.status == "Draft") {
return [__("Draft"), "gray", "status,=,Draft"];
}
if (doc.status == "Requested") {
return [__("Requested"), "green", "status,=,Requested"];
} else if (doc.status == "Initiated") {
return [__("Initiated"), "green", "status,=,Initiated"];
} else if (doc.status == "Partially Paid") {
return [__("Partially Paid"), "orange", "status,=,Partially Paid"];
} else if (doc.status == "Paid") {
return [__("Paid"), "blue", "status,=,Paid"];
} else if (doc.status == "Cancelled") {
return [__("Cancelled"), "red", "status,=,Cancelled"];
}
if (!doc.status || !INDICATORS[doc.status]) return;
return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`];
},
};

View File

@@ -48,6 +48,7 @@
"shipping_address",
"company_address",
"company_address_display",
"company_contact_person",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -1557,12 +1558,19 @@
"fieldname": "update_billed_amount_in_delivery_note",
"fieldtype": "Check",
"label": "Update Billed Amount in Delivery Note"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:00:34.268756",
"modified": "2024-11-26 13:10:50.309570",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -1643,6 +1643,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
# Cost of Item is zero in Purchase Receipt
pr = make_purchase_receipt(qty=1, rate=0)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 0)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.rate = 150
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 150)
# Increase the cost of the item
pr = make_purchase_receipt(qty=1, rate=100)

View File

@@ -160,8 +160,9 @@
"dispatch_address",
"company_address_section",
"company_address",
"company_addr_col_break",
"company_address_display",
"company_addr_col_break",
"company_contact_person",
"terms_tab",
"payment_schedule_section",
"ignore_default_payment_terms_template",
@@ -2171,6 +2172,13 @@
"label": "Update Outstanding for Self",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
@@ -2183,7 +2191,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-07-18 15:30:39.428519",
"modified": "2024-11-26 12:34:09.110690",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1556,8 +1556,12 @@ class SalesInvoice(SellingController):
)
def update_project(self):
if self.project:
project = frappe.get_doc("Project", self.project)
unique_projects = list(set([d.project for d in self.get("items") if d.project]))
if self.project and self.project not in unique_projects:
unique_projects.append(self.project)
for p in unique_projects:
project = frappe.get_doc("Project", p)
project.update_billed_amount()
project.db_update()

View File

@@ -3760,6 +3760,102 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
@change_settings("Accounts Settings", {"enable_common_party_accounting": True})
def test_common_party_with_different_currency_in_debtor_and_creditor(self):
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.setup.utils import get_exchange_rate
creditors = create_account(
account_name="Creditors INR",
parent_account="Accounts Payable - _TC",
company="_Test Company",
account_currency="INR",
account_type="Payable",
)
debtors = create_account(
account_name="Debtors USD",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
account_currency="USD",
account_type="Receivable",
)
# create a customer
customer = make_customer(customer="_Test Common Party USD")
cust_doc = frappe.get_doc("Customer", customer)
cust_doc.default_currency = "USD"
test_account_details = {
"company": "_Test Company",
"account": debtors,
}
cust_doc.append("accounts", test_account_details)
cust_doc.save()
# create a supplier
supplier = create_supplier(supplier_name="_Test Common Party INR").name
supp_doc = frappe.get_doc("Supplier", supplier)
supp_doc.default_currency = "INR"
test_account_details = {
"company": "_Test Company",
"account": creditors,
}
supp_doc.append("accounts", test_account_details)
supp_doc.save()
# create a party link between customer & supplier
create_party_link("Supplier", supplier, customer)
# create a sales invoice
si = create_sales_invoice(
customer=customer,
currency="USD",
conversion_rate=get_exchange_rate("USD", "INR"),
debit_to=debtors,
do_not_save=1,
)
si.party_account_currency = "USD"
si.save()
si.submit()
# check outstanding of sales invoice
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(flt(si.outstanding_amount), 0.0)
# check creation of journal entry
jv = frappe.get_all(
"Journal Entry Account",
{
"account": si.debit_to,
"party_type": "Customer",
"party": si.customer,
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="credit_in_account_currency",
)
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
def test_total_billed_amount(self):
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.project_name = "Test Total Billed Amount"
project.save()
si.project = project.name
si.save()
si.submit()
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -29,6 +29,12 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
from erpnext.utilities.regional import temporary_flag
try:
from frappe.contacts.doctype.address.address import render_address as _render_address
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render_address
PURCHASE_TRANSACTION_TYPES = {
"Supplier Quotation",
"Purchase Order",
@@ -985,10 +991,4 @@ def add_party_account(party_type, party, company, account):
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)
return frappe.call(_render_address, address, check_permissions=check_permissions)

View File

@@ -1004,7 +1004,7 @@ class ReceivablePayableReport:
def get_columns(self):
self.columns = []
self.add_column(_("Posting Date"), fieldtype="Date")
self.add_column(_("Posting Date"), fieldname="posting_date", fieldtype="Date")
self.add_column(
label=_("Party Type"),
fieldname="party_type",
@@ -1018,8 +1018,15 @@ class ReceivablePayableReport:
options="party_type",
width=180,
)
if self.account_type == "Receivable":
label = _("Receivable Account")
elif self.account_type == "Payable":
label = _("Payable Account")
else:
label = _("Party Account")
self.add_column(
label=self.account_type + " Account",
label=label,
fieldname="party_account",
fieldtype="Link",
options="Account",
@@ -1057,7 +1064,7 @@ class ReceivablePayableReport:
width=180,
)
self.add_column(label=_("Due Date"), fieldtype="Date")
self.add_column(label=_("Due Date"), fieldname="due_date", fieldtype="Date")
if self.account_type == "Payable":
self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")

View File

@@ -7,6 +7,7 @@ from frappe import _
from frappe.utils import cint, flt
from erpnext.accounts.report.financial_statements import (
compute_growth_view_data,
get_columns,
get_data,
get_filtered_list_for_consolidated_report,
@@ -101,6 +102,9 @@ def execute(filters=None):
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
)
if filters.get("selected_view") == "Growth":
compute_growth_view_data(data, period_list)
return columns, data, message, chart, report_summary, primitive_summary

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
import functools
import math
import re
@@ -653,3 +654,67 @@ def get_filtered_list_for_consolidated_report(filters, period_list):
filtered_summary_list.append(period)
return filtered_summary_list
def compute_growth_view_data(data, columns):
data_copy = copy.deepcopy(data)
for row_idx in range(len(data_copy)):
for column_idx in range(1, len(columns)):
previous_period_key = columns[column_idx - 1].get("key")
current_period_key = columns[column_idx].get("key")
current_period_value = data_copy[row_idx].get(current_period_key)
previous_period_value = data_copy[row_idx].get(previous_period_key)
annual_growth = 0
if current_period_value is None:
data[row_idx][current_period_key] = None
continue
if previous_period_value == 0 and current_period_value > 0:
annual_growth = 1
elif previous_period_value > 0:
annual_growth = (current_period_value - previous_period_value) / previous_period_value
growth_percent = round(annual_growth * 100, 2)
data[row_idx][current_period_key] = growth_percent
def compute_margin_view_data(data, columns, accumulated_values):
if not columns:
return
if not accumulated_values:
columns.append({"key": "total"})
data_copy = copy.deepcopy(data)
base_row = None
for row in data_copy:
if row.get("account_name") == _("Income"):
base_row = row
break
if not base_row:
return
for row_idx in range(len(data_copy)):
# Taking the total income from each column (for all the financial years) as the base (100%)
row = data_copy[row_idx]
if not row:
continue
for column in columns:
curr_period = column.get("key")
base_value = base_row[curr_period]
curr_value = row[curr_period]
if curr_value is None or base_value <= 0:
data[row_idx][curr_period] = None
continue
margin_percent = round((curr_value / base_value) * 100, 2)
data[row_idx][curr_period] = margin_percent

View File

@@ -402,10 +402,10 @@ class GrossProfitGenerator:
self.load_invoice_items()
self.get_delivery_notes()
self.load_product_bundle()
if filters.group_by == "Invoice":
self.group_items_by_invoice()
self.load_product_bundle()
self.load_non_stock_items()
self.get_returned_invoice_items()
self.process()
@@ -617,6 +617,7 @@ class GrossProfitGenerator:
if packed_item.get("parent_detail_docname") == row.item_row:
packed_item_row = row.copy()
packed_item_row.warehouse = packed_item.warehouse
packed_item_row.qty = packed_item.total_qty * -1
buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code)
return flt(buying_amount, self.currency_precision)
@@ -649,7 +650,9 @@ class GrossProfitGenerator:
else:
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
if (row.update_stock or row.dn_detail) and my_sle:
parenttype, parent = row.parenttype, row.parent
parenttype = row.parenttype
parent = row.invoice or row.parent
if row.dn_detail:
parenttype, parent = "Delivery Note", row.delivery_note
@@ -797,6 +800,7 @@ class GrossProfitGenerator:
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
@@ -857,6 +861,7 @@ class GrossProfitGenerator:
"""
grouped = OrderedDict()
product_bundles = self.product_bundles.get("Sales Invoice", {})
for row in self.si_list:
# initialize list with a header row for each new parent
@@ -867,8 +872,7 @@ class GrossProfitGenerator:
)
# if item is a bundle, add it's components as seperate rows
if frappe.db.exists("Product Bundle", row.item_code):
bundled_items = self.get_bundle_items(row)
if bundled_items := product_bundles.get(row.parent, {}).get(row.item_code):
for x in bundled_items:
bundle_item = self.get_bundle_item_row(row, x)
grouped.get(row.parent).append(bundle_item)
@@ -904,47 +908,40 @@ class GrossProfitGenerator:
"item_row": None,
"is_return": row.is_return,
"cost_center": row.cost_center,
"base_net_amount": frappe.db.get_value("Sales Invoice", row.parent, "base_net_total"),
"base_net_amount": row.invoice_base_net_total,
}
)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
)
def get_bundle_item_row(self, product_bundle, item):
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
def get_bundle_item_row(self, row, item):
return frappe._dict(
{
"parent_invoice": product_bundle.item_code,
"indent": product_bundle.indent + 1,
"parent_invoice": row.item_code,
"parenttype": row.parenttype,
"indent": row.indent + 1,
"parent": None,
"invoice_or_item": item.item_code,
"posting_date": product_bundle.posting_date,
"posting_time": product_bundle.posting_time,
"project": product_bundle.project,
"customer": product_bundle.customer,
"customer_group": product_bundle.customer_group,
"posting_date": row.posting_date,
"posting_time": row.posting_time,
"project": row.project,
"customer": row.customer,
"customer_group": row.customer_group,
"item_code": item.item_code,
"item_name": item_name,
"description": description,
"warehouse": product_bundle.warehouse,
"item_group": item_group,
"brand": brand,
"dn_detail": product_bundle.dn_detail,
"delivery_note": product_bundle.delivery_note,
"qty": (flt(product_bundle.qty) * flt(item.qty)),
"item_row": None,
"is_return": product_bundle.is_return,
"cost_center": product_bundle.cost_center,
"item_name": item.item_name,
"description": item.description,
"warehouse": item.warehouse or row.warehouse,
"update_stock": row.update_stock,
"item_group": "",
"brand": "",
"dn_detail": row.dn_detail,
"delivery_note": row.delivery_note,
"qty": item.total_qty * -1,
"item_row": row.item_row,
"is_return": row.is_return,
"cost_center": row.cost_center,
"invoice": row.parent,
}
)
def get_bundle_item_details(self, item_code):
return frappe.db.get_value("Item", item_code, ["item_name", "description", "item_group", "brand"])
def get_stock_ledger_entries(self, item_code, warehouse):
if item_code and warehouse:
if (item_code, warehouse) not in self.sle:

View File

@@ -7,6 +7,8 @@ from frappe import _
from frappe.utils import flt
from erpnext.accounts.report.financial_statements import (
compute_growth_view_data,
compute_margin_view_data,
get_columns,
get_data,
get_filtered_list_for_consolidated_report,
@@ -68,6 +70,12 @@ def execute(filters=None):
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
)
if filters.get("selected_view") == "Growth":
compute_growth_view_data(data, period_list)
if filters.get("selected_view") == "Margin":
compute_margin_view_data(data, period_list, filters.accumulated_values)
return columns, data, None, chart, report_summary, primitive_summary

View File

@@ -2313,6 +2313,12 @@ class AccountsController(TransactionBase):
secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
primary_account_currency = get_account_currency(primary_account)
secondary_account_currency = get_account_currency(secondary_account)
default_currency = erpnext.get_company_currency(self.company)
# Determine if multi-currency journal entry is needed
multi_currency = (
primary_account_currency != default_currency or secondary_account_currency != default_currency
)
jv = frappe.new_doc("Journal Entry")
jv.voucher_type = "Journal Entry"
@@ -2337,7 +2343,7 @@ class AccountsController(TransactionBase):
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
advance_entry.is_advance = "Yes"
# update dimesions
# Update dimensions
dimensions_dict = frappe._dict()
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
@@ -2346,17 +2352,58 @@ class AccountsController(TransactionBase):
reconcilation_entry.update(dimensions_dict)
advance_entry.update(dimensions_dict)
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
# Calculate exchange rates if necessary
if multi_currency:
# Exchange rates for primary and secondary accounts
exc_rate_primary_to_default = (
1
if primary_account_currency == default_currency
else get_exchange_rate(primary_account_currency, default_currency, self.posting_date)
)
exc_rate_secondary_to_default = (
1
if secondary_account_currency == default_currency
else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date)
)
exc_rate_secondary_to_primary = (
1
if secondary_account_currency == primary_account_currency
else get_exchange_rate(
secondary_account_currency, primary_account_currency, self.posting_date
)
)
# Convert outstanding amount from secondary to primary account currency, if needed
os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default
os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary
if self.doctype == "Sales Invoice":
# Calculate credit and debit values for reconciliation and advance entries
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.credit = os_in_default_currency
advance_entry.debit_in_account_currency = os_in_primary_currency
advance_entry.debit = os_in_default_currency
else:
advance_entry.credit_in_account_currency = os_in_primary_currency
advance_entry.credit = os_in_default_currency
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit = os_in_default_currency
# Set exchange rates for entries
reconcilation_entry.exchange_rate = exc_rate_secondary_to_default
advance_entry.exchange_rate = exc_rate_primary_to_default
else:
advance_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
default_currency = erpnext.get_company_currency(self.company)
if primary_account_currency != default_currency or secondary_account_currency != default_currency:
jv.multi_currency = 1
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
else:
advance_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
jv.multi_currency = multi_currency
jv.append("accounts", reconcilation_entry)
jv.append("accounts", advance_entry)

View File

@@ -68,19 +68,13 @@ class SellingController(StockController):
if customer:
from erpnext.accounts.party import _get_party_details
fetch_payment_terms_template = False
if self.get("__islocal") or self.company != frappe.db.get_value(
self.doctype, self.name, "company"
):
fetch_payment_terms_template = True
party_details = _get_party_details(
customer,
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype,
company=self.company,
posting_date=self.get("posting_date"),
fetch_payment_terms_template=fetch_payment_terms_template,
fetch_payment_terms_template=self.has_value_changed("company"),
party_address=self.customer_address,
shipping_address=self.shipping_address_name,
company_address=self.get("company_address"),
@@ -365,12 +359,32 @@ class SellingController(StockController):
return il
def has_product_bundle(self, item_code):
product_bundle = frappe.qb.DocType("Product Bundle")
return (
frappe.qb.from_(product_bundle)
.select(product_bundle.name)
.where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
).run()
product_bundle_items = getattr(self, "_product_bundle_items", None)
if product_bundle_items is None:
self._product_bundle_items = product_bundle_items = {}
if item_code not in product_bundle_items:
self._fetch_product_bundle_items(item_code)
return product_bundle_items[item_code]
def _fetch_product_bundle_items(self, item_code):
product_bundle_items = self._product_bundle_items
items_to_fetch = {row.item_code for row in self.items if row.item_code not in product_bundle_items}
# fetch for requisite item_code even if it is not in items
items_to_fetch.add(item_code)
items_with_product_bundle = {
row.new_item_code
for row in frappe.get_all(
"Product Bundle",
filters={"new_item_code": ("in", items_to_fetch), "disabled": 0},
fields="new_item_code",
)
}
for item_code in items_to_fetch:
product_bundle_items[item_code] = item_code in items_with_product_bundle
def get_already_delivered_qty(self, current_docname, so, so_detail):
delivered_via_dn = frappe.db.sql(

View File

@@ -89,10 +89,18 @@ class WorkOrder(Document):
self.status = self.get_status()
self.validate_workstation_type()
if self.source_warehouse:
self.set_warehouses()
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
self.set_required_items(reset_only_qty=len(self.get("required_items")))
def set_warehouses(self):
for row in self.required_items:
if not row.source_warehouse:
row.source_warehouse = self.source_warehouse
def validate_workstation_type(self):
for row in self.operations:
if not row.workstation and not row.workstation_type:

View File

@@ -421,7 +421,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
setup_sms() {
var me = this;
let blacklist = ['Purchase Invoice', 'BOM'];
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
if(frappe.boot.sms_gateway_enabled && this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
&& !blacklist.includes(this.frm.doctype)) {
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
}
@@ -990,7 +990,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
apply_discount_on_item(doc, cdt, cdn, field) {
var item = frappe.get_doc(cdt, cdn);
if(!item.price_list_rate) {
if(item && !item.price_list_rate) {
item[field] = 0.0;
} else {
this.price_list_rate(doc, cdt, cdn);

View File

@@ -9,40 +9,29 @@ erpnext.financial_statements = {
data &&
column.colIndex >= 3
) {
//Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns.
const lastAnnualValue = row[column.colIndex - 1].content;
const currentAnnualvalue = data[column.fieldname];
if (currentAnnualvalue == undefined) return "NA"; //making this not applicable for undefined/null values
let annualGrowth = 0;
if (lastAnnualValue == 0 && currentAnnualvalue > 0) {
//If the previous year value is 0 and the current value is greater than 0
annualGrowth = 1;
} else if (lastAnnualValue > 0) {
annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue;
}
const growthPercent = data[column.fieldname];
const growthPercent = Math.round(annualGrowth * 10000) / 100; //calculating the rounded off percentage
if (growthPercent == undefined) return "NA"; //making this not applicable for undefined/null values
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
if (growthPercent < 0) {
value = $(value).addClass("text-danger");
if (column.fieldname === "total") {
value = $(`<span>${growthPercent}</span>`);
} else {
value = $(value).addClass("text-success");
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
if (growthPercent < 0) {
value = $(value).addClass("text-danger");
} else {
value = $(value).addClass("text-success");
}
}
value = $(value).wrap("<p></p>").parent().html();
return value;
} else if (frappe.query_report.get_filter_value("selected_view") == "Margin" && data) {
if (column.fieldname == "account" && data.account_name == __("Income")) {
//Taking the total income from each column (for all the financial years) as the base (100%)
this.baseData = row;
}
if (column.colIndex >= 2) {
//Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns.
const currentAnnualvalue = data[column.fieldname];
const baseValue = this.baseData[column.colIndex].content;
if (currentAnnualvalue == undefined || baseValue <= 0) return "NA";
const marginPercent = Math.round((currentAnnualvalue / baseValue) * 10000) / 100;
const marginPercent = data[column.fieldname];
if (marginPercent == undefined) return "NA"; //making this not applicable for undefined/null values
value = $(`<span>${marginPercent + "%"}</span>`);
if (marginPercent < 0) value = $(value).addClass("text-danger");

View File

@@ -64,6 +64,17 @@ $.extend(erpnext.queries, {
}
},
company_contact_query: function (doc) {
if (!doc.company) {
frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))]));
}
return {
query: "frappe.contacts.doctype.contact.contact.contact_query",
filters: { link_doctype: "Company", link_name: doc.company },
};
},
address_query: function (doc) {
if (frappe.dynamic_link) {
if (!doc[frappe.dynamic_link.fieldname]) {

View File

@@ -95,8 +95,9 @@
"shipping_address",
"company_address_section",
"company_address",
"column_break_87",
"company_address_display",
"column_break_87",
"company_contact_person",
"terms_tab",
"payment_schedule_section",
"payment_terms_template",
@@ -1066,13 +1067,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-shopping-cart",
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:04:21.567847",
"modified": "2024-11-26 12:43:29.293637",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",

View File

@@ -455,7 +455,9 @@ def _make_customer(source_name, ignore_permissions=False):
raise
except frappe.MandatoryError as e:
mandatory_fields = e.args[0].split(":")[1].split(",")
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
mandatory_fields = [
_(customer.meta.get_label(field.strip())) for field in mandatory_fields
]
frappe.local.message_log = []
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)

View File

@@ -112,8 +112,9 @@
"dispatch_address",
"col_break46",
"company_address",
"column_break_92",
"company_address_display",
"column_break_92",
"company_contact_person",
"payment_schedule_section",
"payment_terms_section",
"payment_terms_template",
@@ -1626,13 +1627,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-05-23 16:35:54.905804",
"modified": "2024-11-26 12:42:06.872527",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1711,4 +1719,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -41,6 +41,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
me.frm.set_query('company_address', erpnext.queries.company_address_query);
me.frm.set_query('company_contact_person', erpnext.queries.company_contact_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);

View File

@@ -109,8 +109,9 @@
"dispatch_address",
"company_address_section",
"company_address",
"column_break_101",
"company_address_display",
"column_break_101",
"company_contact_person",
"terms_tab",
"tc_name",
"terms",
@@ -1395,13 +1396,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:05:02.854990",
"modified": "2024-11-26 12:44:28.258215",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -907,7 +907,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
if adjust_incoming_rate:
adjusted_amt = 0.0
if item.billed_amt and item.amount:
if item.billed_amt is not None and item.amount is not None:
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)

View File

@@ -194,11 +194,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
if isinstance(qty_fields, str):
qty_fields = [qty_fields]
distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
integer_uoms = list(
filter(
lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None,
distinct_uoms,
distinct_uoms = tuple(set(uom for uom in (d.get(uom_field) for d in doc.get_all_children()) if uom))
integer_uoms = set(
d[0]
for d in frappe.db.get_values(
"UOM", (("name", "in", distinct_uoms), ("must_be_whole_number", "=", 1)), cache=True
)
)