mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-26 18:18:30 +00:00
Merge pull request #49697 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_break_8",
|
||||
"rate",
|
||||
"section_break_9",
|
||||
@@ -92,6 +93,13 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
|
||||
@@ -137,8 +137,10 @@ def get_payment_entries_for_bank_clearance(
|
||||
entries = []
|
||||
|
||||
condition = ""
|
||||
pe_condition = ""
|
||||
if not include_reconciled_entries:
|
||||
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
||||
pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')"
|
||||
|
||||
journal_entries = frappe.db.sql(
|
||||
f"""
|
||||
@@ -163,19 +165,20 @@ def get_payment_entries_for_bank_clearance(
|
||||
payment_entries = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no as cheque_number, reference_date as cheque_date,
|
||||
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
||||
if(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit,
|
||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry`
|
||||
"Payment Entry" as payment_document, pe.name as payment_entry,
|
||||
pe.reference_no as cheque_number, pe.reference_date as cheque_date,
|
||||
if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit,
|
||||
if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit,
|
||||
pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date,
|
||||
if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry` as pe
|
||||
join `tabCompany` c on c.name = pe.company
|
||||
where
|
||||
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
||||
and posting_date >= %(from)s and posting_date <= %(to)s
|
||||
{condition}
|
||||
(pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1
|
||||
and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s
|
||||
{pe_condition}
|
||||
order by
|
||||
posting_date ASC, name DESC
|
||||
pe.posting_date ASC, pe.name DESC
|
||||
""",
|
||||
{
|
||||
"account": account,
|
||||
|
||||
@@ -131,18 +131,20 @@ class GLEntry(Document):
|
||||
|
||||
if not self.is_cancelled and not (self.party_type and self.party):
|
||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||
if account_type == "Receivable":
|
||||
frappe.throw(
|
||||
_("{0} {1}: Customer is required against Receivable account {2}").format(
|
||||
self.voucher_type, self.voucher_no, self.account
|
||||
# skipping validation for payroll entry creation in case party is not required
|
||||
if not frappe.flags.party_not_required_for_receivable_payable:
|
||||
if account_type == "Receivable":
|
||||
frappe.throw(
|
||||
_("{0} {1}: Customer is required against Receivable account {2}").format(
|
||||
self.voucher_type, self.voucher_no, self.account
|
||||
)
|
||||
)
|
||||
)
|
||||
elif account_type == "Payable":
|
||||
frappe.throw(
|
||||
_("{0} {1}: Supplier is required against Payable account {2}").format(
|
||||
self.voucher_type, self.voucher_no, self.account
|
||||
elif account_type == "Payable":
|
||||
frappe.throw(
|
||||
_("{0} {1}: Supplier is required against Payable account {2}").format(
|
||||
self.voucher_type, self.voucher_no, self.account
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Zero value transaction is not allowed
|
||||
if not (
|
||||
|
||||
@@ -542,8 +542,11 @@ class JournalEntry(AccountsController):
|
||||
def validate_party(self):
|
||||
for d in self.get("accounts"):
|
||||
account_type = frappe.get_cached_value("Account", d.account, "account_type")
|
||||
|
||||
# skipping validation for payroll entry creation
|
||||
skip_validation = frappe.flags.party_not_required_for_receivable_payable
|
||||
if account_type in ["Receivable", "Payable"]:
|
||||
if not (d.party_type and d.party):
|
||||
if not (d.party_type and d.party) and not skip_validation:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"help_section",
|
||||
"loyalty_program_help"
|
||||
],
|
||||
@@ -143,6 +144,12 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"modified": "2019-05-26 09:11:46.120251",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_break_4",
|
||||
"invoices"
|
||||
],
|
||||
@@ -62,6 +63,12 @@
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"sec_break1",
|
||||
"invoice_name",
|
||||
"invoices",
|
||||
@@ -193,6 +194,12 @@
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party",
|
||||
"description": "Only 'Payment Entries' made against this advance account are supported.",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
|
||||
@@ -392,6 +393,12 @@ class PaymentReconciliation(Document):
|
||||
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
|
||||
|
||||
def get_difference_amount(self, payment_entry, invoice, allocated_amount):
|
||||
allocated_amount_precision = get_field_precision(
|
||||
frappe.get_meta("Payment Reconciliation Allocation").get_field("allocated_amount")
|
||||
)
|
||||
difference_amount_precision = get_field_precision(
|
||||
frappe.get_meta("Payment Reconciliation Allocation").get_field("difference_amount")
|
||||
)
|
||||
difference_amount = 0
|
||||
if frappe.get_cached_value(
|
||||
"Account", self.receivable_payable_account, "account_currency"
|
||||
@@ -399,8 +406,14 @@ class PaymentReconciliation(Document):
|
||||
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
|
||||
"exchange_rate", 1
|
||||
):
|
||||
allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
|
||||
allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
|
||||
allocated_amount_in_ref_rate = flt(
|
||||
payment_entry.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
|
||||
difference_amount_precision,
|
||||
)
|
||||
allocated_amount_in_inv_rate = flt(
|
||||
invoice.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
|
||||
difference_amount_precision,
|
||||
)
|
||||
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
|
||||
|
||||
return difference_amount
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_break_9",
|
||||
"account_currency",
|
||||
"tax_amount",
|
||||
@@ -211,6 +212,13 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"section_break_8",
|
||||
"rate",
|
||||
"section_break_9",
|
||||
@@ -188,6 +189,13 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"shipping_amount_section",
|
||||
"calculate_based_on",
|
||||
"column_break_8",
|
||||
@@ -136,6 +137,12 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-truck",
|
||||
|
||||
@@ -164,6 +164,12 @@
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
<div class="show-filters">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
<hr>
|
||||
{% endif %}
|
||||
</div>
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr>
|
||||
|
||||
@@ -974,6 +974,7 @@ class ReceivablePayableReport:
|
||||
|
||||
if self.account_type == "Receivable":
|
||||
self.add_customer_filters()
|
||||
self.exclude_employee_transaction()
|
||||
|
||||
elif self.account_type == "Payable":
|
||||
self.add_supplier_filters()
|
||||
@@ -1053,6 +1054,9 @@ class ReceivablePayableReport:
|
||||
)
|
||||
)
|
||||
|
||||
def exclude_employee_transaction(self):
|
||||
self.qb_selection_filter.append(self.ple.party_type != "Employee")
|
||||
|
||||
def add_supplier_filters(self):
|
||||
supplier = qb.DocType("Supplier")
|
||||
if self.filters.get("supplier_group"):
|
||||
|
||||
@@ -38,6 +38,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
|
||||
get_report_summary as get_pl_summary,
|
||||
)
|
||||
from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
|
||||
from erpnext.accounts.utils import get_zero_cutoff
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -563,7 +564,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
|
||||
|
||||
row[company] = flt(d.get(company, 0.0), 3)
|
||||
|
||||
if abs(row[company]) >= 0.005:
|
||||
if abs(row[company]) >= get_zero_cutoff(filters.presentation_currency):
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
total += flt(row[company])
|
||||
|
||||
@@ -12,6 +12,7 @@ from erpnext.accounts.report.financial_statements import (
|
||||
filter_out_zero_value_rows,
|
||||
)
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||
from erpnext.accounts.utils import get_zero_cutoff
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -154,7 +155,7 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
|
||||
for dimension in dimension_list:
|
||||
row[frappe.scrub(dimension)] = flt(d.get(frappe.scrub(dimension), 0.0), 3)
|
||||
|
||||
if abs(row[frappe.scrub(dimension)]) >= 0.005:
|
||||
if abs(row[frappe.scrub(dimension)]) >= get_zero_cutoff(company_currency):
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
total += flt(d.get(frappe.scrub(dimension), 0.0), 3)
|
||||
|
||||
@@ -34,6 +34,12 @@
|
||||
</h5>
|
||||
{% } %}
|
||||
<hr>
|
||||
<div class="show-filters">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
<hr>
|
||||
{% endif %}
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.accounts.utils import get_fiscal_year, get_zero_cutoff
|
||||
|
||||
|
||||
def get_period_list(
|
||||
@@ -304,7 +304,7 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency, accum
|
||||
|
||||
row[period.key] = flt(d.get(period.key, 0.0), 3)
|
||||
|
||||
if abs(row[period.key]) >= 0.005:
|
||||
if abs(row[period.key]) >= get_zero_cutoff(company_currency):
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
total += flt(row[period.key])
|
||||
|
||||
@@ -21,6 +21,12 @@
|
||||
{%= frappe.datetime.str_to_user(filters.to_date) %}
|
||||
</h5>
|
||||
<hr>
|
||||
<div class="show-filters">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
<hr>
|
||||
{% endif %}
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -645,7 +645,7 @@ def get_columns(filters):
|
||||
"options": "GL Entry",
|
||||
"hidden": 1,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||
{
|
||||
"label": _("Account"),
|
||||
"fieldname": "account",
|
||||
|
||||
@@ -12,6 +12,7 @@ from erpnext.accounts.report.financial_statements import (
|
||||
filter_out_zero_value_rows,
|
||||
)
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||
from erpnext.accounts.utils import get_zero_cutoff
|
||||
|
||||
value_fields = ("income", "expense", "gross_profit_loss")
|
||||
|
||||
@@ -149,7 +150,7 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
|
||||
for key in value_fields:
|
||||
row[key] = flt(d.get(key, 0.0), 3)
|
||||
|
||||
if abs(row[key]) >= 0.005:
|
||||
if abs(row[key]) >= get_zero_cutoff(company_currency):
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from erpnext.accounts.report.financial_statements import (
|
||||
set_gl_entries_by_account,
|
||||
)
|
||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
||||
from erpnext.accounts.utils import get_zero_cutoff
|
||||
|
||||
value_fields = (
|
||||
"opening_debit",
|
||||
@@ -413,7 +414,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
|
||||
for key in value_fields:
|
||||
row[key] = flt(d.get(key, 0.0), 3)
|
||||
|
||||
if abs(row[key]) >= 0.005:
|
||||
if abs(row[key]) >= get_zero_cutoff(company_currency):
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from erpnext.accounts.party import get_party_shipping_address
|
||||
from erpnext.accounts.utils import (
|
||||
get_future_stock_vouchers,
|
||||
get_voucherwise_gl_entries,
|
||||
get_zero_cutoff,
|
||||
sort_stock_vouchers_by_posting_date,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
@@ -156,6 +157,11 @@ class TestUtils(unittest.TestCase):
|
||||
self.assertSequenceEqual(doc_name[0:2], ("SUP", fiscal_year))
|
||||
frappe.db.set_default("supp_master_name", "Supplier Name")
|
||||
|
||||
def test_get_zero_cutoff(self):
|
||||
self.assertEqual(get_zero_cutoff(None), 0.005)
|
||||
self.assertEqual(get_zero_cutoff("EUR"), 0.005)
|
||||
self.assertEqual(get_zero_cutoff("BHD"), 0.0005)
|
||||
|
||||
|
||||
ADDRESS_RECORDS = [
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ from frappe.utils import (
|
||||
now,
|
||||
nowdate,
|
||||
)
|
||||
from frappe.utils.caching import site_cache
|
||||
from pypika import Order
|
||||
from pypika.functions import Coalesce
|
||||
from pypika.terms import ExistsCriterion
|
||||
@@ -1130,6 +1131,29 @@ def get_currency_precision():
|
||||
return precision
|
||||
|
||||
|
||||
def get_fraction_units(currency: str) -> int:
|
||||
"""Returns the number of fraction units for a currency."""
|
||||
fraction_units = frappe.db.get_value("Currency", currency, "fraction_units")
|
||||
|
||||
if fraction_units is None:
|
||||
fraction_units = 100
|
||||
|
||||
return fraction_units
|
||||
|
||||
|
||||
@site_cache()
|
||||
def get_zero_cutoff(currency: str) -> float:
|
||||
"""Returns the zero cutoff for a currency.
|
||||
|
||||
For example, if the Fraction Units for a currency are set to 100, then the zero cutoff is 0.005.
|
||||
We don't want to display values less than the zero cutoff.
|
||||
This value was chosen for compatibility with the previous hard-coded value of 0.005.
|
||||
"""
|
||||
fraction_units = get_fraction_units(currency)
|
||||
|
||||
return 0.5 / (fraction_units or 1)
|
||||
|
||||
|
||||
def get_held_invoices(party_type, party):
|
||||
"""
|
||||
Returns a list of names Purchase Invoices for the given party that are on hold
|
||||
@@ -2451,6 +2475,10 @@ def build_qb_match_conditions(doctype, user=None) -> list:
|
||||
for filter in match_filters:
|
||||
for link_option, allowed_values in filter.items():
|
||||
fieldnames = link_fields_map.get(link_option, [])
|
||||
cond = None
|
||||
|
||||
if link_option == doctype:
|
||||
cond = _dt["name"].isin(allowed_values)
|
||||
|
||||
for fieldname in fieldnames:
|
||||
field = _dt[fieldname]
|
||||
@@ -2459,6 +2487,7 @@ def build_qb_match_conditions(doctype, user=None) -> list:
|
||||
if not apply_strict_user_permissions:
|
||||
cond = (Coalesce(field, "") == "") | cond
|
||||
|
||||
if cond:
|
||||
criterion.append(cond)
|
||||
|
||||
return criterion
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"target_fixed_asset_account"
|
||||
],
|
||||
"fields": [
|
||||
@@ -288,6 +289,12 @@
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"fixed_asset_account"
|
||||
],
|
||||
"fields": [
|
||||
@@ -98,6 +99,13 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -118,7 +118,8 @@
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate"
|
||||
"label": "Rate",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
@@ -161,7 +162,8 @@
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_yuca",
|
||||
@@ -183,13 +185,15 @@
|
||||
"fieldname": "base_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Amount"
|
||||
"label": "Base Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Rate"
|
||||
"label": "Base Rate",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
||||
@@ -6,7 +6,10 @@ def execute():
|
||||
"POS Invoice Merge Log", {"docstatus": 1}, ["name", "pos_closing_entry"]
|
||||
)
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
for log in pos_invoice_merge_logs:
|
||||
if log.pos_closing_entry and frappe.db.exists("POS Closing Entry", log.pos_closing_entry):
|
||||
company = frappe.db.get_value("POS Closing Entry", log.pos_closing_entry, "company")
|
||||
frappe.db.set_value("POS Invoice Merge Log", log.name, "company", company)
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = 0
|
||||
|
||||
@@ -931,7 +931,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
party_name = me.frm.doc.party_name
|
||||
}
|
||||
else{
|
||||
party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
|
||||
party_type = frappe.meta.has_field(me.frm.doc.doctype, "supplier") ? "Supplier" : "Customer";
|
||||
party_name = me.frm.doc[party_type.toLowerCase()];
|
||||
}
|
||||
if (party_name) {
|
||||
|
||||
@@ -1762,6 +1762,11 @@ def create_pick_list(source_name, target_doc=None):
|
||||
target.qty = qty_to_be_picked
|
||||
target.stock_qty = qty_to_be_picked * flt(source.conversion_factor)
|
||||
|
||||
# update available qty
|
||||
bin_details = get_bin_details(source.item_code, source.warehouse, source_parent.company)
|
||||
target.actual_qty = bin_details.get("actual_qty")
|
||||
target.company_total_stock = bin_details.get("company_total_stock")
|
||||
|
||||
def update_packed_item_qty(source, target, source_parent) -> None:
|
||||
qty = flt(source.qty)
|
||||
for item in source_parent.items:
|
||||
|
||||
@@ -5,8 +5,10 @@ import unittest
|
||||
|
||||
import frappe
|
||||
import frappe.utils
|
||||
from frappe.query_builder import Criterion
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import build_qb_match_conditions
|
||||
from erpnext.setup.doctype.employee.employee import InactiveEmployeeStatusError
|
||||
|
||||
test_records = frappe.get_test_records("Employee")
|
||||
@@ -34,6 +36,32 @@ class TestEmployee(unittest.TestCase):
|
||||
employee_doc.save()
|
||||
self.assertTrue("Employee" not in frappe.get_roles(user))
|
||||
|
||||
def test_employee_user_permission(self):
|
||||
employee1 = make_employee("employee_1_test@company.com", create_user_permission=1)
|
||||
employee2 = make_employee("employee_2_test@company.com", create_user_permission=1)
|
||||
make_employee("employee_3_test@company.com", create_user_permission=1)
|
||||
|
||||
employee1_doc = frappe.get_doc("Employee", employee1)
|
||||
employee2_doc = frappe.get_doc("Employee", employee2)
|
||||
|
||||
employee2_doc.reload()
|
||||
employee2_doc.reports_to = employee1_doc.name
|
||||
employee2_doc.save()
|
||||
|
||||
frappe.set_user(employee1_doc.user_id)
|
||||
|
||||
Employee = frappe.qb.DocType("Employee")
|
||||
qb_employee_list = (
|
||||
frappe.qb.from_(Employee)
|
||||
.select(Employee.name)
|
||||
.where(Criterion.all(build_qb_match_conditions("Employee")))
|
||||
.orderby(Employee.Name)
|
||||
).run(pluck=Employee.name)
|
||||
employee_list = frappe.db.get_list("Employee", pluck="name", order_by="name")
|
||||
|
||||
self.assertEqual(qb_employee_list, employee_list)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
|
||||
@@ -218,6 +218,7 @@ def get_batch_qty(
|
||||
batch_no=None,
|
||||
warehouse=None,
|
||||
item_code=None,
|
||||
creation=None,
|
||||
posting_date=None,
|
||||
posting_time=None,
|
||||
ignore_voucher_nos=None,
|
||||
@@ -244,6 +245,7 @@ def get_batch_qty(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"creation": creation,
|
||||
"posting_date": posting_date,
|
||||
"posting_time": posting_time,
|
||||
"batch_no": batch_no,
|
||||
|
||||
@@ -724,7 +724,10 @@ class Item(Document):
|
||||
|
||||
item_defaults = frappe.db.get_values(
|
||||
"Item Default",
|
||||
{"parent": self.item_group},
|
||||
{
|
||||
"parent": self.item_group,
|
||||
"parenttype": "Item Group",
|
||||
},
|
||||
[
|
||||
"company",
|
||||
"default_warehouse",
|
||||
|
||||
@@ -354,10 +354,12 @@ frappe.ui.form.on("Pick List Item", {
|
||||
item_code: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (row.item_code) {
|
||||
get_item_details(row.item_code).then((data) => {
|
||||
get_item_details(row.item_code, row.uom, row.warehouse, frm.doc.company).then((data) => {
|
||||
frappe.model.set_value(cdt, cdn, "uom", data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, "stock_uom", data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, "conversion_factor", 1);
|
||||
frappe.model.set_value(cdt, cdn, "actual_qty", data.actual_qty);
|
||||
frappe.model.set_value(cdt, cdn, "company_total_stock", data.company_total_stock);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -371,6 +373,15 @@ frappe.ui.form.on("Pick List Item", {
|
||||
}
|
||||
},
|
||||
|
||||
warehouse: (frm, cdt, cdn) => {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
if (!row.item_code || !row.warehouse) return;
|
||||
get_item_details(row.item_code, row.uom, row.warehouse, frm.doc.company).then((data) => {
|
||||
frappe.model.set_value(cdt, cdn, "actual_qty", data.actual_qty);
|
||||
frappe.model.set_value(cdt, cdn, "company_total_stock", data.company_total_stock);
|
||||
});
|
||||
},
|
||||
|
||||
qty: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.set_value(cdt, cdn, "stock_qty", row.qty * row.conversion_factor);
|
||||
@@ -412,11 +423,13 @@ frappe.ui.form.on("Pick List Item", {
|
||||
},
|
||||
});
|
||||
|
||||
function get_item_details(item_code, uom = null) {
|
||||
function get_item_details(item_code, uom = null, warehouse = null, company = null) {
|
||||
if (item_code) {
|
||||
return frappe.xcall("erpnext.stock.doctype.pick_list.pick_list.get_item_details", {
|
||||
item_code,
|
||||
uom,
|
||||
warehouse,
|
||||
company,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle impor
|
||||
get_auto_batch_nos,
|
||||
get_picked_serial_nos,
|
||||
)
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.get_item_details import get_company_total_stock, get_conversion_factor
|
||||
from erpnext.stock.serial_batch_bundle import (
|
||||
SerialBatchCreation,
|
||||
get_batches_from_bundle,
|
||||
@@ -74,6 +74,9 @@ class PickList(TransactionBase):
|
||||
if self.has_reserved_stock():
|
||||
self.set_onload("has_reserved_stock", True)
|
||||
|
||||
for item in self.get("locations"):
|
||||
item.update(get_item_details(item.item_code, item.uom, item.warehouse, self.company))
|
||||
|
||||
def validate(self):
|
||||
self.validate_expired_batches()
|
||||
self.validate_for_qty()
|
||||
@@ -1442,15 +1445,29 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item_code, uom=None):
|
||||
def get_item_details(item_code, uom=None, warehouse=None, company=None):
|
||||
details = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
|
||||
details.uom = uom or details.stock_uom
|
||||
if uom:
|
||||
details.update(get_conversion_factor(item_code, uom))
|
||||
|
||||
if warehouse:
|
||||
details.actual_qty = flt(get_actual_qty(item_code, warehouse))
|
||||
|
||||
if company:
|
||||
details.company_total_stock = get_company_total_stock(item_code, company)
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def get_actual_qty(item_code, warehouse):
|
||||
return frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": item_code, "warehouse": warehouse},
|
||||
"actual_qty",
|
||||
)
|
||||
|
||||
|
||||
def update_delivery_note_item(source, target, delivery_note):
|
||||
cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
|
||||
if not cost_center:
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
"conversion_factor",
|
||||
"stock_uom",
|
||||
"delivered_qty",
|
||||
"available_quantity_section",
|
||||
"actual_qty",
|
||||
"column_break_kyek",
|
||||
"company_total_stock",
|
||||
"serial_no_and_batch_section",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
@@ -124,7 +128,7 @@
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Stock Qty",
|
||||
"label": "Qty (in Stock UOM)",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -248,11 +252,38 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "available_quantity_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Available Qty"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Warehouse)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_kyek",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty (Company)",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-31 19:57:43.531298",
|
||||
"modified": "2025-09-23 00:02:57.817040",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
|
||||
@@ -15,7 +15,9 @@ class PickListItem(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
actual_qty: DF.Float
|
||||
batch_no: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
delivered_qty: DF.Float
|
||||
description: DF.Text | None
|
||||
|
||||
@@ -2360,6 +2360,16 @@ def get_available_batches(kwargs):
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
if kwargs.get("creation"):
|
||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
timestamp_condition |= (
|
||||
stock_ledger_entry.posting_datetime
|
||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
||||
|
||||
query = query.where(timestamp_condition)
|
||||
|
||||
for field in ["warehouse", "item_code"]:
|
||||
@@ -2601,6 +2611,16 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
if kwargs.get("creation"):
|
||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
timestamp_condition |= (
|
||||
stock_ledger_entry.posting_datetime
|
||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
||||
|
||||
query = query.where(timestamp_condition)
|
||||
|
||||
for field in ["warehouse", "item_code", "serial_no"]:
|
||||
@@ -2659,6 +2679,16 @@ def get_stock_ledgers_batches(kwargs):
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
if kwargs.get("creation"):
|
||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
||||
kwargs.posting_date, kwargs.posting_time
|
||||
)
|
||||
|
||||
timestamp_condition |= (
|
||||
stock_ledger_entry.posting_datetime
|
||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
||||
|
||||
query = query.where(timestamp_condition)
|
||||
|
||||
if kwargs.get("ignore_voucher_nos"):
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _, bold, json, msgprint
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import add_to_date, cint, cstr, flt, get_datetime
|
||||
from frappe.utils import add_to_date, cint, cstr, flt, get_datetime, now
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
@@ -1034,7 +1034,7 @@ class StockReconciliation(StockController):
|
||||
val_rate = 0.0
|
||||
current_qty = 0.0
|
||||
if row.current_serial_and_batch_bundle:
|
||||
current_qty = self.get_current_qty_for_serial_or_batch(row)
|
||||
current_qty = self.get_current_qty_for_serial_or_batch(row, sle_creation)
|
||||
elif row.serial_no:
|
||||
item_dict = get_stock_balance_for(
|
||||
row.item_code,
|
||||
@@ -1143,17 +1143,17 @@ class StockReconciliation(StockController):
|
||||
|
||||
return allow_negative_stock
|
||||
|
||||
def get_current_qty_for_serial_or_batch(self, row):
|
||||
def get_current_qty_for_serial_or_batch(self, row, sle_creation):
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
|
||||
current_qty = 0.0
|
||||
if doc.has_serial_no:
|
||||
current_qty = self.get_current_qty_for_serial_nos(doc)
|
||||
current_qty = self.get_current_qty_for_serial_nos(doc, sle_creation)
|
||||
elif doc.has_batch_no:
|
||||
current_qty = self.get_current_qty_for_batch_nos(doc)
|
||||
current_qty = self.get_current_qty_for_batch_nos(doc, sle_creation)
|
||||
|
||||
return abs(current_qty)
|
||||
|
||||
def get_current_qty_for_serial_nos(self, doc):
|
||||
def get_current_qty_for_serial_nos(self, doc, sle_creation):
|
||||
serial_nos_details = get_available_serial_nos(
|
||||
frappe._dict(
|
||||
{
|
||||
@@ -1161,6 +1161,7 @@ class StockReconciliation(StockController):
|
||||
"warehouse": doc.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"creation": sle_creation,
|
||||
"voucher_no": self.name,
|
||||
"ignore_warehouse": 1,
|
||||
}
|
||||
@@ -1190,7 +1191,7 @@ class StockReconciliation(StockController):
|
||||
|
||||
return current_qty
|
||||
|
||||
def get_current_qty_for_batch_nos(self, doc):
|
||||
def get_current_qty_for_batch_nos(self, doc, sle_creation):
|
||||
current_qty = 0.0
|
||||
precision = doc.entries[0].precision("qty")
|
||||
for d in doc.entries:
|
||||
@@ -1198,6 +1199,7 @@ class StockReconciliation(StockController):
|
||||
get_batch_qty(
|
||||
d.batch_no,
|
||||
doc.warehouse,
|
||||
creation=sle_creation,
|
||||
posting_date=doc.posting_date,
|
||||
posting_time=doc.posting_time,
|
||||
ignore_voucher_nos=[doc.voucher_no],
|
||||
@@ -1494,6 +1496,7 @@ def get_stock_balance_for(
|
||||
"company": company,
|
||||
"posting_date": posting_date,
|
||||
"posting_time": posting_time,
|
||||
"creation": row.get("creation") if row and row.get("creation") else now(),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -312,7 +312,7 @@ def is_first_response(issue):
|
||||
|
||||
|
||||
def calculate_first_response_time(issue, first_responded_on):
|
||||
issue_creation_date = issue.service_level_agreement_creation or issue.creation
|
||||
issue_creation_date = get_datetime(issue.service_level_agreement_creation or issue.creation)
|
||||
issue_creation_time = get_time_in_seconds(issue_creation_date)
|
||||
first_responded_on_in_seconds = get_time_in_seconds(first_responded_on)
|
||||
support_hours = frappe.get_cached_doc(
|
||||
|
||||
@@ -25,7 +25,7 @@ from frappe.utils.caching import redis_cache
|
||||
from frappe.utils.nestedset import get_ancestors_of
|
||||
from frappe.utils.safe_exec import get_safe_globals
|
||||
|
||||
from erpnext.support.doctype.issue.issue import get_holidays
|
||||
from erpnext.support.doctype.issue.issue import calculate_first_response_time, get_holidays
|
||||
|
||||
|
||||
class ServiceLevelAgreement(Document):
|
||||
@@ -552,6 +552,8 @@ def handle_status_change(doc, apply_sla_for_resolution):
|
||||
def set_first_response():
|
||||
if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
|
||||
doc.first_responded_on = now_time
|
||||
if doc.meta.has_field("first_response_time"):
|
||||
doc.first_response_time = calculate_first_response_time(doc, doc.first_responded_on)
|
||||
if get_datetime(doc.get("first_responded_on")) > get_datetime(doc.get("response_by")):
|
||||
record_assigned_users_on_failure(doc)
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ from erpnext.utilities.activation import get_level
|
||||
|
||||
class TestActivation(FrappeTestCase):
|
||||
def test_activation(self):
|
||||
levels = get_level()
|
||||
site_info = {"activation": {"activation_level": 0, "sales_data": []}}
|
||||
levels = get_level(site_info)
|
||||
self.assertTrue(levels)
|
||||
|
||||
@@ -37,7 +37,7 @@ def get_site_info(site_info):
|
||||
if company:
|
||||
domain = frappe.get_cached_value("Company", cstr(company), "domain")
|
||||
|
||||
return {"company": company, "domain": domain, "activation": get_level()}
|
||||
return {"company": company, "domain": domain, "activation": get_level(site_info)}
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -9,9 +9,9 @@ from frappe.core.doctype.installed_applications.installed_applications import ge
|
||||
import erpnext
|
||||
|
||||
|
||||
def get_level():
|
||||
activation_level = 0
|
||||
sales_data = []
|
||||
def get_level(site_info):
|
||||
activation_level = site_info.get("activation", {}).get("activation_level", 0)
|
||||
sales_data = site_info.get("activation", {}).get("sales_data", [])
|
||||
min_count = 0
|
||||
doctypes = {
|
||||
"Asset": 5,
|
||||
|
||||
Reference in New Issue
Block a user