mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-31 10:49:09 +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",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"rate",
|
"rate",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
@@ -92,6 +93,13 @@
|
|||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
|||||||
@@ -137,8 +137,10 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
condition = ""
|
condition = ""
|
||||||
|
pe_condition = ""
|
||||||
if not include_reconciled_entries:
|
if not include_reconciled_entries:
|
||||||
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
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(
|
journal_entries = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
@@ -163,19 +165,20 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
payment_entries = frappe.db.sql(
|
payment_entries = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
select
|
select
|
||||||
"Payment Entry" as payment_document, name as payment_entry,
|
"Payment Entry" as payment_document, pe.name as payment_entry,
|
||||||
reference_no as cheque_number, reference_date as cheque_date,
|
pe.reference_no as cheque_number, pe.reference_date as cheque_date,
|
||||||
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
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(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit,
|
if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit,
|
||||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
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(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency
|
||||||
from `tabPayment Entry`
|
from `tabPayment Entry` as pe
|
||||||
|
join `tabCompany` c on c.name = pe.company
|
||||||
where
|
where
|
||||||
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
(pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1
|
||||||
and posting_date >= %(from)s and posting_date <= %(to)s
|
and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s
|
||||||
{condition}
|
{pe_condition}
|
||||||
order by
|
order by
|
||||||
posting_date ASC, name DESC
|
pe.posting_date ASC, pe.name DESC
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"account": account,
|
"account": account,
|
||||||
|
|||||||
@@ -131,18 +131,20 @@ class GLEntry(Document):
|
|||||||
|
|
||||||
if not self.is_cancelled and not (self.party_type and self.party):
|
if not self.is_cancelled and not (self.party_type and self.party):
|
||||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||||
if account_type == "Receivable":
|
# skipping validation for payroll entry creation in case party is not required
|
||||||
frappe.throw(
|
if not frappe.flags.party_not_required_for_receivable_payable:
|
||||||
_("{0} {1}: Customer is required against Receivable account {2}").format(
|
if account_type == "Receivable":
|
||||||
self.voucher_type, self.voucher_no, self.account
|
frappe.throw(
|
||||||
|
_("{0} {1}: Customer is required against Receivable account {2}").format(
|
||||||
|
self.voucher_type, self.voucher_no, self.account
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
elif account_type == "Payable":
|
||||||
elif account_type == "Payable":
|
frappe.throw(
|
||||||
frappe.throw(
|
_("{0} {1}: Supplier is required against Payable account {2}").format(
|
||||||
_("{0} {1}: Supplier is required against Payable account {2}").format(
|
self.voucher_type, self.voucher_no, self.account
|
||||||
self.voucher_type, self.voucher_no, self.account
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Zero value transaction is not allowed
|
# Zero value transaction is not allowed
|
||||||
if not (
|
if not (
|
||||||
|
|||||||
@@ -542,8 +542,11 @@ class JournalEntry(AccountsController):
|
|||||||
def validate_party(self):
|
def validate_party(self):
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
account_type = frappe.get_cached_value("Account", d.account, "account_type")
|
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 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(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
|
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"help_section",
|
"help_section",
|
||||||
"loyalty_program_help"
|
"loyalty_program_help"
|
||||||
],
|
],
|
||||||
@@ -143,6 +144,12 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2019-05-26 09:11:46.120251",
|
"modified": "2019-05-26 09:11:46.120251",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"section_break_4",
|
"section_break_4",
|
||||||
"invoices"
|
"invoices"
|
||||||
],
|
],
|
||||||
@@ -62,6 +63,12 @@
|
|||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
"invoice_name",
|
"invoice_name",
|
||||||
"invoices",
|
"invoices",
|
||||||
@@ -193,6 +194,12 @@
|
|||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.party",
|
"depends_on": "eval:doc.party",
|
||||||
"description": "Only 'Payment Entries' made against this advance account are supported.",
|
"description": "Only 'Payment Entries' made against this advance account are supported.",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint, qb
|
from frappe import _, msgprint, qb
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import Criterion
|
from frappe.query_builder import Criterion
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
|
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"))
|
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
|
||||||
|
|
||||||
def get_difference_amount(self, payment_entry, invoice, allocated_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
|
difference_amount = 0
|
||||||
if frappe.get_cached_value(
|
if frappe.get_cached_value(
|
||||||
"Account", self.receivable_payable_account, "account_currency"
|
"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(
|
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
|
||||||
"exchange_rate", 1
|
"exchange_rate", 1
|
||||||
):
|
):
|
||||||
allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
|
allocated_amount_in_ref_rate = flt(
|
||||||
allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
|
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
|
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
|
||||||
|
|
||||||
return difference_amount
|
return difference_amount
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
"account_currency",
|
"account_currency",
|
||||||
"tax_amount",
|
"tax_amount",
|
||||||
@@ -211,6 +212,13 @@
|
|||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"rate",
|
"rate",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
@@ -188,6 +189,13 @@
|
|||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"shipping_amount_section",
|
"shipping_amount_section",
|
||||||
"calculate_based_on",
|
"calculate_based_on",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
@@ -136,6 +137,12 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-truck",
|
"icon": "fa fa-truck",
|
||||||
|
|||||||
@@ -164,6 +164,12 @@
|
|||||||
{% } %}
|
{% } %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
<div class="show-filters">
|
||||||
|
{% if subtitle %}
|
||||||
|
{{ subtitle }}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -974,6 +974,7 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
if self.account_type == "Receivable":
|
if self.account_type == "Receivable":
|
||||||
self.add_customer_filters()
|
self.add_customer_filters()
|
||||||
|
self.exclude_employee_transaction()
|
||||||
|
|
||||||
elif self.account_type == "Payable":
|
elif self.account_type == "Payable":
|
||||||
self.add_supplier_filters()
|
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):
|
def add_supplier_filters(self):
|
||||||
supplier = qb.DocType("Supplier")
|
supplier = qb.DocType("Supplier")
|
||||||
if self.filters.get("supplier_group"):
|
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,
|
get_report_summary as get_pl_summary,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
|
from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
|
||||||
|
from erpnext.accounts.utils import get_zero_cutoff
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
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)
|
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
|
# ignore zero values
|
||||||
has_value = True
|
has_value = True
|
||||||
total += flt(row[company])
|
total += flt(row[company])
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from erpnext.accounts.report.financial_statements import (
|
|||||||
filter_out_zero_value_rows,
|
filter_out_zero_value_rows,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||||
|
from erpnext.accounts.utils import get_zero_cutoff
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@@ -154,7 +155,7 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
|
|||||||
for dimension in dimension_list:
|
for dimension in dimension_list:
|
||||||
row[frappe.scrub(dimension)] = flt(d.get(frappe.scrub(dimension), 0.0), 3)
|
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
|
# ignore zero values
|
||||||
has_value = True
|
has_value = True
|
||||||
total += flt(d.get(frappe.scrub(dimension), 0.0), 3)
|
total += flt(d.get(frappe.scrub(dimension), 0.0), 3)
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
</h5>
|
</h5>
|
||||||
{% } %}
|
{% } %}
|
||||||
<hr>
|
<hr>
|
||||||
|
<div class="show-filters">
|
||||||
|
{% if subtitle %}
|
||||||
|
{{ subtitle }}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_dimension_with_children,
|
get_dimension_with_children,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
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(
|
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)
|
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
|
# ignore zero values
|
||||||
has_value = True
|
has_value = True
|
||||||
total += flt(row[period.key])
|
total += flt(row[period.key])
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
{%= frappe.datetime.str_to_user(filters.to_date) %}
|
{%= frappe.datetime.str_to_user(filters.to_date) %}
|
||||||
</h5>
|
</h5>
|
||||||
<hr>
|
<hr>
|
||||||
|
<div class="show-filters">
|
||||||
|
{% if subtitle %}
|
||||||
|
{{ subtitle }}
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -645,7 +645,7 @@ def get_columns(filters):
|
|||||||
"options": "GL Entry",
|
"options": "GL Entry",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||||
{
|
{
|
||||||
"label": _("Account"),
|
"label": _("Account"),
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from erpnext.accounts.report.financial_statements import (
|
|||||||
filter_out_zero_value_rows,
|
filter_out_zero_value_rows,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
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")
|
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:
|
for key in value_fields:
|
||||||
row[key] = flt(d.get(key, 0.0), 3)
|
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
|
# ignore zero values
|
||||||
has_value = True
|
has_value = True
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from erpnext.accounts.report.financial_statements import (
|
|||||||
set_gl_entries_by_account,
|
set_gl_entries_by_account,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
||||||
|
from erpnext.accounts.utils import get_zero_cutoff
|
||||||
|
|
||||||
value_fields = (
|
value_fields = (
|
||||||
"opening_debit",
|
"opening_debit",
|
||||||
@@ -413,7 +414,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
|
|||||||
for key in value_fields:
|
for key in value_fields:
|
||||||
row[key] = flt(d.get(key, 0.0), 3)
|
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
|
# ignore zero values
|
||||||
has_value = True
|
has_value = True
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from erpnext.accounts.party import get_party_shipping_address
|
|||||||
from erpnext.accounts.utils import (
|
from erpnext.accounts.utils import (
|
||||||
get_future_stock_vouchers,
|
get_future_stock_vouchers,
|
||||||
get_voucherwise_gl_entries,
|
get_voucherwise_gl_entries,
|
||||||
|
get_zero_cutoff,
|
||||||
sort_stock_vouchers_by_posting_date,
|
sort_stock_vouchers_by_posting_date,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
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))
|
self.assertSequenceEqual(doc_name[0:2], ("SUP", fiscal_year))
|
||||||
frappe.db.set_default("supp_master_name", "Supplier Name")
|
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 = [
|
ADDRESS_RECORDS = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from frappe.utils import (
|
|||||||
now,
|
now,
|
||||||
nowdate,
|
nowdate,
|
||||||
)
|
)
|
||||||
|
from frappe.utils.caching import site_cache
|
||||||
from pypika import Order
|
from pypika import Order
|
||||||
from pypika.functions import Coalesce
|
from pypika.functions import Coalesce
|
||||||
from pypika.terms import ExistsCriterion
|
from pypika.terms import ExistsCriterion
|
||||||
@@ -1130,6 +1131,29 @@ def get_currency_precision():
|
|||||||
return 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):
|
def get_held_invoices(party_type, party):
|
||||||
"""
|
"""
|
||||||
Returns a list of names Purchase Invoices for the given party that are on hold
|
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 filter in match_filters:
|
||||||
for link_option, allowed_values in filter.items():
|
for link_option, allowed_values in filter.items():
|
||||||
fieldnames = link_fields_map.get(link_option, [])
|
fieldnames = link_fields_map.get(link_option, [])
|
||||||
|
cond = None
|
||||||
|
|
||||||
|
if link_option == doctype:
|
||||||
|
cond = _dt["name"].isin(allowed_values)
|
||||||
|
|
||||||
for fieldname in fieldnames:
|
for fieldname in fieldnames:
|
||||||
field = _dt[fieldname]
|
field = _dt[fieldname]
|
||||||
@@ -2459,6 +2487,7 @@ def build_qb_match_conditions(doctype, user=None) -> list:
|
|||||||
if not apply_strict_user_permissions:
|
if not apply_strict_user_permissions:
|
||||||
cond = (Coalesce(field, "") == "") | cond
|
cond = (Coalesce(field, "") == "") | cond
|
||||||
|
|
||||||
|
if cond:
|
||||||
criterion.append(cond)
|
criterion.append(cond)
|
||||||
|
|
||||||
return criterion
|
return criterion
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"target_fixed_asset_account"
|
"target_fixed_asset_account"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -288,6 +289,12 @@
|
|||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
|
"project",
|
||||||
"fixed_asset_account"
|
"fixed_asset_account"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -98,6 +99,13 @@
|
|||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "finance_book",
|
"fieldname": "finance_book",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
|||||||
@@ -118,7 +118,8 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate"
|
"label": "Rate",
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 1,
|
"columns": 1,
|
||||||
@@ -161,7 +162,8 @@
|
|||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_yuca",
|
"fieldname": "column_break_yuca",
|
||||||
@@ -183,13 +185,15 @@
|
|||||||
"fieldname": "base_amount",
|
"fieldname": "base_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Base Amount"
|
"label": "Base Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "base_rate",
|
"fieldname": "base_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Base Rate"
|
"label": "Base Rate",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ def execute():
|
|||||||
"POS Invoice Merge Log", {"docstatus": 1}, ["name", "pos_closing_entry"]
|
"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:
|
for log in pos_invoice_merge_logs:
|
||||||
if log.pos_closing_entry and frappe.db.exists("POS Closing Entry", log.pos_closing_entry):
|
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")
|
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.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
|
party_name = me.frm.doc.party_name
|
||||||
}
|
}
|
||||||
else{
|
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()];
|
party_name = me.frm.doc[party_type.toLowerCase()];
|
||||||
}
|
}
|
||||||
if (party_name) {
|
if (party_name) {
|
||||||
|
|||||||
@@ -1762,6 +1762,11 @@ def create_pick_list(source_name, target_doc=None):
|
|||||||
target.qty = qty_to_be_picked
|
target.qty = qty_to_be_picked
|
||||||
target.stock_qty = qty_to_be_picked * flt(source.conversion_factor)
|
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:
|
def update_packed_item_qty(source, target, source_parent) -> None:
|
||||||
qty = flt(source.qty)
|
qty = flt(source.qty)
|
||||||
for item in source_parent.items:
|
for item in source_parent.items:
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.utils
|
import frappe.utils
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
from erpnext.accounts.utils import build_qb_match_conditions
|
||||||
from erpnext.setup.doctype.employee.employee import InactiveEmployeeStatusError
|
from erpnext.setup.doctype.employee.employee import InactiveEmployeeStatusError
|
||||||
|
|
||||||
test_records = frappe.get_test_records("Employee")
|
test_records = frappe.get_test_records("Employee")
|
||||||
@@ -34,6 +36,32 @@ class TestEmployee(unittest.TestCase):
|
|||||||
employee_doc.save()
|
employee_doc.save()
|
||||||
self.assertTrue("Employee" not in frappe.get_roles(user))
|
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):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ def get_batch_qty(
|
|||||||
batch_no=None,
|
batch_no=None,
|
||||||
warehouse=None,
|
warehouse=None,
|
||||||
item_code=None,
|
item_code=None,
|
||||||
|
creation=None,
|
||||||
posting_date=None,
|
posting_date=None,
|
||||||
posting_time=None,
|
posting_time=None,
|
||||||
ignore_voucher_nos=None,
|
ignore_voucher_nos=None,
|
||||||
@@ -244,6 +245,7 @@ def get_batch_qty(
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
|
"creation": creation,
|
||||||
"posting_date": posting_date,
|
"posting_date": posting_date,
|
||||||
"posting_time": posting_time,
|
"posting_time": posting_time,
|
||||||
"batch_no": batch_no,
|
"batch_no": batch_no,
|
||||||
|
|||||||
@@ -724,7 +724,10 @@ class Item(Document):
|
|||||||
|
|
||||||
item_defaults = frappe.db.get_values(
|
item_defaults = frappe.db.get_values(
|
||||||
"Item Default",
|
"Item Default",
|
||||||
{"parent": self.item_group},
|
{
|
||||||
|
"parent": self.item_group,
|
||||||
|
"parenttype": "Item Group",
|
||||||
|
},
|
||||||
[
|
[
|
||||||
"company",
|
"company",
|
||||||
"default_warehouse",
|
"default_warehouse",
|
||||||
|
|||||||
@@ -354,10 +354,12 @@ frappe.ui.form.on("Pick List Item", {
|
|||||||
item_code: (frm, cdt, cdn) => {
|
item_code: (frm, cdt, cdn) => {
|
||||||
let row = frappe.get_doc(cdt, cdn);
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
if (row.item_code) {
|
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, "uom", data.stock_uom);
|
||||||
frappe.model.set_value(cdt, cdn, "stock_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, "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) => {
|
qty: (frm, cdt, cdn) => {
|
||||||
let row = frappe.get_doc(cdt, cdn);
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.set_value(cdt, cdn, "stock_qty", row.qty * row.conversion_factor);
|
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) {
|
if (item_code) {
|
||||||
return frappe.xcall("erpnext.stock.doctype.pick_list.pick_list.get_item_details", {
|
return frappe.xcall("erpnext.stock.doctype.pick_list.pick_list.get_item_details", {
|
||||||
item_code,
|
item_code,
|
||||||
uom,
|
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_auto_batch_nos,
|
||||||
get_picked_serial_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 (
|
from erpnext.stock.serial_batch_bundle import (
|
||||||
SerialBatchCreation,
|
SerialBatchCreation,
|
||||||
get_batches_from_bundle,
|
get_batches_from_bundle,
|
||||||
@@ -74,6 +74,9 @@ class PickList(TransactionBase):
|
|||||||
if self.has_reserved_stock():
|
if self.has_reserved_stock():
|
||||||
self.set_onload("has_reserved_stock", True)
|
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):
|
def validate(self):
|
||||||
self.validate_expired_batches()
|
self.validate_expired_batches()
|
||||||
self.validate_for_qty()
|
self.validate_for_qty()
|
||||||
@@ -1442,15 +1445,29 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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 = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
|
||||||
details.uom = uom or details.stock_uom
|
details.uom = uom or details.stock_uom
|
||||||
if uom:
|
if uom:
|
||||||
details.update(get_conversion_factor(item_code, 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
|
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):
|
def update_delivery_note_item(source, target, delivery_note):
|
||||||
cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
|
cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
|
||||||
if not cost_center:
|
if not cost_center:
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"delivered_qty",
|
"delivered_qty",
|
||||||
|
"available_quantity_section",
|
||||||
|
"actual_qty",
|
||||||
|
"column_break_kyek",
|
||||||
|
"company_total_stock",
|
||||||
"serial_no_and_batch_section",
|
"serial_no_and_batch_section",
|
||||||
"pick_serial_and_batch",
|
"pick_serial_and_batch",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
@@ -124,7 +128,7 @@
|
|||||||
"fieldname": "stock_qty",
|
"fieldname": "stock_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Stock Qty",
|
"label": "Qty (in Stock UOM)",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -248,11 +252,38 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 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,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-31 19:57:43.531298",
|
"modified": "2025-09-23 00:02:57.817040",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List Item",
|
"name": "Pick List Item",
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ class PickListItem(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
actual_qty: DF.Float
|
||||||
batch_no: DF.Link | None
|
batch_no: DF.Link | None
|
||||||
|
company_total_stock: DF.Float
|
||||||
conversion_factor: DF.Float
|
conversion_factor: DF.Float
|
||||||
delivered_qty: DF.Float
|
delivered_qty: DF.Float
|
||||||
description: DF.Text | None
|
description: DF.Text | None
|
||||||
|
|||||||
@@ -2360,6 +2360,16 @@ def get_available_batches(kwargs):
|
|||||||
kwargs.posting_date, kwargs.posting_time
|
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)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
for field in ["warehouse", "item_code"]:
|
for field in ["warehouse", "item_code"]:
|
||||||
@@ -2601,6 +2611,16 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
|||||||
kwargs.posting_date, kwargs.posting_time
|
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)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
for field in ["warehouse", "item_code", "serial_no"]:
|
for field in ["warehouse", "item_code", "serial_no"]:
|
||||||
@@ -2659,6 +2679,16 @@ def get_stock_ledgers_batches(kwargs):
|
|||||||
kwargs.posting_date, kwargs.posting_time
|
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)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
if kwargs.get("ignore_voucher_nos"):
|
if kwargs.get("ignore_voucher_nos"):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold, json, msgprint
|
from frappe import _, bold, json, msgprint
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
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
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_company_default
|
from erpnext.accounts.utils import get_company_default
|
||||||
@@ -1034,7 +1034,7 @@ class StockReconciliation(StockController):
|
|||||||
val_rate = 0.0
|
val_rate = 0.0
|
||||||
current_qty = 0.0
|
current_qty = 0.0
|
||||||
if row.current_serial_and_batch_bundle:
|
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:
|
elif row.serial_no:
|
||||||
item_dict = get_stock_balance_for(
|
item_dict = get_stock_balance_for(
|
||||||
row.item_code,
|
row.item_code,
|
||||||
@@ -1143,17 +1143,17 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
return allow_negative_stock
|
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)
|
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
|
||||||
current_qty = 0.0
|
current_qty = 0.0
|
||||||
if doc.has_serial_no:
|
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:
|
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)
|
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(
|
serial_nos_details = get_available_serial_nos(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
{
|
{
|
||||||
@@ -1161,6 +1161,7 @@ class StockReconciliation(StockController):
|
|||||||
"warehouse": doc.warehouse,
|
"warehouse": doc.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
|
"creation": sle_creation,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"ignore_warehouse": 1,
|
"ignore_warehouse": 1,
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1191,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
return current_qty
|
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
|
current_qty = 0.0
|
||||||
precision = doc.entries[0].precision("qty")
|
precision = doc.entries[0].precision("qty")
|
||||||
for d in doc.entries:
|
for d in doc.entries:
|
||||||
@@ -1198,6 +1199,7 @@ class StockReconciliation(StockController):
|
|||||||
get_batch_qty(
|
get_batch_qty(
|
||||||
d.batch_no,
|
d.batch_no,
|
||||||
doc.warehouse,
|
doc.warehouse,
|
||||||
|
creation=sle_creation,
|
||||||
posting_date=doc.posting_date,
|
posting_date=doc.posting_date,
|
||||||
posting_time=doc.posting_time,
|
posting_time=doc.posting_time,
|
||||||
ignore_voucher_nos=[doc.voucher_no],
|
ignore_voucher_nos=[doc.voucher_no],
|
||||||
@@ -1494,6 +1496,7 @@ def get_stock_balance_for(
|
|||||||
"company": company,
|
"company": company,
|
||||||
"posting_date": posting_date,
|
"posting_date": posting_date,
|
||||||
"posting_time": posting_time,
|
"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):
|
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)
|
issue_creation_time = get_time_in_seconds(issue_creation_date)
|
||||||
first_responded_on_in_seconds = get_time_in_seconds(first_responded_on)
|
first_responded_on_in_seconds = get_time_in_seconds(first_responded_on)
|
||||||
support_hours = frappe.get_cached_doc(
|
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.nestedset import get_ancestors_of
|
||||||
from frappe.utils.safe_exec import get_safe_globals
|
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):
|
class ServiceLevelAgreement(Document):
|
||||||
@@ -552,6 +552,8 @@ def handle_status_change(doc, apply_sla_for_resolution):
|
|||||||
def set_first_response():
|
def set_first_response():
|
||||||
if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
|
if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
|
||||||
doc.first_responded_on = now_time
|
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")):
|
if get_datetime(doc.get("first_responded_on")) > get_datetime(doc.get("response_by")):
|
||||||
record_assigned_users_on_failure(doc)
|
record_assigned_users_on_failure(doc)
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ from erpnext.utilities.activation import get_level
|
|||||||
|
|
||||||
class TestActivation(FrappeTestCase):
|
class TestActivation(FrappeTestCase):
|
||||||
def test_activation(self):
|
def test_activation(self):
|
||||||
levels = get_level()
|
site_info = {"activation": {"activation_level": 0, "sales_data": []}}
|
||||||
|
levels = get_level(site_info)
|
||||||
self.assertTrue(levels)
|
self.assertTrue(levels)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def get_site_info(site_info):
|
|||||||
if company:
|
if company:
|
||||||
domain = frappe.get_cached_value("Company", cstr(company), "domain")
|
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
|
@contextmanager
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ from frappe.core.doctype.installed_applications.installed_applications import ge
|
|||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
|
|
||||||
def get_level():
|
def get_level(site_info):
|
||||||
activation_level = 0
|
activation_level = site_info.get("activation", {}).get("activation_level", 0)
|
||||||
sales_data = []
|
sales_data = site_info.get("activation", {}).get("sales_data", [])
|
||||||
min_count = 0
|
min_count = 0
|
||||||
doctypes = {
|
doctypes = {
|
||||||
"Asset": 5,
|
"Asset": 5,
|
||||||
|
|||||||
Reference in New Issue
Block a user