mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 19:19:17 +00:00
Merge pull request #42938 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -469,7 +469,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-20 09:37:47.650347",
|
||||
"modified": "2024-01-22 12:10:10.151819",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
|
||||
|
||||
import erpnext
|
||||
|
||||
@@ -210,8 +210,11 @@ class BankClearance(Document):
|
||||
|
||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
||||
d.idx, d.clearance_date, d.cheque_date
|
||||
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
|
||||
d.idx,
|
||||
get_link_to_form(d.payment_document, d.payment_entry),
|
||||
d.clearance_date,
|
||||
d.cheque_date,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@@ -77,6 +82,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@@ -326,6 +334,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.customer_query"
|
||||
}
|
||||
} else if (frm.doc.party_type == "Shareholder") {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1563,7 +1563,7 @@ def get_outstanding_reference_documents(args):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
||||
if args.get("party_type") != "Employee":
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
|
||||
@@ -19,7 +19,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
)
|
||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.utils import get_account_currency, get_currency_precision
|
||||
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
|
||||
from erpnext.utilities import payment_app_import_guard
|
||||
|
||||
@@ -520,7 +520,7 @@ def get_amount(ref_doc, payment_account=None):
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
if grand_total > 0:
|
||||
return grand_total
|
||||
return flt(grand_total, get_currency_precision())
|
||||
else:
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
inv.save()
|
||||
|
||||
self.assertEqual(inv.net_total, 4298.25)
|
||||
self.assertEqual(inv.net_total, 4298.24)
|
||||
self.assertEqual(inv.grand_total, 4900.00)
|
||||
|
||||
def test_tax_calculation_with_multiple_items(self):
|
||||
|
||||
@@ -340,7 +340,7 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
inv.load_from_db()
|
||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||
self.assertEqual(consolidated_invoice.status, "Return")
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
|
||||
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
@@ -485,7 +485,7 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item):
|
||||
continue
|
||||
|
||||
stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0)
|
||||
amount = stock_qty * (row.get("price_list_rate") or row.get("rate"))
|
||||
amount = stock_qty * (flt(row.get("price_list_rate")) or flt(row.get("rate")))
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row)
|
||||
|
||||
if pricing_rules and pricing_rules[0]:
|
||||
|
||||
@@ -307,7 +307,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
si.insert()
|
||||
|
||||
# with inclusive tax
|
||||
self.assertEqual(si.items[0].net_amount, 3947.368421052631)
|
||||
self.assertEqual(si.items[0].net_amount, 3947.37)
|
||||
self.assertEqual(si.net_total, si.base_net_total)
|
||||
self.assertEqual(si.net_total, 3947.37)
|
||||
self.assertEqual(si.grand_total, 5000)
|
||||
|
||||
@@ -414,8 +415,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEqual(d.get(k), expected_values[d.account_head][i])
|
||||
|
||||
self.assertEqual(si.base_grand_total, 1500)
|
||||
self.assertEqual(si.grand_total, 1500)
|
||||
self.assertEqual(si.base_grand_total, 1500.01)
|
||||
self.assertEqual(si.grand_total, 1500.01)
|
||||
self.assertEqual(si.rounding_adjustment, -0.01)
|
||||
|
||||
def test_discount_amount_gl_entry(self):
|
||||
@@ -651,7 +652,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
62.5,
|
||||
625.0,
|
||||
50,
|
||||
499.97600115194473,
|
||||
499.98,
|
||||
],
|
||||
"_Test Item Home Desktop 200": [
|
||||
190.66,
|
||||
@@ -662,7 +663,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
190.66,
|
||||
953.3,
|
||||
150,
|
||||
749.9968530500239,
|
||||
750,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -675,20 +676,21 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(d.get(k), expected_values[d.item_code][i])
|
||||
|
||||
# check net total
|
||||
self.assertEqual(si.net_total, 1249.97)
|
||||
self.assertEqual(si.base_net_total, si.net_total)
|
||||
self.assertEqual(si.net_total, 1249.98)
|
||||
self.assertEqual(si.total, 1578.3)
|
||||
|
||||
# check tax calculation
|
||||
expected_values = {
|
||||
"keys": ["tax_amount", "total"],
|
||||
"_Test Account Excise Duty - _TC": [140, 1389.97],
|
||||
"_Test Account Education Cess - _TC": [2.8, 1392.77],
|
||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17],
|
||||
"_Test Account CST - _TC": [27.88, 1422.05],
|
||||
"_Test Account VAT - _TC": [156.25, 1578.30],
|
||||
"_Test Account Customs Duty - _TC": [125, 1703.30],
|
||||
"_Test Account Shipping Charges - _TC": [100, 1803.30],
|
||||
"_Test Account Discount - _TC": [-180.33, 1622.97],
|
||||
"_Test Account Excise Duty - _TC": [140, 1389.98],
|
||||
"_Test Account Education Cess - _TC": [2.8, 1392.78],
|
||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
|
||||
"_Test Account CST - _TC": [27.88, 1422.06],
|
||||
"_Test Account VAT - _TC": [156.25, 1578.31],
|
||||
"_Test Account Customs Duty - _TC": [125, 1703.31],
|
||||
"_Test Account Shipping Charges - _TC": [100, 1803.31],
|
||||
"_Test Account Discount - _TC": [-180.33, 1622.98],
|
||||
}
|
||||
|
||||
for d in si.get("taxes"):
|
||||
@@ -724,7 +726,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"base_rate": 2500,
|
||||
"base_amount": 25000,
|
||||
"net_rate": 40,
|
||||
"net_amount": 399.9808009215558,
|
||||
"net_amount": 399.98,
|
||||
"base_net_rate": 2000,
|
||||
"base_net_amount": 19999,
|
||||
},
|
||||
@@ -738,7 +740,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"base_rate": 7500,
|
||||
"base_amount": 37500,
|
||||
"net_rate": 118.01,
|
||||
"net_amount": 590.0531205155963,
|
||||
"net_amount": 590.05,
|
||||
"base_net_rate": 5900.5,
|
||||
"base_net_amount": 29502.5,
|
||||
},
|
||||
@@ -776,8 +778,13 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
self.assertEqual(si.base_grand_total, 60795)
|
||||
self.assertEqual(si.grand_total, 1215.90)
|
||||
self.assertEqual(si.rounding_adjustment, 0.01)
|
||||
self.assertEqual(si.base_rounding_adjustment, 0.50)
|
||||
# no rounding adjustment as the Smallest Currency Fraction Value of USD is 0.01
|
||||
if frappe.db.get_value("Currency", "USD", "smallest_currency_fraction_value") < 0.01:
|
||||
self.assertEqual(si.rounding_adjustment, 0.10)
|
||||
self.assertEqual(si.base_rounding_adjustment, 5.0)
|
||||
else:
|
||||
self.assertEqual(si.rounding_adjustment, 0.0)
|
||||
self.assertEqual(si.base_rounding_adjustment, 0.0)
|
||||
|
||||
def test_outstanding(self):
|
||||
w = self.make()
|
||||
@@ -2099,7 +2106,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(si.net_total, 19453.13)
|
||||
self.assertEqual(si.grand_total, 24900)
|
||||
self.assertEqual(si.total_taxes_and_charges, 5446.88)
|
||||
self.assertEqual(si.rounding_adjustment, -0.01)
|
||||
self.assertEqual(si.rounding_adjustment, 0.00)
|
||||
|
||||
expected_values = dict(
|
||||
(d[0], d)
|
||||
@@ -2126,7 +2133,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
def test_rounding_adjustment_2(self):
|
||||
si = create_sales_invoice(rate=400, do_not_save=True)
|
||||
for rate in [400, 600, 100]:
|
||||
for rate in [400.25, 600.30, 100.65]:
|
||||
si.append(
|
||||
"items",
|
||||
{
|
||||
@@ -2152,17 +2159,18 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
)
|
||||
si.save()
|
||||
si.submit()
|
||||
self.assertEqual(si.net_total, 1271.19)
|
||||
self.assertEqual(si.grand_total, 1500)
|
||||
self.assertEqual(si.total_taxes_and_charges, 228.82)
|
||||
self.assertEqual(si.rounding_adjustment, -0.01)
|
||||
self.assertEqual(si.net_total, si.base_net_total)
|
||||
self.assertEqual(si.net_total, 1272.20)
|
||||
self.assertEqual(si.grand_total, 1501.20)
|
||||
self.assertEqual(si.total_taxes_and_charges, 229)
|
||||
self.assertEqual(si.rounding_adjustment, -0.20)
|
||||
|
||||
expected_values = [
|
||||
["_Test Account Service Tax - _TC", 0.0, 114.41],
|
||||
["_Test Account VAT - _TC", 0.0, 114.41],
|
||||
[si.debit_to, 1500, 0.0],
|
||||
["Round Off - _TC", 0.01, 0.01],
|
||||
["Sales - _TC", 0.0, 1271.18],
|
||||
["_Test Account Service Tax - _TC", 0.0, 114.50],
|
||||
["_Test Account VAT - _TC", 0.0, 114.50],
|
||||
[si.debit_to, 1501, 0.0],
|
||||
["Round Off - _TC", 0.20, 0.0],
|
||||
["Sales - _TC", 0.0, 1272.20],
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
@@ -2220,7 +2228,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
si.save()
|
||||
si.submit()
|
||||
self.assertEqual(si.net_total, 4007.16)
|
||||
self.assertEqual(si.net_total, si.base_net_total)
|
||||
self.assertEqual(si.net_total, 4007.15)
|
||||
self.assertEqual(si.grand_total, 4488.02)
|
||||
self.assertEqual(si.total_taxes_and_charges, 480.86)
|
||||
self.assertEqual(si.rounding_adjustment, -0.02)
|
||||
@@ -2232,7 +2241,7 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
["_Test Account Service Tax - _TC", 0.0, 240.43],
|
||||
["_Test Account VAT - _TC", 0.0, 240.43],
|
||||
["Sales - _TC", 0.0, 4007.15],
|
||||
["Round Off - _TC", 0.02, 0.01],
|
||||
["Round Off - _TC", 0.01, 0.0],
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -694,10 +694,7 @@ class Subscription(Document):
|
||||
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
||||
|
||||
process_subscription = frappe.new_doc("Process Subscription")
|
||||
process_subscription.posting_date = processing_date
|
||||
process_subscription.subscription = self.name
|
||||
process_subscription.save().submit()
|
||||
self.process(posting_date=processing_date)
|
||||
|
||||
|
||||
def get_calendar_months(billing_interval):
|
||||
|
||||
@@ -150,8 +150,8 @@ def get_payment_entries(filters):
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit,
|
||||
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`
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Cheques and Deposits Incorrectly cleared"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
{
|
||||
fieldname: "account",
|
||||
label: __("Bank Account"),
|
||||
fieldtype: "Link",
|
||||
options: "Account",
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]
|
||||
: "",
|
||||
reqd: 1,
|
||||
get_query: function () {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_account_list",
|
||||
filters: [
|
||||
["Account", "account_type", "in", "Bank, Cash"],
|
||||
["Account", "is_group", "=", 0],
|
||||
["Account", "disabled", "=", 0],
|
||||
["Account", "company", "=", company],
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "report_date",
|
||||
label: __("Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2024-07-30 17:20:07.570971",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2024-07-30 17:20:07.570971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cheques and Deposits Incorrectly cleared",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Payment Entry",
|
||||
"report_name": "Cheques and Deposits Incorrectly cleared",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.query_builder import CustomFunction
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns()
|
||||
data = build_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def build_payment_entry_dict(row: dict) -> dict:
|
||||
row_dict = frappe._dict()
|
||||
row_dict.update(
|
||||
{
|
||||
"payment_document": row.get("doctype"),
|
||||
"payment_entry": row.get("name"),
|
||||
"posting_date": row.get("posting_date"),
|
||||
"clearance_date": row.get("clearance_date"),
|
||||
}
|
||||
)
|
||||
if row.get("payment_type") == "Receive" and row.get("party_type") in ["Customer", "Supplier"]:
|
||||
row_dict.update(
|
||||
{
|
||||
"debit": row.get("amount"),
|
||||
"credit": 0,
|
||||
}
|
||||
)
|
||||
else:
|
||||
row_dict.update(
|
||||
{
|
||||
"debit": 0,
|
||||
"credit": row.get("amount"),
|
||||
}
|
||||
)
|
||||
return row_dict
|
||||
|
||||
|
||||
def build_journal_entry_dict(row: dict) -> dict:
|
||||
row_dict = frappe._dict()
|
||||
row_dict.update(
|
||||
{
|
||||
"payment_document": row.get("doctype"),
|
||||
"payment_entry": row.get("name"),
|
||||
"posting_date": row.get("posting_date"),
|
||||
"clearance_date": row.get("clearance_date"),
|
||||
"debit": row.get("debit_in_account_currency"),
|
||||
"credit": row.get("credit_in_account_currency"),
|
||||
}
|
||||
)
|
||||
return row_dict
|
||||
|
||||
|
||||
def build_data(filters):
|
||||
vouchers = get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters)
|
||||
data = []
|
||||
for x in vouchers:
|
||||
if x.doctype == "Payment Entry":
|
||||
data.append(build_payment_entry_dict(x))
|
||||
elif x.doctype == "Journal Entry":
|
||||
data.append(build_journal_entry_dict(x))
|
||||
return data
|
||||
|
||||
|
||||
def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters):
|
||||
je = qb.DocType("Journal Entry")
|
||||
jea = qb.DocType("Journal Entry Account")
|
||||
doctype_name = ConstantColumn("Journal Entry")
|
||||
|
||||
journals = (
|
||||
qb.from_(je)
|
||||
.inner_join(jea)
|
||||
.on(je.name == jea.parent)
|
||||
.select(
|
||||
doctype_name.as_("doctype"),
|
||||
je.name,
|
||||
jea.debit_in_account_currency,
|
||||
jea.credit_in_account_currency,
|
||||
je.posting_date,
|
||||
je.clearance_date,
|
||||
)
|
||||
.where(
|
||||
je.docstatus.eq(1)
|
||||
& jea.account.eq(filters.account)
|
||||
& je.posting_date.gt(filters.report_date)
|
||||
& je.clearance_date.lte(filters.report_date)
|
||||
& (je.is_opening.isnull() | je.is_opening.eq("No"))
|
||||
)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
||||
pe = qb.DocType("Payment Entry")
|
||||
doctype_name = ConstantColumn("Payment Entry")
|
||||
payments = (
|
||||
qb.from_(pe)
|
||||
.select(
|
||||
doctype_name.as_("doctype"),
|
||||
pe.name,
|
||||
ifelse(pe.paid_from.eq(filters.account), pe.paid_amount, pe.received_amount).as_("amount"),
|
||||
pe.payment_type,
|
||||
pe.party_type,
|
||||
pe.posting_date,
|
||||
pe.clearance_date,
|
||||
)
|
||||
.where(
|
||||
pe.docstatus.eq(1)
|
||||
& (pe.paid_from.eq(filters.account) | pe.paid_to.eq(filters.account))
|
||||
& pe.posting_date.gt(filters.report_date)
|
||||
& pe.clearance_date.lte(filters.report_date)
|
||||
)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
return journals + payments
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
"fieldname": "payment_document",
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldtype": "Data",
|
||||
"width": 220,
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"label": _("Payment Document"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "payment_document",
|
||||
"width": 220,
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": _("Debit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "account_currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": _("Credit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "account_currency",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 110},
|
||||
{"fieldname": "clearance_date", "label": _("Clearance Date"), "fieldtype": "Date", "width": 110},
|
||||
]
|
||||
@@ -1192,6 +1192,12 @@ class AccountsController(TransactionBase):
|
||||
# Cancelling existing exchange gain/loss journals is handled during the `on_cancel` event.
|
||||
# see accounts/utils.py:cancel_exchange_gain_loss_journal()
|
||||
if self.docstatus == 1:
|
||||
if dimensions_dict is None:
|
||||
dimensions_dict = frappe._dict()
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
dimensions_dict[dim.fieldname] = self.get(dim.fieldname)
|
||||
|
||||
if self.get("doctype") == "Journal Entry":
|
||||
# 'args' is populated with exchange gain/loss account and the amount to be booked.
|
||||
# These are generated by Sales/Purchase Invoice during reconciliation and advance allocation.
|
||||
|
||||
@@ -8,6 +8,7 @@ import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
from frappe.utils.deprecations import deprecated
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
|
||||
@@ -71,7 +72,7 @@ class calculate_taxes_and_totals:
|
||||
self.calculate_net_total()
|
||||
self.calculate_tax_withholding_net_total()
|
||||
self.calculate_taxes()
|
||||
self.manipulate_grand_total_for_inclusive_tax()
|
||||
self.adjust_grand_total_for_inclusive_tax()
|
||||
self.calculate_totals()
|
||||
self._cleanup()
|
||||
self.calculate_total_net_weight()
|
||||
@@ -280,7 +281,7 @@ class calculate_taxes_and_totals:
|
||||
):
|
||||
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
||||
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||
item.discount_percentage = flt(
|
||||
item.discount_percentage, item.precision("discount_percentage")
|
||||
@@ -505,7 +506,12 @@ class calculate_taxes_and_totals:
|
||||
tax.base_tax_amount = round(tax.base_tax_amount, 0)
|
||||
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
|
||||
|
||||
@deprecated
|
||||
def manipulate_grand_total_for_inclusive_tax(self):
|
||||
# for backward compatablility - if in case used by an external application
|
||||
return self.adjust_grand_total_for_inclusive_tax()
|
||||
|
||||
def adjust_grand_total_for_inclusive_tax(self):
|
||||
# if fully inclusive taxes and diff
|
||||
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
||||
last_tax = self.doc.get("taxes")[-1]
|
||||
@@ -527,17 +533,21 @@ class calculate_taxes_and_totals:
|
||||
diff = flt(diff, self.doc.precision("rounding_adjustment"))
|
||||
|
||||
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
|
||||
self.doc.rounding_adjustment = diff
|
||||
self.doc.grand_total_diff = diff
|
||||
else:
|
||||
self.doc.grand_total_diff = 0
|
||||
|
||||
def calculate_totals(self):
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment)
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(
|
||||
self.doc.get("grand_total_diff")
|
||||
)
|
||||
else:
|
||||
self.doc.grand_total = flt(self.doc.net_total)
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.total_taxes_and_charges = flt(
|
||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.get("grand_total_diff")),
|
||||
self.doc.precision("total_taxes_and_charges"),
|
||||
)
|
||||
else:
|
||||
@@ -600,8 +610,8 @@ class calculate_taxes_and_totals:
|
||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||
)
|
||||
|
||||
# if print_in_rate is set, we would have already calculated rounding adjustment
|
||||
self.doc.rounding_adjustment += flt(
|
||||
# rounding adjustment should always be the difference vetween grand and rounded total
|
||||
self.doc.rounding_adjustment = flt(
|
||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||
)
|
||||
|
||||
|
||||
@@ -185,7 +185,8 @@
|
||||
{
|
||||
"fieldname": "expected_closing",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Closing Date"
|
||||
"label": "Expected Closing Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
@@ -357,6 +358,7 @@
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Opportunity Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date",
|
||||
"reqd": 1,
|
||||
@@ -388,6 +390,7 @@
|
||||
"fieldname": "first_response_time",
|
||||
"fieldtype": "Duration",
|
||||
"label": "First Response Time",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -622,7 +625,7 @@
|
||||
"icon": "fa fa-info-sign",
|
||||
"idx": 195,
|
||||
"links": [],
|
||||
"modified": "2022-10-13 12:42:21.545636",
|
||||
"modified": "2024-08-20 04:12:29.095761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
||||
@@ -230,7 +230,8 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) {
|
||||
|
||||
}
|
||||
|
||||
cur_frm.cscript.validate_inclusive_tax = function(tax) {
|
||||
cur_frm.cscript.validate_inclusive_tax = function(tax, frm) {
|
||||
this.frm = this.frm || frm;
|
||||
var actual_type_error = function() {
|
||||
var msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
||||
frappe.throw(msg);
|
||||
@@ -246,12 +247,12 @@ cur_frm.cscript.validate_inclusive_tax = function(tax) {
|
||||
if(tax.charge_type == "Actual") {
|
||||
// inclusive tax cannot be of type Actual
|
||||
actual_type_error();
|
||||
} else if(tax.charge_type == "On Previous Row Amount" &&
|
||||
} else if(tax.charge_type == "On Previous Row Amount" && this.frm &&
|
||||
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_print_rate)
|
||||
) {
|
||||
// referred row should also be an inclusive tax
|
||||
on_previous_row_error(tax.row_id);
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
} else if(tax.charge_type == "On Previous Row Total" && this.frm) {
|
||||
var taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
|
||||
function(t) { return cint(t.included_in_print_rate) ? null : t; });
|
||||
if(taxes_not_included.length > 0) {
|
||||
@@ -294,7 +295,7 @@ if(!erpnext.taxes.flags[cur_frm.cscript.tax_table]) {
|
||||
var tax = frappe.get_doc(cdt, cdn);
|
||||
try {
|
||||
cur_frm.cscript.validate_taxes_and_charges(cdt, cdn);
|
||||
cur_frm.cscript.validate_inclusive_tax(tax);
|
||||
cur_frm.cscript.validate_inclusive_tax(tax, frm);
|
||||
} catch(e) {
|
||||
tax.included_in_print_rate = 0;
|
||||
refresh_field("included_in_print_rate", tax.name, tax.parentfield);
|
||||
|
||||
@@ -103,7 +103,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.determine_exclusive_rate();
|
||||
this.calculate_net_total();
|
||||
this.calculate_taxes();
|
||||
this.manipulate_grand_total_for_inclusive_tax();
|
||||
this.adjust_grand_total_for_inclusive_tax();
|
||||
this.calculate_totals();
|
||||
this._cleanup();
|
||||
}
|
||||
@@ -243,7 +243,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) {
|
||||
var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty;
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction));
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item));
|
||||
item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0;
|
||||
|
||||
me.set_in_company_currency(item, ["net_rate", "net_amount"]);
|
||||
@@ -298,6 +298,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
me.frm.doc.net_total += item.net_amount;
|
||||
me.frm.doc.base_net_total += item.base_net_amount;
|
||||
});
|
||||
|
||||
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
|
||||
}
|
||||
|
||||
calculate_shipping_charges() {
|
||||
@@ -506,8 +508,17 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use adjust_grand_total_for_inclusive_tax instead.
|
||||
*/
|
||||
manipulate_grand_total_for_inclusive_tax() {
|
||||
// for backward compatablility - if in case used by an external application
|
||||
this.adjust_grand_total_for_inclusive_tax()
|
||||
}
|
||||
|
||||
adjust_grand_total_for_inclusive_tax() {
|
||||
var me = this;
|
||||
|
||||
// if fully inclusive taxes and diff
|
||||
if (this.frm.doc["taxes"] && this.frm.doc["taxes"].length) {
|
||||
var any_inclusive_tax = false;
|
||||
@@ -533,7 +544,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
diff = flt(diff, precision("rounding_adjustment"));
|
||||
|
||||
if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) {
|
||||
me.frm.doc.rounding_adjustment = diff;
|
||||
me.frm.doc.grand_total_diff = diff;
|
||||
} else {
|
||||
me.frm.doc.grand_total_diff = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -544,7 +557,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
var me = this;
|
||||
var tax_count = this.frm.doc["taxes"] ? this.frm.doc["taxes"].length : 0;
|
||||
this.frm.doc.grand_total = flt(tax_count
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.rounding_adjustment)
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff)
|
||||
: this.frm.doc.net_total);
|
||||
|
||||
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
@@ -604,7 +617,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
||||
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
|
||||
this.frm.doc.currency, precision("rounded_total"));
|
||||
this.frm.doc.rounding_adjustment += flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||
this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||
precision("rounding_adjustment"));
|
||||
|
||||
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
||||
@@ -672,8 +685,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
if (total_for_discount_amount) {
|
||||
$.each(this.frm._items || [], function(i, item) {
|
||||
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
||||
item.net_amount = flt(item.net_amount - distributed_amount,
|
||||
precision("base_amount", item));
|
||||
item.net_amount = flt(item.net_amount - distributed_amount, precision("net_amount", item));
|
||||
net_total += item.net_amount;
|
||||
|
||||
// discount amount rounding loss adjustment if no taxes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
||||
from frappe.utils import add_days, cint, cstr, flt, get_datetime, getdate, nowtime, today
|
||||
from pypika import functions as fn
|
||||
|
||||
import erpnext
|
||||
@@ -2603,6 +2603,71 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
company_doc.default_inventory_account = None
|
||||
company_doc.save()
|
||||
|
||||
def test_sles_with_same_posting_datetime_and_creation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.report.stock_balance.stock_balance import execute
|
||||
|
||||
item_code = "Test Item for SLE with same posting datetime and creation"
|
||||
create_item(item_code)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
rate=100,
|
||||
posting_date="2023-11-06",
|
||||
posting_time="00:00:00",
|
||||
)
|
||||
|
||||
sr = make_stock_entry(
|
||||
item_code=item_code,
|
||||
source=pr.items[0].warehouse,
|
||||
qty=10,
|
||||
posting_date="2023-11-07",
|
||||
posting_time="14:28:0.330404",
|
||||
)
|
||||
|
||||
sle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
|
||||
"name",
|
||||
)
|
||||
|
||||
sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
|
||||
sle_doc.db_set("creation", "2023-11-07 14:28:01.208930")
|
||||
|
||||
sle_doc.reload()
|
||||
self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.208930"))
|
||||
|
||||
sr = make_stock_entry(
|
||||
item_code=item_code,
|
||||
target=pr.items[0].warehouse,
|
||||
qty=50,
|
||||
posting_date="2023-11-07",
|
||||
posting_time="14:28:0.920825",
|
||||
)
|
||||
|
||||
sle = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
|
||||
"name",
|
||||
)
|
||||
|
||||
sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
|
||||
sle_doc.db_set("creation", "2023-11-07 14:28:01.044561")
|
||||
|
||||
sle_doc.reload()
|
||||
self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.044561"))
|
||||
|
||||
pr.repost_future_sle_and_gle(force=True)
|
||||
|
||||
columns, data = execute(
|
||||
filters=frappe._dict(
|
||||
{"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company}
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(data[0].get("bal_qty"), 50.0)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
from frappe.permissions import add_user_permission, remove_user_permission
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, nowtime, today
|
||||
from frappe.utils import add_days, cstr, flt, get_time, getdate, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.stock.doctype.item.test_item import (
|
||||
@@ -1727,6 +1727,74 @@ class TestStockEntry(FrappeTestCase):
|
||||
mr.cancel()
|
||||
mr.delete()
|
||||
|
||||
def test_stock_entry_for_same_posting_date_and_time(self):
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
item_code = "Test Stock Entry For Same Posting Datetime 1"
|
||||
make_item(item_code, {"is_stock_item": 1})
|
||||
posting_date = nowdate()
|
||||
posting_time = nowtime()
|
||||
|
||||
for index in range(25):
|
||||
se = make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=1,
|
||||
to_warehouse=warehouse,
|
||||
posting_date=posting_date,
|
||||
posting_time=posting_time,
|
||||
do_not_submit=True,
|
||||
purpose="Material Receipt",
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
se.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item_code,
|
||||
"item_name": se.items[0].item_name,
|
||||
"description": se.items[0].description,
|
||||
"t_warehouse": se.items[0].t_warehouse,
|
||||
"basic_rate": 100,
|
||||
"qty": 1,
|
||||
"stock_qty": 1,
|
||||
"conversion_factor": 1,
|
||||
"expense_account": se.items[0].expense_account,
|
||||
"cost_center": se.items[0].cost_center,
|
||||
"uom": se.items[0].uom,
|
||||
"stock_uom": se.items[0].stock_uom,
|
||||
},
|
||||
)
|
||||
|
||||
se.remarks = f"The current number is {cstr(index)}"
|
||||
|
||||
se.submit()
|
||||
|
||||
sles = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=[
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"actual_qty",
|
||||
"qty_after_transaction",
|
||||
"incoming_rate",
|
||||
"stock_value_difference",
|
||||
"stock_value",
|
||||
],
|
||||
filters={"item_code": item_code, "warehouse": warehouse},
|
||||
order_by="creation",
|
||||
)
|
||||
|
||||
self.assertEqual(len(sles), 50)
|
||||
i = 0
|
||||
for sle in sles:
|
||||
i += 1
|
||||
self.assertEqual(getdate(sle.posting_date), getdate(posting_date))
|
||||
self.assertEqual(get_time(sle.posting_time), get_time(posting_time))
|
||||
self.assertEqual(sle.actual_qty, 1)
|
||||
self.assertEqual(sle.qty_after_transaction, i)
|
||||
self.assertEqual(sle.incoming_rate, 100)
|
||||
self.assertEqual(sle.stock_value_difference, 100)
|
||||
self.assertEqual(sle.stock_value, 100 * i)
|
||||
|
||||
|
||||
def make_serialized_item(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -319,7 +319,8 @@
|
||||
{
|
||||
"fieldname": "posting_datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Posting Datetime"
|
||||
"label": "Posting Datetime",
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -328,7 +329,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-07 09:18:13.999231",
|
||||
"modified": "2024-08-27 09:29:03.961443",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Ledger Entry",
|
||||
|
||||
@@ -1028,6 +1028,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual(50, _get_stock_credit(final_consumption))
|
||||
|
||||
def test_tie_breaking(self):
|
||||
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_entries
|
||||
|
||||
frappe.flags.dont_execute_stock_reposts = True
|
||||
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
|
||||
|
||||
@@ -1070,6 +1072,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual([10, 11], ordered_qty_after_transaction())
|
||||
|
||||
first.cancel()
|
||||
repost_entries()
|
||||
self.assertEqual([1], ordered_qty_after_transaction())
|
||||
|
||||
backdated = make_stock_entry(
|
||||
@@ -1169,7 +1172,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
qty=5,
|
||||
posting_date="2021-01-01",
|
||||
rate=10,
|
||||
posting_time="02:00:00.1234",
|
||||
posting_time="02:00:00",
|
||||
)
|
||||
|
||||
time.sleep(3)
|
||||
@@ -1181,7 +1184,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
qty=100,
|
||||
rate=10,
|
||||
posting_date="2021-01-01",
|
||||
posting_time="02:00:00",
|
||||
posting_time="02:00:00.1234",
|
||||
)
|
||||
|
||||
sle = frappe.get_all(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _, bold, msgprint
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import add_to_date, cint, cstr, flt
|
||||
from frappe.utils import add_to_date, cint, cstr, flt, get_link_to_form
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
@@ -357,7 +357,6 @@ class StockReconciliation(StockController):
|
||||
|
||||
sl_entries.append(args)
|
||||
|
||||
qty_after_transaction = 0
|
||||
for serial_no in serial_nos:
|
||||
args = self.get_sle_for_items(row, [serial_no])
|
||||
|
||||
@@ -373,27 +372,16 @@ class StockReconciliation(StockController):
|
||||
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
|
||||
# If serial no exists in different warehouse
|
||||
|
||||
warehouse = previous_sle.get("warehouse", "") or row.warehouse
|
||||
|
||||
if not qty_after_transaction:
|
||||
qty_after_transaction = get_stock_balance(
|
||||
row.item_code, warehouse, self.posting_date, self.posting_time
|
||||
frappe.throw(
|
||||
_(
|
||||
"The Serial No {0} already exists in the warehouse {1}. It cannot be transferred to the warehouse {2}"
|
||||
).format(
|
||||
get_link_to_form("Serial No", serial_no),
|
||||
bold(previous_sle.get("warehouse")),
|
||||
row.warehouse,
|
||||
)
|
||||
|
||||
qty_after_transaction -= 1
|
||||
|
||||
new_args = args.copy()
|
||||
new_args.update(
|
||||
{
|
||||
"actual_qty": -1,
|
||||
"qty_after_transaction": qty_after_transaction,
|
||||
"warehouse": warehouse,
|
||||
"valuation_rate": previous_sle.get("valuation_rate"),
|
||||
}
|
||||
)
|
||||
|
||||
sl_entries.append(new_args)
|
||||
|
||||
if row.qty:
|
||||
args = self.get_sle_for_items(row)
|
||||
|
||||
|
||||
@@ -1119,6 +1119,33 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
active_serial_no = frappe.get_all("Serial No", filters={"status": "Active", "item_code": item_code})
|
||||
self.assertEqual(len(active_serial_no), 5)
|
||||
|
||||
def test_stock_reco_for_serialized_item_with_different_warehouse(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
# Add new serial nos
|
||||
serial_item_code = "Stock-Reco-Serial-Item-11"
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
|
||||
|
||||
self.make_item(
|
||||
serial_item_code, {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SNT-SRS11.####"}
|
||||
)
|
||||
|
||||
make_stock_entry(item_code=serial_item_code, target=warehouse, qty=10, basic_rate=100)
|
||||
stock_entry = make_stock_entry(
|
||||
item_code=serial_item_code, target=serial_warehouse, qty=10, basic_rate=200
|
||||
)
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=serial_item_code, warehouse=warehouse, qty=11, rate=200, do_not_submit=True
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(stock_entry.items[0].serial_no)
|
||||
|
||||
sr.items[0].serial_no += f"\n{serial_nos[0]}"
|
||||
sr.save()
|
||||
self.assertRaises(frappe.ValidationError, sr.submit)
|
||||
|
||||
|
||||
def create_batch_item_with_batch(item_name, batch_id):
|
||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||
|
||||
@@ -445,6 +445,7 @@ class update_entries_after:
|
||||
and (
|
||||
posting_datetime = %(posting_datetime)s
|
||||
)
|
||||
and creation = %(creation)s
|
||||
order by
|
||||
creation ASC
|
||||
for update
|
||||
@@ -1236,6 +1237,11 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
||||
voucher_no = args.get("voucher_no")
|
||||
voucher_condition = f"and voucher_no != '{voucher_no}'"
|
||||
|
||||
elif args.get("creation"):
|
||||
creation = args.get("creation")
|
||||
operator = "<="
|
||||
voucher_condition = f"and creation < '{creation}'"
|
||||
|
||||
sle = frappe.db.sql(
|
||||
f"""
|
||||
select *, posting_datetime as "timestamp"
|
||||
@@ -1247,7 +1253,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
||||
and (
|
||||
posting_datetime {operator} %(posting_datetime)s
|
||||
)
|
||||
order by posting_datetime desc, creation desc
|
||||
order by posting_date desc, posting_time desc, creation desc
|
||||
limit 1
|
||||
for update""",
|
||||
{
|
||||
@@ -1341,7 +1347,7 @@ def get_stock_ledger_entries(
|
||||
where item_code = %(item_code)s
|
||||
and is_cancelled = 0
|
||||
{conditions}
|
||||
order by posting_datetime {order}, creation {order}
|
||||
order by posting_date {order}, posting_time {order}, creation {order}
|
||||
{limit} {for_update}""".format(
|
||||
conditions=conditions,
|
||||
limit=limit or "",
|
||||
@@ -1446,7 +1452,7 @@ def get_valuation_rate(
|
||||
AND valuation_rate >= 0
|
||||
AND is_cancelled = 0
|
||||
AND NOT (voucher_no = %s AND voucher_type = %s)
|
||||
order by posting_datetime desc, name desc limit 1""",
|
||||
order by posting_date desc, posting_time desc, name desc limit 1""",
|
||||
(item_code, warehouse, voucher_no, voucher_type),
|
||||
)
|
||||
|
||||
@@ -1695,7 +1701,8 @@ def get_future_sle_with_negative_qty(sle):
|
||||
& (SLE.is_cancelled == 0)
|
||||
& (SLE.qty_after_transaction < 0)
|
||||
)
|
||||
.orderby(SLE.posting_datetime)
|
||||
.orderby(SLE.posting_date)
|
||||
.orderby(SLE.posting_time)
|
||||
.limit(1)
|
||||
)
|
||||
|
||||
@@ -1711,14 +1718,14 @@ def get_future_sle_with_negative_batch_qty(args):
|
||||
with batch_ledger as (
|
||||
select
|
||||
posting_date, posting_time, posting_datetime, voucher_type, voucher_no,
|
||||
sum(actual_qty) over (order by posting_datetime, creation) as cumulative_total
|
||||
sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total
|
||||
from `tabStock Ledger Entry`
|
||||
where
|
||||
item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
and batch_no=%(batch_no)s
|
||||
and is_cancelled = 0
|
||||
order by posting_datetime, creation
|
||||
order by posting_date, posting_time, creation
|
||||
)
|
||||
select * from batch_ledger
|
||||
where
|
||||
|
||||
@@ -9108,13 +9108,13 @@ Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
Ex Works,Ab Werk,
|
||||
Free Carrier,Frei Frachtführer,
|
||||
Free Alongside Ship,Frei Längsseite Schiff,
|
||||
Free on Board,Frei an Bord,
|
||||
Free On Board,Frei an Bord,
|
||||
Carriage Paid To,Frachtfrei,
|
||||
Carriage and Insurance Paid to,Frachtfrei versichert,
|
||||
Cost and Freight,Kosten und Fracht,
|
||||
"Cost, Insurance and Freight","Kosten, Versicherung und Fracht",
|
||||
Delivered at Place,Geliefert benannter Ort,
|
||||
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
|
||||
Delivered At Place,Geliefert benannter Ort,
|
||||
Delivered At Place Unloaded,Geliefert benannter Ort entladen,
|
||||
Delivered Duty Paid,Geliefert verzollt,
|
||||
Discount Validity,Frist für den Rabatt,
|
||||
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
|
||||
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user