mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 21:29:11 +00:00
Merge pull request #41264 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -486,6 +486,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||||
# Apply discount on discounted rate
|
# Apply discount on discounted rate
|
||||||
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
|
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
|
||||||
|
elif args.price_list_rate:
|
||||||
|
value = pricing_rule.get(field, 0)
|
||||||
|
calculate_discount_percentage = False
|
||||||
|
if field == "discount_percentage":
|
||||||
|
field = "discount_amount"
|
||||||
|
value = args.price_list_rate * (value / 100)
|
||||||
|
calculate_discount_percentage = True
|
||||||
|
|
||||||
|
if field not in item_details:
|
||||||
|
item_details.setdefault(field, 0)
|
||||||
|
|
||||||
|
item_details[field] += value if pricing_rule else args.get(field, 0)
|
||||||
|
if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount:
|
||||||
|
item_details.discount_percentage = flt(
|
||||||
|
(flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if field not in item_details:
|
if field not in item_details:
|
||||||
item_details.setdefault(field, 0)
|
item_details.setdefault(field, 0)
|
||||||
|
|||||||
@@ -978,6 +978,59 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||||
self.assertEqual(so.items[1].qty, 4)
|
self.assertEqual(so.items[1].qty, 4)
|
||||||
|
|
||||||
|
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
test_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Pricing Rule 1",
|
||||||
|
"name": "_Test Pricing Rule 1",
|
||||||
|
"apply_on": "Item Code",
|
||||||
|
"currency": "USD",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selling": 1,
|
||||||
|
"price_or_product_discount": "Price",
|
||||||
|
"rate_or_discount": "Discount Percentage",
|
||||||
|
"discount_percentage": 10,
|
||||||
|
"apply_multiple_pricing_rules": 1,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.get_doc(test_record.copy()).insert()
|
||||||
|
|
||||||
|
test_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Pricing Rule 2",
|
||||||
|
"name": "_Test Pricing Rule 2",
|
||||||
|
"apply_on": "Item Code",
|
||||||
|
"currency": "USD",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selling": 1,
|
||||||
|
"price_or_product_discount": "Price",
|
||||||
|
"rate_or_discount": "Discount Amount",
|
||||||
|
"discount_amount": 100,
|
||||||
|
"apply_multiple_pricing_rules": 1,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.get_doc(test_record.copy()).insert()
|
||||||
|
|
||||||
|
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
|
||||||
|
self.assertEqual(so.items[0].discount_amount, 200)
|
||||||
|
self.assertEqual(so.items[0].rate, 800)
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Sales Order", so.name)
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
|
|
||||||
test_dependencies = ["Campaign"]
|
test_dependencies = ["Campaign"]
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-04-13 18:44:25.055382",
|
"modified": "2024-04-30 10:26:48.21829",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Tax Withholding Account",
|
"name": "Tax Withholding Account",
|
||||||
|
|||||||
@@ -253,6 +253,14 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
|||||||
if taxable_vouchers:
|
if taxable_vouchers:
|
||||||
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
|
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
|
||||||
|
|
||||||
|
# If advance is outside the current tax withholding period (usually a fiscal year), `get_deducted_tax` won't fetch it.
|
||||||
|
# updating `tax_deducted` with correct advance tax value (from current and previous previous withholding periods), will allow the
|
||||||
|
# rest of the below logic to function properly
|
||||||
|
# ---FY 2023-------------||---------------------FY 2024-----------------------||--
|
||||||
|
# ---Advance-------------||---------Inv_1--------Inv_2------------------------||--
|
||||||
|
if tax_deducted_on_advances:
|
||||||
|
tax_deducted += get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details)
|
||||||
|
|
||||||
tax_amount = 0
|
tax_amount = 0
|
||||||
|
|
||||||
if party_type == "Supplier":
|
if party_type == "Supplier":
|
||||||
@@ -389,7 +397,7 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
|||||||
frappe.qb.from_(at)
|
frappe.qb.from_(at)
|
||||||
.inner_join(pe)
|
.inner_join(pe)
|
||||||
.on(pe.name == at.parent)
|
.on(pe.name == at.parent)
|
||||||
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
|
.select(pe.posting_date, at.parent, at.name, at.tax_amount, at.allocated_amount)
|
||||||
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
|
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
|
||||||
.where(at.parent.isin(advances))
|
.where(at.parent.isin(advances))
|
||||||
.where(at.account_head == tax_details.account_head)
|
.where(at.account_head == tax_details.account_head)
|
||||||
@@ -414,6 +422,16 @@ def get_deducted_tax(taxable_vouchers, tax_details):
|
|||||||
return sum(entries)
|
return sum(entries)
|
||||||
|
|
||||||
|
|
||||||
|
def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
|
||||||
|
"""
|
||||||
|
Only applies for Taxes deducted on Advance Payments
|
||||||
|
"""
|
||||||
|
advance_tax_from_across_fiscal_year = sum(
|
||||||
|
[adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date]
|
||||||
|
)
|
||||||
|
return advance_tax_from_across_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
||||||
tds_amount = 0
|
tds_amount = 0
|
||||||
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
|
import datetime
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
from frappe.tests.utils import change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import today
|
from frappe.utils import add_days, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
||||||
|
|
||||||
test_dependencies = ["Supplier Group", "Customer Group"]
|
test_dependencies = ["Supplier Group", "Customer Group"]
|
||||||
|
|
||||||
|
|
||||||
class TestTaxWithholdingCategory(unittest.TestCase):
|
class TestTaxWithholdingCategory(FrappeTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
# create relevant supplier, etc
|
# create relevant supplier, etc
|
||||||
@@ -22,7 +25,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
make_pan_no_field()
|
make_pan_no_field()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
cancel_invoices()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def test_cumulative_threshold_tds(self):
|
def test_cumulative_threshold_tds(self):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
@@ -322,8 +325,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
d.cancel()
|
d.cancel()
|
||||||
|
|
||||||
def test_tds_deduction_for_po_via_payment_entry(self):
|
def test_tds_deduction_for_po_via_payment_entry(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
|
||||||
|
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
|
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||||
)
|
)
|
||||||
@@ -490,6 +491,133 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
pi2.cancel()
|
pi2.cancel()
|
||||||
pi3.cancel()
|
pi3.cancel()
|
||||||
|
|
||||||
|
def set_previous_fy_and_tax_category(self):
|
||||||
|
test_company = "_Test Company"
|
||||||
|
category = "Cumulative Threshold TDS"
|
||||||
|
|
||||||
|
def add_company_to_fy(fy, company):
|
||||||
|
if not [x.company for x in fy.companies if x.company == company]:
|
||||||
|
fy.append("companies", {"company": company})
|
||||||
|
fy.save()
|
||||||
|
|
||||||
|
# setup previous fiscal year
|
||||||
|
fiscal_year = get_fiscal_year(today(), company=test_company)
|
||||||
|
if prev_fiscal_year := get_fiscal_year(add_days(fiscal_year[1], -10)):
|
||||||
|
self.prev_fy = frappe.get_doc("Fiscal Year", prev_fiscal_year[0])
|
||||||
|
add_company_to_fy(self.prev_fy, test_company)
|
||||||
|
else:
|
||||||
|
# make previous fiscal year
|
||||||
|
start = datetime.date(fiscal_year[1].year - 1, fiscal_year[1].month, fiscal_year[1].day)
|
||||||
|
end = datetime.date(fiscal_year[2].year - 1, fiscal_year[2].month, fiscal_year[2].day)
|
||||||
|
self.prev_fy = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year_start_date": start,
|
||||||
|
"year_end_date": end,
|
||||||
|
"companies": [{"company": test_company}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.prev_fy.save()
|
||||||
|
|
||||||
|
# setup tax withholding category for previous fiscal year
|
||||||
|
cat = frappe.get_doc("Tax Withholding Category", category)
|
||||||
|
cat.append(
|
||||||
|
"rates",
|
||||||
|
{
|
||||||
|
"from_date": self.prev_fy.year_start_date,
|
||||||
|
"to_date": self.prev_fy.year_end_date,
|
||||||
|
"tax_withholding_rate": 10,
|
||||||
|
"single_threshold": 0,
|
||||||
|
"cumulative_threshold": 30000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cat.save()
|
||||||
|
|
||||||
|
def test_tds_across_fiscal_year(self):
|
||||||
|
"""
|
||||||
|
Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year
|
||||||
|
--||-----FY 2023-----||-----FY 2024-----||--
|
||||||
|
--||-----Advance-----||---Inv1---Inv2---||--
|
||||||
|
"""
|
||||||
|
self.set_previous_fy_and_tax_category()
|
||||||
|
supplier = "Test TDS Supplier"
|
||||||
|
# Cumulative threshold 30000 and tax rate 10%
|
||||||
|
category = "Cumulative Threshold TDS"
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Supplier",
|
||||||
|
supplier,
|
||||||
|
{
|
||||||
|
"tax_withholding_category": category,
|
||||||
|
"pan": "ABCTY1234D",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
po_and_advance_posting_date = add_days(self.prev_fy.year_end_date, -10)
|
||||||
|
po = create_purchase_order(supplier=supplier, qty=10, rate=10000)
|
||||||
|
po.transaction_date = po_and_advance_posting_date
|
||||||
|
po.taxes = []
|
||||||
|
po.apply_tds = False
|
||||||
|
po.tax_withholding_category = None
|
||||||
|
po.save().submit()
|
||||||
|
|
||||||
|
# Partial advance
|
||||||
|
payment = get_payment_entry(po.doctype, po.name)
|
||||||
|
payment.posting_date = po_and_advance_posting_date
|
||||||
|
payment.paid_amount = 60000
|
||||||
|
payment.apply_tax_withholding_amount = 1
|
||||||
|
payment.tax_withholding_category = category
|
||||||
|
payment.references = []
|
||||||
|
payment.taxes = []
|
||||||
|
payment.save().submit()
|
||||||
|
|
||||||
|
self.assertEqual(len(payment.taxes), 1)
|
||||||
|
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||||
|
|
||||||
|
# Multiple partial invoices
|
||||||
|
payment.reload()
|
||||||
|
pi1 = make_purchase_invoice(source_name=po.name)
|
||||||
|
pi1.apply_tds = True
|
||||||
|
pi1.tax_withholding_category = category
|
||||||
|
pi1.items[0].qty = 3
|
||||||
|
pi1.items[0].rate = 10000
|
||||||
|
advances = pi1.get_advance_entries()
|
||||||
|
pi1.append(
|
||||||
|
"advances",
|
||||||
|
{
|
||||||
|
"reference_type": advances[0].reference_type,
|
||||||
|
"reference_name": advances[0].reference_name,
|
||||||
|
"advance_amount": advances[0].amount,
|
||||||
|
"allocated_amount": 30000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pi1.save().submit()
|
||||||
|
pi1.reload()
|
||||||
|
payment.reload()
|
||||||
|
self.assertEqual(pi1.taxes, [])
|
||||||
|
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||||
|
self.assertEqual(payment.taxes[0].allocated_amount, 3000)
|
||||||
|
|
||||||
|
pi2 = make_purchase_invoice(source_name=po.name)
|
||||||
|
pi2.apply_tds = True
|
||||||
|
pi2.tax_withholding_category = category
|
||||||
|
pi2.items[0].qty = 3
|
||||||
|
pi2.items[0].rate = 10000
|
||||||
|
advances = pi2.get_advance_entries()
|
||||||
|
pi2.append(
|
||||||
|
"advances",
|
||||||
|
{
|
||||||
|
"reference_type": advances[0].reference_type,
|
||||||
|
"reference_name": advances[0].reference_name,
|
||||||
|
"advance_amount": advances[0].amount,
|
||||||
|
"allocated_amount": 30000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pi2.save().submit()
|
||||||
|
pi2.reload()
|
||||||
|
payment.reload()
|
||||||
|
self.assertEqual(pi2.taxes, [])
|
||||||
|
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||||
|
self.assertEqual(payment.taxes[0].allocated_amount, 6000)
|
||||||
|
|
||||||
|
|
||||||
def cancel_invoices():
|
def cancel_invoices():
|
||||||
purchase_invoices = frappe.get_all(
|
purchase_invoices = frappe.get_all(
|
||||||
|
|||||||
@@ -501,8 +501,9 @@ class ReceivablePayableReport:
|
|||||||
# Deduct that from paid amount pre allocation
|
# Deduct that from paid amount pre allocation
|
||||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||||
|
|
||||||
# If no or single payment terms, no need to split the row
|
# If single payment terms, no need to split the row
|
||||||
if len(payment_terms_details) <= 1:
|
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
|
||||||
|
self.append_payment_term(row, payment_terms_details[0], original_row)
|
||||||
return
|
return
|
||||||
|
|
||||||
for d in payment_terms_details:
|
for d in payment_terms_details:
|
||||||
|
|||||||
@@ -479,6 +479,11 @@ def reconcile_against_document(
|
|||||||
# re-submit advance entry
|
# re-submit advance entry
|
||||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||||
gl_map = doc.build_gl_map()
|
gl_map = doc.build_gl_map()
|
||||||
|
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
||||||
|
|
||||||
|
# Make sure there is no overallocation
|
||||||
|
process_debit_credit_difference(gl_map)
|
||||||
|
|
||||||
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
|
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
|
||||||
|
|
||||||
# Only update outstanding for newly linked vouchers
|
# Only update outstanding for newly linked vouchers
|
||||||
|
|||||||
@@ -1008,7 +1008,12 @@ def is_reposting_pending():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def future_sle_exists(args, sl_entries=None):
|
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
|
||||||
|
if allow_force_reposting and frappe.db.get_single_value(
|
||||||
|
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
key = (args.voucher_type, args.voucher_no)
|
key = (args.voucher_type, args.voucher_no)
|
||||||
if not hasattr(frappe.local, "future_sle"):
|
if not hasattr(frappe.local, "future_sle"):
|
||||||
frappe.local.future_sle = {}
|
frappe.local.future_sle = {}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ def update_qty(bin_name, args):
|
|||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
# actual qty is not up to date in case of backdated transaction
|
# actual qty is not up to date in case of backdated transaction
|
||||||
if future_sle_exists(args):
|
if future_sle_exists(args, allow_force_reposting=False):
|
||||||
last_sle_qty = (
|
last_sle_qty = (
|
||||||
frappe.qb.from_(sle)
|
frappe.qb.from_(sle)
|
||||||
.select(sle.qty_after_transaction)
|
.select(sle.qty_after_transaction)
|
||||||
|
|||||||
@@ -739,7 +739,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_delivery_trip(source_name, target_doc=None):
|
def make_delivery_trip(source_name, target_doc=None, kwargs=None):
|
||||||
def update_stop_details(source_doc, target_doc, source_parent):
|
def update_stop_details(source_doc, target_doc, source_parent):
|
||||||
target_doc.customer = source_parent.customer
|
target_doc.customer = source_parent.customer
|
||||||
target_doc.address = source_parent.shipping_address_name
|
target_doc.address = source_parent.shipping_address_name
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ frappe.ui.form.on("Delivery Trip", {
|
|||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Delivery Note"),
|
__("Delivery Note"),
|
||||||
() => {
|
() => {
|
||||||
|
frm.clear_table("delivery_stops");
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
||||||
source_doctype: "Delivery Note",
|
source_doctype: "Delivery Note",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"end_time",
|
"end_time",
|
||||||
"limits_dont_apply_on",
|
"limits_dont_apply_on",
|
||||||
"item_based_reposting",
|
"item_based_reposting",
|
||||||
|
"do_reposting_for_each_stock_transaction",
|
||||||
"errors_notification_section",
|
"errors_notification_section",
|
||||||
"notify_reposting_error_to_role"
|
"notify_reposting_error_to_role"
|
||||||
],
|
],
|
||||||
@@ -65,12 +66,18 @@
|
|||||||
"fieldname": "errors_notification_section",
|
"fieldname": "errors_notification_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Errors Notification"
|
"label": "Errors Notification"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "do_reposting_for_each_stock_transaction",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Do reposting for each Stock Transaction"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-01 16:14:29.080697",
|
"modified": "2024-04-24 12:19:40.204888",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Reposting Settings",
|
"name": "Stock Reposting Settings",
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ class StockRepostingSettings(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_minimum_reposting_time_slot()
|
self.set_minimum_reposting_time_slot()
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
if self.do_reposting_for_each_stock_transaction:
|
||||||
|
self.item_based_reposting = 1
|
||||||
|
|
||||||
def set_minimum_reposting_time_slot(self):
|
def set_minimum_reposting_time_slot(self):
|
||||||
"""Ensure that timeslot for reposting is at least 12 hours."""
|
"""Ensure that timeslot for reposting is at least 12 hours."""
|
||||||
if not self.limit_reposting_timeslot:
|
if not self.limit_reposting_timeslot:
|
||||||
|
|||||||
@@ -38,3 +38,51 @@ class TestStockRepostingSettings(unittest.TestCase):
|
|||||||
|
|
||||||
users = get_recipients()
|
users = get_recipients()
|
||||||
self.assertTrue(user in users)
|
self.assertTrue(user in users)
|
||||||
|
|
||||||
|
def test_do_reposting_for_each_stock_transaction(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1)
|
||||||
|
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
||||||
|
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test item for reposting check for each transaction", properties={"is_stock_item": 1}
|
||||||
|
).name
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
stock_entry_type="Material Receipt",
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
||||||
|
self.assertTrue(riv)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
||||||
|
|
||||||
|
def test_do_not_reposting_for_each_stock_transaction(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
||||||
|
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
||||||
|
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1}
|
||||||
|
).name
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
stock_entry_type="Material Receipt",
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
||||||
|
self.assertFalse(riv)
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ def get_stock_ledger_entries(filters):
|
|||||||
|
|
||||||
|
|
||||||
def get_item_warehouse_batch_map(filters, float_precision):
|
def get_item_warehouse_batch_map(filters, float_precision):
|
||||||
|
_system_settings = frappe.get_cached_doc("System Settings")
|
||||||
with frappe.db.unbuffered_cursor():
|
with frappe.db.unbuffered_cursor():
|
||||||
sle = get_stock_ledger_entries(filters)
|
sle = get_stock_ledger_entries(filters)
|
||||||
sle = sle.run(as_dict=True, as_iterator=True)
|
sle = sle.run(as_dict=True, as_iterator=True)
|
||||||
|
|||||||
@@ -229,11 +229,12 @@ class FIFOSlots:
|
|||||||
"""
|
"""
|
||||||
stock_ledger_entries = self.sle
|
stock_ledger_entries = self.sle
|
||||||
|
|
||||||
|
_system_settings = frappe.get_cached_doc("System Settings")
|
||||||
with frappe.db.unbuffered_cursor():
|
with frappe.db.unbuffered_cursor():
|
||||||
if self.sle is None:
|
if stock_ledger_entries is None:
|
||||||
self.sle = self.__get_stock_ledger_entries()
|
stock_ledger_entries = self.__get_stock_ledger_entries()
|
||||||
|
|
||||||
for d in self.sle:
|
for d in stock_ledger_entries:
|
||||||
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
||||||
|
|
||||||
if d.voucher_type == "Stock Reconciliation":
|
if d.voucher_type == "Stock Reconciliation":
|
||||||
|
|||||||
@@ -141,6 +141,8 @@ class StockBalanceReport:
|
|||||||
if self.filters.get("show_stock_ageing_data"):
|
if self.filters.get("show_stock_ageing_data"):
|
||||||
self.sle_entries = self.sle_query.run(as_dict=True)
|
self.sle_entries = self.sle_query.run(as_dict=True)
|
||||||
|
|
||||||
|
# HACK: This is required to avoid causing db query in flt
|
||||||
|
_system_settings = frappe.get_cached_doc("System Settings")
|
||||||
with frappe.db.unbuffered_cursor():
|
with frappe.db.unbuffered_cursor():
|
||||||
if not self.filters.get("show_stock_ageing_data"):
|
if not self.filters.get("show_stock_ageing_data"):
|
||||||
self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
|
self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
|
||||||
|
|||||||
@@ -156,13 +156,6 @@ def get_columns(filters):
|
|||||||
"width": 100,
|
"width": 100,
|
||||||
"convertible": "qty",
|
"convertible": "qty",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": _("Voucher #"),
|
|
||||||
"fieldname": "voucher_no",
|
|
||||||
"fieldtype": "Dynamic Link",
|
|
||||||
"options": "voucher_type",
|
|
||||||
"width": 150,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": _("Warehouse"),
|
"label": _("Warehouse"),
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
|
|||||||
@@ -1490,6 +1490,10 @@ def get_stock_reco_qty_shift(args):
|
|||||||
stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance)
|
stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance)
|
||||||
else:
|
else:
|
||||||
stock_reco_qty_shift = flt(args.actual_qty)
|
stock_reco_qty_shift = flt(args.actual_qty)
|
||||||
|
|
||||||
|
elif args.get("serial_no") or args.get("batch_no"):
|
||||||
|
stock_reco_qty_shift = flt(args.actual_qty)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# reco is being submitted
|
# reco is being submitted
|
||||||
last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get(
|
last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get(
|
||||||
@@ -1559,6 +1563,15 @@ def get_datetime_limit_condition(detail):
|
|||||||
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
||||||
if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
|
if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
args.voucher_type == "Stock Reconciliation"
|
||||||
|
and args.actual_qty < 0
|
||||||
|
and args.get("batch_no")
|
||||||
|
and frappe.db.get_value("Stock Reconciliation Item", args.voucher_detail_no, "qty") > 0
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
|
if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Married,既婚,
|
|
||||||
|
Reference in New Issue
Block a user