mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-03 12:19:12 +00:00
Merge pull request #33164 from frappe/version-13-hotfix
chore: release v13
This commit is contained in:
@@ -298,20 +298,22 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
||||||
)
|
)
|
||||||
round_off_account_exists = False
|
|
||||||
round_off_gle = frappe._dict()
|
round_off_gle = frappe._dict()
|
||||||
for d in gl_map:
|
round_off_account_exists = False
|
||||||
if d.account == round_off_account:
|
|
||||||
round_off_gle = d
|
|
||||||
if d.debit:
|
|
||||||
debit_credit_diff -= flt(d.debit)
|
|
||||||
else:
|
|
||||||
debit_credit_diff += flt(d.credit)
|
|
||||||
round_off_account_exists = True
|
|
||||||
|
|
||||||
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
|
if gl_map[0].voucher_type != "Period Closing Voucher":
|
||||||
gl_map.remove(round_off_gle)
|
for d in gl_map:
|
||||||
return
|
if d.account == round_off_account:
|
||||||
|
round_off_gle = d
|
||||||
|
if d.debit:
|
||||||
|
debit_credit_diff -= flt(d.debit) - flt(d.credit)
|
||||||
|
else:
|
||||||
|
debit_credit_diff += flt(d.credit)
|
||||||
|
round_off_account_exists = True
|
||||||
|
|
||||||
|
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
|
||||||
|
gl_map.remove(round_off_gle)
|
||||||
|
return
|
||||||
|
|
||||||
if not round_off_gle:
|
if not round_off_gle:
|
||||||
for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
|
for k in ["voucher_type", "voucher_no", "company", "posting_date", "remarks"]:
|
||||||
@@ -334,7 +336,6 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
)
|
)
|
||||||
|
|
||||||
update_accounting_dimensions(round_off_gle)
|
update_accounting_dimensions(round_off_gle)
|
||||||
|
|
||||||
if not round_off_account_exists:
|
if not round_off_account_exists:
|
||||||
gl_map.append(round_off_gle)
|
gl_map.append(round_off_gle)
|
||||||
|
|
||||||
|
|||||||
@@ -685,10 +685,10 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
if self.filters.get(scrub(self.party_type)):
|
if self.filters.get(scrub(self.party_type)):
|
||||||
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
|
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
|
||||||
|
doc_currency_fields = "debit as debit_in_account_currency, credit as credit_in_account_currency"
|
||||||
else:
|
else:
|
||||||
select_fields = "debit, credit"
|
select_fields = "debit, credit"
|
||||||
|
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
|
||||||
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
|
|
||||||
|
|
||||||
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
||||||
|
|
||||||
|
|||||||
@@ -825,7 +825,9 @@ def update_maintenance_status():
|
|||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
asset = frappe.get_doc("Asset", asset.name)
|
asset = frappe.get_doc("Asset", asset.name)
|
||||||
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
|
if frappe.db.exists(
|
||||||
|
"Asset Repair", {"asset_name": asset.name, "repair_status": "Pending", "docstatus": 0}
|
||||||
|
):
|
||||||
asset.set_status("Out of Order")
|
asset.set_status("Out of Order")
|
||||||
elif frappe.db.exists(
|
elif frappe.db.exists(
|
||||||
"Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}
|
"Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}
|
||||||
|
|||||||
@@ -578,6 +578,7 @@ class SellingController(StockController):
|
|||||||
"customer_address": "address_display",
|
"customer_address": "address_display",
|
||||||
"shipping_address_name": "shipping_address",
|
"shipping_address_name": "shipping_address",
|
||||||
"company_address": "company_address_display",
|
"company_address": "company_address_display",
|
||||||
|
"dispatch_address_name": "dispatch_address",
|
||||||
}
|
}
|
||||||
|
|
||||||
for address_field, address_display_field in address_dict.items():
|
for address_field, address_display_field in address_dict.items():
|
||||||
|
|||||||
@@ -191,7 +191,9 @@ def get_total_pledged_security_value(loan):
|
|||||||
|
|
||||||
for security, qty in pledged_securities.items():
|
for security, qty in pledged_securities.items():
|
||||||
after_haircut_percentage = 100 - hair_cut_map.get(security)
|
after_haircut_percentage = 100 - hair_cut_map.get(security)
|
||||||
security_value += (loan_security_price_map.get(security) * qty * after_haircut_percentage) / 100
|
security_value += (
|
||||||
|
loan_security_price_map.get(security, 0) * qty * after_haircut_percentage
|
||||||
|
) / 100
|
||||||
|
|
||||||
return security_value
|
return security_value
|
||||||
|
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ frappe.query_reports["Job Card Summary"] = {
|
|||||||
options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
|
options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __("Sales Orders"),
|
label: __("Work Orders"),
|
||||||
fieldname: "sales_order",
|
fieldname: "work_order",
|
||||||
fieldtype: "MultiSelectList",
|
fieldtype: "MultiSelectList",
|
||||||
get_data: function(txt) {
|
get_data: function(txt) {
|
||||||
return frappe.db.get_link_options('Sales Order', txt);
|
return frappe.db.get_link_options('Work Order', txt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,10 +36,14 @@ def get_data(filters):
|
|||||||
"total_time_in_mins",
|
"total_time_in_mins",
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in ["work_order", "workstation", "operation", "company"]:
|
for field in ["work_order", "production_item"]:
|
||||||
if filters.get(field):
|
if filters.get(field):
|
||||||
query_filters[field] = ("in", filters.get(field))
|
query_filters[field] = ("in", filters.get(field))
|
||||||
|
|
||||||
|
for field in ["workstation", "operation", "status", "company"]:
|
||||||
|
if filters.get(field):
|
||||||
|
query_filters[field] = filters.get(field)
|
||||||
|
|
||||||
data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
|
data = frappe.get_all("Job Card", fields=fields, filters=query_filters)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
|
|||||||
@@ -39,10 +39,14 @@ def get_data(filters):
|
|||||||
"lead_time",
|
"lead_time",
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in ["sales_order", "production_item", "status", "company"]:
|
for field in ["sales_order", "production_item"]:
|
||||||
if filters.get(field):
|
if filters.get(field):
|
||||||
query_filters[field] = ("in", filters.get(field))
|
query_filters[field] = ("in", filters.get(field))
|
||||||
|
|
||||||
|
for field in ["status", "company"]:
|
||||||
|
if filters.get(field):
|
||||||
|
query_filters[field] = filters.get(field)
|
||||||
|
|
||||||
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
|
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
|
||||||
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
|
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
|
||||||
|
|
||||||
|
|||||||
@@ -346,6 +346,8 @@ class PayrollEntry(Document):
|
|||||||
"credit_in_account_currency": flt(payable_amt, precision),
|
"credit_in_account_currency": flt(payable_amt, precision),
|
||||||
"exchange_rate": flt(exchange_rate),
|
"exchange_rate": flt(exchange_rate),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
|
"reference_type": self.doctype,
|
||||||
|
"reference_name": self.name,
|
||||||
},
|
},
|
||||||
accounting_dimensions,
|
accounting_dimensions,
|
||||||
)
|
)
|
||||||
@@ -720,12 +722,21 @@ def get_month_details(year, month):
|
|||||||
|
|
||||||
def get_payroll_entry_bank_entries(payroll_entry_name):
|
def get_payroll_entry_bank_entries(payroll_entry_name):
|
||||||
journal_entries = frappe.db.sql(
|
journal_entries = frappe.db.sql(
|
||||||
"select name from `tabJournal Entry Account` "
|
"""
|
||||||
'where reference_type="Payroll Entry" '
|
select
|
||||||
"and reference_name=%s and docstatus=1",
|
je.name
|
||||||
|
from
|
||||||
|
`tabJournal Entry` je,
|
||||||
|
`tabJournal Entry Account` jea
|
||||||
|
where
|
||||||
|
je.name = jea.parent
|
||||||
|
and je.voucher_type = 'Bank Entry'
|
||||||
|
and jea.reference_type = 'Payroll Entry'
|
||||||
|
and jea.reference_name = %s
|
||||||
|
""",
|
||||||
payroll_entry_name,
|
payroll_entry_name,
|
||||||
as_dict=1,
|
as_dict=True,
|
||||||
)
|
) # nosemgrep
|
||||||
|
|
||||||
return journal_entries
|
return journal_entries
|
||||||
|
|
||||||
|
|||||||
@@ -133,9 +133,17 @@ class TestPayrollEntry(FrappeTestCase):
|
|||||||
|
|
||||||
payment_entry = frappe.db.sql(
|
payment_entry = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
|
select
|
||||||
Where je.name = jea.parent
|
ifnull(sum(je.total_debit),0) as total_debit,
|
||||||
And jea.reference_name = %s
|
ifnull(sum(je.total_credit),0) as total_credit
|
||||||
|
from
|
||||||
|
`tabJournal Entry` je,
|
||||||
|
`tabJournal Entry Account` jea
|
||||||
|
Where
|
||||||
|
je.name = jea.parent
|
||||||
|
and je.voucher_type = 'Bank Entry'
|
||||||
|
and jea.reference_type = 'Payroll Entry'
|
||||||
|
and jea.reference_name = %s
|
||||||
""",
|
""",
|
||||||
(payroll_entry.name),
|
(payroll_entry.name),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
|
|||||||
@@ -10,3 +10,8 @@ class EInvoiceSettings(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
if self.enable and not self.credentials:
|
if self.enable and not self.credentials:
|
||||||
frappe.throw(_("You must add atleast one credentials to be able to use E Invoicing."))
|
frappe.throw(_("You must add atleast one credentials to be able to use E Invoicing."))
|
||||||
|
|
||||||
|
prev_doc = self.get_doc_before_save()
|
||||||
|
if prev_doc.client_secret != self.client_secret or prev_doc.client_id != self.client_id:
|
||||||
|
self.auth_token = None
|
||||||
|
self.token_expiry = None
|
||||||
|
|||||||
@@ -370,9 +370,6 @@ frappe.ui.form.on("Material Request Item", {
|
|||||||
if (flt(d.qty) < flt(d.min_order_qty)) {
|
if (flt(d.qty) < flt(d.min_order_qty)) {
|
||||||
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
|
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = locals[doctype][name];
|
|
||||||
frm.events.get_item_data(frm, item, false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
from_warehouse: function(frm, doctype, name) {
|
from_warehouse: function(frm, doctype, name) {
|
||||||
|
|||||||
@@ -34,6 +34,22 @@ frappe.ui.form.on('Repost Item Valuation', {
|
|||||||
frm.trigger('setup_realtime_progress');
|
frm.trigger('setup_realtime_progress');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
based_on: function(frm) {
|
||||||
|
var fields_to_reset = [];
|
||||||
|
|
||||||
|
if (frm.doc.based_on == 'Transaction') {
|
||||||
|
fields_to_reset = ['item_code', 'warehouse'];
|
||||||
|
} else if (frm.doc.based_on == 'Item and Warehouse') {
|
||||||
|
fields_to_reset = ['voucher_type', 'voucher_no'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields_to_reset) {
|
||||||
|
fields_to_reset.forEach(field => {
|
||||||
|
frm.set_value(field, undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setup_realtime_progress: function(frm) {
|
setup_realtime_progress: function(frm) {
|
||||||
frappe.realtime.on('item_reposting_progress', data => {
|
frappe.realtime.on('item_reposting_progress', data => {
|
||||||
if (frm.doc.name !== data.name) {
|
if (frm.doc.name !== data.name) {
|
||||||
|
|||||||
@@ -50,13 +50,15 @@
|
|||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Posting Date",
|
"label": "Posting Date",
|
||||||
|
"read_only_depends_on": "eval: doc.based_on == \"Transaction\"",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "voucher_no.posting_time",
|
"fetch_from": "voucher_no.posting_time",
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Posting Time"
|
"label": "Posting Time",
|
||||||
|
"read_only_depends_on": "eval: doc.based_on == \"Transaction\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Queued",
|
"default": "Queued",
|
||||||
@@ -195,7 +197,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-13 12:20:22.182322",
|
"modified": "2022-11-28 16:00:05.637440",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Repost Item Valuation",
|
"name": "Repost Item Valuation",
|
||||||
|
|||||||
@@ -1050,7 +1050,8 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
|
|||||||
if (frm.doc.purpose === 'Material Receipt') return;
|
if (frm.doc.purpose === 'Material Receipt') return;
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||||
new erpnext.SerialNoBatchSelector({
|
if (frm.batch_selector?.dialog?.display) return;
|
||||||
|
frm.batch_selector = new erpnext.SerialNoBatchSelector({
|
||||||
frm: frm,
|
frm: frm,
|
||||||
item: item,
|
item: item,
|
||||||
warehouse_details: get_warehouse_type_and_name(item),
|
warehouse_details: get_warehouse_type_and_name(item),
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
if item.has_serial_no or item.has_batch_no:
|
if item.has_serial_no or item.has_batch_no:
|
||||||
has_serial_no = True
|
has_serial_no = True
|
||||||
self.get_sle_for_serialized_items(row, sl_entries)
|
self.get_sle_for_serialized_items(row, sl_entries, item)
|
||||||
else:
|
else:
|
||||||
if row.serial_no or row.batch_no:
|
if row.serial_no or row.batch_no:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@@ -281,7 +281,7 @@ class StockReconciliation(StockController):
|
|||||||
if has_serial_no and sl_entries:
|
if has_serial_no and sl_entries:
|
||||||
self.update_valuation_rate_for_serial_no()
|
self.update_valuation_rate_for_serial_no()
|
||||||
|
|
||||||
def get_sle_for_serialized_items(self, row, sl_entries):
|
def get_sle_for_serialized_items(self, row, sl_entries, item):
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle
|
from erpnext.stock.stock_ledger import get_previous_sle
|
||||||
|
|
||||||
serial_nos = get_serial_nos(row.serial_no)
|
serial_nos = get_serial_nos(row.serial_no)
|
||||||
@@ -347,6 +347,9 @@ class StockReconciliation(StockController):
|
|||||||
if row.qty:
|
if row.qty:
|
||||||
args = self.get_sle_for_items(row)
|
args = self.get_sle_for_items(row)
|
||||||
|
|
||||||
|
if item.has_serial_no and item.has_batch_no:
|
||||||
|
args["qty_after_transaction"] = row.qty
|
||||||
|
|
||||||
args.update(
|
args.update(
|
||||||
{
|
{
|
||||||
"actual_qty": row.qty,
|
"actual_qty": row.qty,
|
||||||
|
|||||||
@@ -643,6 +643,38 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
)
|
)
|
||||||
self.assertEqual(len(active_sr_no), 0)
|
self.assertEqual(len(active_sr_no), 0)
|
||||||
|
|
||||||
|
def test_serial_no_batch_no_item(self):
|
||||||
|
item = self.make_item(
|
||||||
|
"Test Serial No Batch No Item",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"serial_no_series": "SRS9.####",
|
||||||
|
"batch_number_series": "BNS9.####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(
|
||||||
|
item_code=item.name,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
sl_entry = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Stock Reconciliation", "voucher_no": sr.name},
|
||||||
|
["actual_qty", "qty_after_transaction"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(flt(sl_entry.actual_qty), 1.0)
|
||||||
|
self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0)
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
|
|||||||
Reference in New Issue
Block a user