Merge pull request #33164 from frappe/version-13-hotfix

chore: release v13
This commit is contained in:
Deepesh Garg
2022-11-29 18:47:36 +05:30
committed by GitHub
17 changed files with 127 additions and 38 deletions

View File

@@ -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)

View File

@@ -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 ""

View File

@@ -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()}

View File

@@ -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():

View File

@@ -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

View File

@@ -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);
} }
}, },
{ {

View File

@@ -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:

View File

@@ -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"))

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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),

View File

@@ -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,

View File

@@ -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)