mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-27 14:38:18 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c7dc5328e | ||
|
|
cb655226de | ||
|
|
010ccf80f5 | ||
|
|
89695022c7 | ||
|
|
50627733b4 | ||
|
|
7942ad488e | ||
|
|
c3d7348c73 | ||
|
|
9a2674ea29 | ||
|
|
0405aae4a0 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.47.1"
|
||||
__version__ = "14.47.4"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -181,7 +181,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
|
||||
make_maintenance_schedule() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
|
||||
@@ -557,15 +556,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// Income Account in Details Table
|
||||
// --------------------------------
|
||||
cur_frm.set_query("income_account", "items", function(doc) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {'company': doc.company}
|
||||
}
|
||||
});
|
||||
|
||||
// Cost Center in Details Table
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
|
||||
@@ -660,6 +650,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("income_account", "items", function() {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
|
||||
@@ -173,12 +173,18 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
|
||||
@@ -281,11 +281,20 @@ class ReceivablePayableReport(object):
|
||||
|
||||
row.invoice_grand_total = row.invoiced
|
||||
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
must_consider = False
|
||||
if self.filters.get("for_revaluation_journals"):
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) or (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
):
|
||||
must_consider = True
|
||||
else:
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
must_consider = True
|
||||
|
||||
if must_consider:
|
||||
# non-zero oustanding, we must consider this row
|
||||
|
||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||
|
||||
@@ -560,6 +560,8 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"warehouse",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"is_adjustment_entry",
|
||||
"column_break_6",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
@@ -309,6 +310,12 @@
|
||||
"label": "Recalculate Incoming/Outgoing Rate",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_adjustment_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Adjustment Entry"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -317,7 +324,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-12-21 06:25:30.040801",
|
||||
"modified": "2023-10-23 18:07:42.063615",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Ledger Entry",
|
||||
@@ -341,4 +348,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,10 @@ class StockReconciliation(StockController):
|
||||
if item.has_batch_no:
|
||||
has_batch_no = True
|
||||
|
||||
if not row.qty and not row.valuation_rate and not row.current_qty:
|
||||
self.make_adjustment_entry(row, sl_entries)
|
||||
continue
|
||||
|
||||
if item.has_serial_no or item.has_batch_no:
|
||||
has_serial_no = True
|
||||
self.get_sle_for_serialized_items(row, sl_entries, item)
|
||||
@@ -402,6 +406,21 @@ class StockReconciliation(StockController):
|
||||
# update valuation rate
|
||||
self.update_valuation_rate_for_serial_nos(row, serial_nos)
|
||||
|
||||
def make_adjustment_entry(self, row, sl_entries):
|
||||
from erpnext.stock.stock_ledger import get_stock_value_difference
|
||||
|
||||
difference_amount = get_stock_value_difference(
|
||||
row.item_code, row.warehouse, self.posting_date, self.posting_time
|
||||
)
|
||||
|
||||
if not difference_amount:
|
||||
return
|
||||
|
||||
args = self.get_sle_for_items(row)
|
||||
args.update({"stock_value_difference": -1 * difference_amount, "is_adjustment_entry": 1})
|
||||
|
||||
sl_entries.append(args)
|
||||
|
||||
def update_valuation_rate_for_serial_no(self):
|
||||
for d in self.items:
|
||||
if not d.serial_no:
|
||||
@@ -890,6 +909,7 @@ def get_stock_balance_for(
|
||||
with_valuation_rate=with_valuation_rate,
|
||||
with_serial_no=has_serial_no,
|
||||
inventory_dimensions_dict=inventory_dimensions_dict,
|
||||
batch_no=batch_no,
|
||||
)
|
||||
|
||||
if has_serial_no:
|
||||
|
||||
@@ -1009,6 +1009,52 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
|
||||
self.assertEqual(sr2.docstatus, 1)
|
||||
|
||||
def test_current_qty_and_current_serial_no_count(self):
|
||||
# Step - 1: Create a Serial Batch Item
|
||||
item = self.make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "TEST-SERIAL-.###",
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TEST-BATCH-.###",
|
||||
}
|
||||
).name
|
||||
|
||||
# Step - 2: Inward stock in multiple Batches
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
se1 = make_stock_entry(
|
||||
item_code=item,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=10,
|
||||
)
|
||||
se2 = make_stock_entry(
|
||||
item_code=item,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
)
|
||||
|
||||
# Step - 3: Create Stock Reconciliation
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
qty=0,
|
||||
batch_no=se1.items[0].batch_no,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
# Test - 1: Current Serial No Count should be equal to Current Qty
|
||||
self.assertEqual(sr.items[0].current_qty, se1.items[0].qty)
|
||||
self.assertEqual(len(sr.items[0].current_serial_no.split("\n")), sr.items[0].current_qty)
|
||||
|
||||
sr.items[0].batch_no = se2.items[0].batch_no
|
||||
sr.save()
|
||||
|
||||
self.assertEqual(sr.items[0].current_qty, se2.items[0].qty)
|
||||
self.assertEqual(len(sr.items[0].current_serial_no.split("\n")), sr.items[0].current_qty)
|
||||
|
||||
|
||||
def create_batch_item_with_batch(item_name, batch_id):
|
||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||
|
||||
@@ -635,9 +635,11 @@ class update_entries_after(object):
|
||||
sle.valuation_rate = self.wh_data.valuation_rate
|
||||
sle.stock_value = self.wh_data.stock_value
|
||||
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
|
||||
if not sle.is_adjustment_entry or not self.args.get("sle_id"):
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
|
||||
sle.doctype = "Stock Ledger Entry"
|
||||
frappe.get_doc(sle).db_update()
|
||||
|
||||
if not self.args.get("sle_id"):
|
||||
@@ -1711,3 +1713,27 @@ def is_internal_transfer(sle):
|
||||
|
||||
if data.is_internal_supplier and data.represents_company == data.company:
|
||||
return True
|
||||
|
||||
|
||||
def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None):
|
||||
table = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Sum(table.stock_value_difference).as_("value"))
|
||||
.where(
|
||||
(table.is_cancelled == 0)
|
||||
& (table.item_code == item_code)
|
||||
& (table.warehouse == warehouse)
|
||||
& (
|
||||
(table.posting_date < posting_date)
|
||||
| ((table.posting_date == posting_date) & (table.posting_time <= posting_time))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if voucher_no:
|
||||
query = query.where(table.voucher_no != voucher_no)
|
||||
|
||||
difference_amount = query.run()
|
||||
return flt(difference_amount[0][0]) if difference_amount else 0
|
||||
|
||||
@@ -95,6 +95,7 @@ def get_stock_balance(
|
||||
with_valuation_rate=False,
|
||||
with_serial_no=False,
|
||||
inventory_dimensions_dict=None,
|
||||
batch_no=None,
|
||||
):
|
||||
"""Returns stock balance quantity at given warehouse on given posting date or current date.
|
||||
|
||||
@@ -124,6 +125,9 @@ def get_stock_balance(
|
||||
|
||||
if with_valuation_rate:
|
||||
if with_serial_no:
|
||||
if batch_no:
|
||||
args["batch_no"] = batch_no
|
||||
|
||||
serial_nos = get_serial_nos_data_after_transactions(args)
|
||||
|
||||
return (
|
||||
@@ -140,27 +144,30 @@ def get_stock_balance(
|
||||
|
||||
|
||||
def get_serial_nos_data_after_transactions(args):
|
||||
|
||||
serial_nos = set()
|
||||
args = frappe._dict(args)
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
stock_ledger_entries = (
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
.select("serial_no", "actual_qty")
|
||||
.select(sle.serial_no, sle.actual_qty)
|
||||
.where(
|
||||
(sle.item_code == args.item_code)
|
||||
(sle.is_cancelled == 0)
|
||||
& (sle.item_code == args.item_code)
|
||||
& (sle.warehouse == args.warehouse)
|
||||
& (
|
||||
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||
< CombineDatetime(args.posting_date, args.posting_time)
|
||||
)
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
.orderby(sle.posting_date, sle.posting_time, sle.creation)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
if args.batch_no:
|
||||
query = query.where(sle.batch_no == args.batch_no)
|
||||
|
||||
stock_ledger_entries = query.run(as_dict=True)
|
||||
|
||||
for stock_ledger_entry in stock_ledger_entries:
|
||||
changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no)
|
||||
if stock_ledger_entry.actual_qty > 0:
|
||||
|
||||
Reference in New Issue
Block a user