Merge branch 'develop' into bank_statement_import_fix_develop
This commit is contained in:
commit
26dc02aa38
@ -231,25 +231,25 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_purchase_invoice_with_exchange_rate_difference(self):
|
||||
pr = make_purchase_receipt(currency = "USD", conversion_rate = 70)
|
||||
pi = make_purchase_invoice(currency = "USD", conversion_rate = 80, do_not_save = "True")
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as create_purchase_invoice
|
||||
|
||||
pi.items[0].purchase_receipt = pr.name
|
||||
pi.items[0].pr_detail = pr.items[0].name
|
||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
|
||||
currency = "USD", conversion_rate = 70)
|
||||
|
||||
pi = create_purchase_invoice(pr.name)
|
||||
pi.conversion_rate = 80
|
||||
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
# fetching the latest GL Entry with 'Exchange Gain/Loss - _TC' account
|
||||
gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - _TC'})
|
||||
voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no')
|
||||
# Get exchnage gain and loss account
|
||||
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
|
||||
|
||||
self.assertEqual(pi.name, voucher_no)
|
||||
|
||||
exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit')
|
||||
# fetching the latest GL Entry with exchange gain and loss account account
|
||||
amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pi.name}, 'debit')
|
||||
discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
|
||||
|
||||
self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff)
|
||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||
|
||||
def test_purchase_invoice_change_naming_series(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
@ -1031,21 +1031,21 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
# Check GLE for Purchase Invoice
|
||||
# Zero net effect on final TDS Payable on invoice
|
||||
expected_gle = [
|
||||
['_Test Account Cost for Goods Sold - _TC', 30000, 0],
|
||||
['_Test Account Excise Duty - _TC', 0, 3000],
|
||||
['Creditors - _TC', 0, 27000],
|
||||
['TDS Payable - _TC', 3000, 3000]
|
||||
['_Test Account Cost for Goods Sold - _TC', 30000],
|
||||
['_Test Account Excise Duty - _TC', -3000],
|
||||
['Creditors - _TC', -27000],
|
||||
['TDS Payable - _TC', 0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
group by account
|
||||
order by account asc""", (purchase_invoice.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.debit)
|
||||
self.assertEqual(expected_gle[i][2], gle.credit)
|
||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||
|
||||
def update_tax_witholding_category(company, account, date):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
|
||||
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||
from `tabGL Entry`
|
||||
where party_type = %s and party=%s
|
||||
and is_cancelled = 0
|
||||
group by company""", (party_type, party)))
|
||||
|
||||
for d in companies:
|
||||
|
@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
||||
gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
|
||||
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
|
||||
acc.account_name, acc.account_number
|
||||
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s
|
||||
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
|
||||
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
|
||||
order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions),
|
||||
{
|
||||
|
@ -1100,6 +1100,8 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
|
||||
},
|
||||
'BOM Item': {
|
||||
'doctype': 'BOM Item',
|
||||
# stop get_mapped_doc copying parent bom_no to children
|
||||
'field_no_map': ['bom_no'],
|
||||
'condition': lambda doc: doc.has_variants == 0
|
||||
},
|
||||
}, target_doc, postprocess)
|
||||
|
@ -8,6 +8,7 @@ import frappe
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.manufacturing.doctype.bom.bom import make_variant_bom
|
||||
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
@ -248,6 +249,37 @@ class TestBOM(unittest.TestCase):
|
||||
for reqd_item, created_item in zip(reqd_order, created_order):
|
||||
self.assertEqual(reqd_item, created_item.item_code)
|
||||
|
||||
def test_generated_variant_bom(self):
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
|
||||
template_item = make_item(
|
||||
"_TestTemplateItem", {"has_variants": 1, "attributes": [{"attribute": "Test Size"},]}
|
||||
)
|
||||
variant = create_variant(template_item.item_code, {"Test Size": "Large"})
|
||||
variant.insert(ignore_if_duplicate=True)
|
||||
|
||||
bom_tree = {
|
||||
template_item.item_code: {
|
||||
"SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
|
||||
"ChildPart5": {},
|
||||
}
|
||||
}
|
||||
template_bom = create_nested_bom(bom_tree, prefix="")
|
||||
variant_bom = make_variant_bom(
|
||||
template_bom.name, template_bom.name, variant.item_code, variant_items=[]
|
||||
)
|
||||
variant_bom.save()
|
||||
|
||||
reqd_order = template_bom.get_tree_representation().level_order_traversal()
|
||||
created_order = variant_bom.get_tree_representation().level_order_traversal()
|
||||
|
||||
self.assertEqual(len(reqd_order), len(created_order))
|
||||
|
||||
for reqd_item, created_item in zip(reqd_order, created_order):
|
||||
self.assertEqual(reqd_item.item_code, created_item.item_code)
|
||||
self.assertEqual(reqd_item.qty, created_item.qty)
|
||||
self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
@ -291,7 +291,7 @@ class PurchaseReceipt(BuyingController):
|
||||
continue
|
||||
|
||||
self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks,
|
||||
stock_rbnb, account_currency=warehouse_account_currency, item=d)
|
||||
stock_rbnb, account_currency=warehouse_account_currency, item=d)
|
||||
|
||||
# GL Entry for from warehouse or Stock Received but not billed
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
@ -318,11 +318,11 @@ class PurchaseReceipt(BuyingController):
|
||||
(exchange_rate_map[d.purchase_invoice] - self.conversion_rate)
|
||||
|
||||
self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference,
|
||||
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=credit_currency, item=d)
|
||||
|
||||
self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0,
|
||||
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0,
|
||||
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||
account_currency=credit_currency, item=d)
|
||||
|
||||
# Amount added through landed-cos-voucher
|
||||
@ -407,6 +407,7 @@ class PurchaseReceipt(BuyingController):
|
||||
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
|
||||
total_valuation_amount = sum(valuation_tax.values())
|
||||
amount_including_divisional_loss = negative_expense_to_be_booked
|
||||
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
|
||||
i = 1
|
||||
for tax in self.get("taxes"):
|
||||
if valuation_tax.get(tax.name):
|
||||
|
@ -1054,30 +1054,30 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
def test_purchase_receipt_with_exchange_rate_difference(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
|
||||
|
||||
pi = create_purchase_invoice(currency = "USD", conversion_rate = 70)
|
||||
|
||||
create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory",
|
||||
properties={"account": '_Test Account Stock In Hand - TCP1'})
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
|
||||
|
||||
pr = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1',
|
||||
company="_Test Company with perpetual inventory", currency = "USD", conversion_rate = 80,
|
||||
do_not_save = "True")
|
||||
|
||||
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
|
||||
cost_center = "Main - TCP1",
|
||||
warehouse = "Stores - TCP1",
|
||||
expense_account ="_Test Account Cost for Goods Sold - TCP1",
|
||||
currency = "USD", conversion_rate = 70)
|
||||
|
||||
pr = create_purchase_receipt(pi.name)
|
||||
pr.conversion_rate = 80
|
||||
pr.items[0].purchase_invoice = pi.name
|
||||
pr.items[0].purchase_invoice_item = pi.items[0].name
|
||||
|
||||
pr.insert()
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
# fetching the latest GL Entry with 'Exchange Gain/Loss - TCP1' account
|
||||
gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - TCP1'})
|
||||
voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no')
|
||||
self.assertEqual(pr.name, voucher_no)
|
||||
# Get exchnage gain and loss account
|
||||
exchange_gain_loss_account = frappe.db.get_value('Company', pr.company, 'exchange_gain_loss_account')
|
||||
|
||||
exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit')
|
||||
# fetching the latest GL Entry with exchange gain and loss account account
|
||||
amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pr.name}, 'credit')
|
||||
discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
|
||||
self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff)
|
||||
|
||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||
|
||||
def get_sl_entries(voucher_type, voucher_no):
|
||||
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
|
||||
|
@ -357,6 +357,7 @@ class StockReconciliation(StockController):
|
||||
if row.current_qty:
|
||||
data.actual_qty = -1 * row.current_qty
|
||||
data.qty_after_transaction = flt(row.current_qty)
|
||||
data.previous_qty_after_transaction = flt(row.qty)
|
||||
data.valuation_rate = flt(row.current_valuation_rate)
|
||||
data.stock_value = data.qty_after_transaction * data.valuation_rate
|
||||
data.stock_value_difference = -1 * flt(row.amount_difference)
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, unittest
|
||||
from frappe.utils import flt, nowdate, nowtime
|
||||
from frappe.utils import flt, nowdate, nowtime, add_days
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
|
||||
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
|
||||
@ -14,6 +14,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
class TestStockReconciliation(unittest.TestCase):
|
||||
@classmethod
|
||||
@ -204,6 +205,117 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
self.assertEqual(sr.get("items")[0].valuation_rate, 0)
|
||||
self.assertEqual(sr.get("items")[0].amount, 0)
|
||||
|
||||
def test_backdated_stock_reco_qty_reposting(self):
|
||||
"""
|
||||
Test if a backdated stock reco recalculates future qty until next reco.
|
||||
-------------------------------------------
|
||||
Var | Doc | Qty | Balance
|
||||
-------------------------------------------
|
||||
SR5 | Reco | 0 | 8 (posting date: today-4) [backdated]
|
||||
PR1 | PR | 10 | 18 (posting date: today-3)
|
||||
PR2 | PR | 1 | 19 (posting date: today-2)
|
||||
SR4 | Reco | 0 | 6 (posting date: today-1) [backdated]
|
||||
PR3 | PR | 1 | 7 (posting date: today) # can't post future PR
|
||||
"""
|
||||
item_code = "Backdated-Reco-Item"
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
|
||||
posting_date=add_days(nowdate(), -3))
|
||||
pr2 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
|
||||
posting_date=add_days(nowdate(), -2))
|
||||
pr3 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
|
||||
posting_date=nowdate())
|
||||
|
||||
pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
self.assertEqual(pr1_balance, 10)
|
||||
self.assertEqual(pr3_balance, 12)
|
||||
|
||||
# post backdated stock reco in between
|
||||
sr4 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=6, rate=100,
|
||||
posting_date=add_days(nowdate(), -1))
|
||||
pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
self.assertEqual(pr3_balance, 7)
|
||||
|
||||
# post backdated stock reco at the start
|
||||
sr5 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=8, rate=100,
|
||||
posting_date=add_days(nowdate(), -4))
|
||||
pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
self.assertEqual(pr1_balance, 18)
|
||||
self.assertEqual(pr2_balance, 19)
|
||||
self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
|
||||
|
||||
# cancel backdated stock reco and check future impact
|
||||
sr5.cancel()
|
||||
pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
self.assertEqual(pr1_balance, 10)
|
||||
self.assertEqual(pr2_balance, 11)
|
||||
self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
|
||||
|
||||
# teardown
|
||||
sr4.cancel()
|
||||
pr3.cancel()
|
||||
pr2.cancel()
|
||||
pr1.cancel()
|
||||
|
||||
def test_backdated_stock_reco_future_negative_stock(self):
|
||||
"""
|
||||
Test if a backdated stock reco causes future negative stock and is blocked.
|
||||
-------------------------------------------
|
||||
Var | Doc | Qty | Balance
|
||||
-------------------------------------------
|
||||
PR1 | PR | 10 | 10 (posting date: today-2)
|
||||
SR3 | Reco | 0 | 1 (posting date: today-1) [backdated & blocked]
|
||||
DN2 | DN | -2 | 8(-1) (posting date: today)
|
||||
"""
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
item_code = "Backdated-Reco-Item"
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
create_item(item_code)
|
||||
|
||||
negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0)
|
||||
|
||||
pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
|
||||
posting_date=add_days(nowdate(), -2))
|
||||
dn2 = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=2, rate=120,
|
||||
posting_date=nowdate())
|
||||
|
||||
pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
dn2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn2.name, "is_cancelled": 0},
|
||||
"qty_after_transaction")
|
||||
self.assertEqual(pr1_balance, 10)
|
||||
self.assertEqual(dn2_balance, 8)
|
||||
|
||||
# check if stock reco is blocked
|
||||
sr3 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
|
||||
posting_date=add_days(nowdate(), -1), do_not_submit=True)
|
||||
self.assertRaises(NegativeStockError, sr3.submit)
|
||||
|
||||
# teardown
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting)
|
||||
sr3.cancel()
|
||||
dn2.cancel()
|
||||
pr1.cancel()
|
||||
|
||||
def insert_existing_sle(warehouse):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
|
@ -55,6 +55,11 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
||||
sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
|
||||
|
||||
args = sle_doc.as_dict()
|
||||
|
||||
if sle.get("voucher_type") == "Stock Reconciliation":
|
||||
# preserve previous_qty_after_transaction for qty reposting
|
||||
args.previous_qty_after_transaction = sle.get("previous_qty_after_transaction")
|
||||
|
||||
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
|
||||
|
||||
def get_args_for_future_sle(row):
|
||||
@ -215,7 +220,7 @@ class update_entries_after(object):
|
||||
"""
|
||||
self.data.setdefault(args.warehouse, frappe._dict())
|
||||
warehouse_dict = self.data[args.warehouse]
|
||||
previous_sle = self.get_previous_sle_of_current_voucher(args)
|
||||
previous_sle = get_previous_sle_of_current_voucher(args)
|
||||
warehouse_dict.previous_sle = previous_sle
|
||||
|
||||
for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
|
||||
@ -227,29 +232,6 @@ class update_entries_after(object):
|
||||
"stock_value_difference": 0.0
|
||||
})
|
||||
|
||||
def get_previous_sle_of_current_voucher(self, args):
|
||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||
|
||||
args['time_format'] = '%H:%i:%s'
|
||||
if not args.get("posting_date"):
|
||||
args["posting_date"] = "1900-01-01"
|
||||
if not args.get("posting_time"):
|
||||
args["posting_time"] = "00:00"
|
||||
|
||||
sle = frappe.db.sql("""
|
||||
select *, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
and is_cancelled = 0
|
||||
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||
order by timestamp(posting_date, posting_time) desc, creation desc
|
||||
limit 1
|
||||
for update""", args, as_dict=1)
|
||||
|
||||
return sle[0] if sle else frappe._dict()
|
||||
|
||||
|
||||
def build(self):
|
||||
from erpnext.controllers.stock_controller import future_sle_exists
|
||||
|
||||
@ -734,6 +716,35 @@ class update_entries_after(object):
|
||||
bin_doc.flags.via_stock_ledger_entry = True
|
||||
bin_doc.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
|
||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||
|
||||
args['time_format'] = '%H:%i:%s'
|
||||
if not args.get("posting_date"):
|
||||
args["posting_date"] = "1900-01-01"
|
||||
if not args.get("posting_time"):
|
||||
args["posting_time"] = "00:00"
|
||||
|
||||
voucher_condition = ""
|
||||
if exclude_current_voucher:
|
||||
voucher_no = args.get("voucher_no")
|
||||
voucher_condition = f"and voucher_no != '{voucher_no}'"
|
||||
|
||||
sle = frappe.db.sql("""
|
||||
select *, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
and is_cancelled = 0
|
||||
{voucher_condition}
|
||||
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
|
||||
order by timestamp(posting_date, posting_time) desc, creation desc
|
||||
limit 1
|
||||
for update""".format(voucher_condition=voucher_condition), args, as_dict=1)
|
||||
|
||||
return sle[0] if sle else frappe._dict()
|
||||
|
||||
def get_previous_sle(args, for_update=False):
|
||||
"""
|
||||
get the last sle on or before the current time-bucket,
|
||||
@ -862,9 +873,24 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
||||
return valuation_rate
|
||||
|
||||
def update_qty_in_future_sle(args, allow_negative_stock=None):
|
||||
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
|
||||
datetime_limit_condition = ""
|
||||
qty_shift = args.actual_qty
|
||||
|
||||
# find difference/shift in qty caused by stock reconciliation
|
||||
if args.voucher_type == "Stock Reconciliation":
|
||||
qty_shift = get_stock_reco_qty_shift(args)
|
||||
|
||||
# find the next nearest stock reco so that we only recalculate SLEs till that point
|
||||
next_stock_reco_detail = get_next_stock_reco(args)
|
||||
if next_stock_reco_detail:
|
||||
detail = next_stock_reco_detail[0]
|
||||
# add condition to update SLEs before this date & time
|
||||
datetime_limit_condition = get_datetime_limit_condition(detail)
|
||||
|
||||
frappe.db.sql("""
|
||||
update `tabStock Ledger Entry`
|
||||
set qty_after_transaction = qty_after_transaction + {qty}
|
||||
set qty_after_transaction = qty_after_transaction + {qty_shift}
|
||||
where
|
||||
item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
@ -876,15 +902,70 @@ def update_qty_in_future_sle(args, allow_negative_stock=None):
|
||||
and creation > %(creation)s
|
||||
)
|
||||
)
|
||||
""".format(qty=args.actual_qty), args)
|
||||
{datetime_limit_condition}
|
||||
""".format(qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition), args)
|
||||
|
||||
validate_negative_qty_in_future_sle(args, allow_negative_stock)
|
||||
|
||||
def get_stock_reco_qty_shift(args):
|
||||
stock_reco_qty_shift = 0
|
||||
if args.get("is_cancelled"):
|
||||
if args.get("previous_qty_after_transaction"):
|
||||
# get qty (balance) that was set at submission
|
||||
last_balance = args.get("previous_qty_after_transaction")
|
||||
stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance)
|
||||
else:
|
||||
stock_reco_qty_shift = flt(args.actual_qty)
|
||||
else:
|
||||
# reco is being submitted
|
||||
last_balance = get_previous_sle_of_current_voucher(args,
|
||||
exclude_current_voucher=True).get("qty_after_transaction")
|
||||
|
||||
if last_balance is not None:
|
||||
stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance)
|
||||
else:
|
||||
stock_reco_qty_shift = args.qty_after_transaction
|
||||
|
||||
return stock_reco_qty_shift
|
||||
|
||||
def get_next_stock_reco(args):
|
||||
"""Returns next nearest stock reconciliaton's details."""
|
||||
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
name, posting_date, posting_time, creation, voucher_no
|
||||
from
|
||||
`tabStock Ledger Entry`
|
||||
where
|
||||
item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
and voucher_type = 'Stock Reconciliation'
|
||||
and voucher_no != %(voucher_no)s
|
||||
and is_cancelled = 0
|
||||
and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
|
||||
or (
|
||||
timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
|
||||
and creation > %(creation)s
|
||||
)
|
||||
)
|
||||
limit 1
|
||||
""", args, as_dict=1)
|
||||
|
||||
def get_datetime_limit_condition(detail):
|
||||
return f"""
|
||||
and
|
||||
(timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}')
|
||||
or (
|
||||
timestamp(posting_date, posting_time) = timestamp('{detail.posting_date}', '{detail.posting_time}')
|
||||
and creation < '{detail.creation}'
|
||||
)
|
||||
)"""
|
||||
|
||||
def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
|
||||
allow_negative_stock = allow_negative_stock \
|
||||
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||
|
||||
if args.actual_qty < 0 and not allow_negative_stock:
|
||||
if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock:
|
||||
sle = get_future_sle_with_negative_qty(args)
|
||||
if sle:
|
||||
message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
|
||||
|
@ -81,6 +81,7 @@ class TestServiceLevelAgreement(unittest.TestCase):
|
||||
|
||||
# check SLA custom fields created for leads
|
||||
sla_fields = get_service_level_agreement_fields()
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
|
||||
for field in sla_fields:
|
||||
|
Loading…
x
Reference in New Issue
Block a user