mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 02:01:21 +00:00
Merge pull request #33403 from frappe/version-13-hotfix
chore: release v13
This commit is contained in:
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint, qb
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import flt, getdate, nowdate, today
|
from frappe.utils import flt, getdate, nowdate, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -120,58 +122,77 @@ class PaymentReconciliation(Document):
|
|||||||
return list(journal_entries)
|
return list(journal_entries)
|
||||||
|
|
||||||
def get_dr_or_cr_notes(self):
|
def get_dr_or_cr_notes(self):
|
||||||
condition = self.get_conditions(get_return_invoices=True)
|
gl = qb.DocType("GL Entry")
|
||||||
|
|
||||||
|
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
||||||
|
doc = qb.DocType(voucher_type)
|
||||||
|
|
||||||
|
# build conditions
|
||||||
|
sub_query_conditions = []
|
||||||
|
conditions = []
|
||||||
|
sub_query_conditions.append(doc.company == self.company)
|
||||||
|
|
||||||
|
if self.get("from_payment_date"):
|
||||||
|
sub_query_conditions.append(doc.posting_date.gte(self.from_payment_date))
|
||||||
|
|
||||||
|
if self.get("to_payment_date"):
|
||||||
|
sub_query_conditions.append(doc.posting_date.lte(self.to_payment_date))
|
||||||
|
|
||||||
if self.get("cost_center"):
|
if self.get("cost_center"):
|
||||||
condition += " and doc.cost_center = '{0}' ".format(self.cost_center)
|
sub_query_conditions.append(doc.cost_center == self.cost_center)
|
||||||
|
|
||||||
dr_or_cr = (
|
dr_or_cr = (
|
||||||
"credit_in_account_currency"
|
gl["credit_in_account_currency"]
|
||||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||||
else "debit_in_account_currency"
|
else gl["debit_in_account_currency"]
|
||||||
)
|
)
|
||||||
|
|
||||||
reconciled_dr_or_cr = (
|
reconciled_dr_or_cr = (
|
||||||
"debit_in_account_currency"
|
gl["debit_in_account_currency"]
|
||||||
if dr_or_cr == "credit_in_account_currency"
|
if dr_or_cr == gl["credit_in_account_currency"]
|
||||||
else "credit_in_account_currency"
|
else gl["credit_in_account_currency"]
|
||||||
)
|
)
|
||||||
|
|
||||||
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
|
if self.minimum_payment_amount:
|
||||||
|
conditions.append(dr_or_cr.gte(self.minimum_payment_amount))
|
||||||
|
if self.maximum_payment_amount:
|
||||||
|
conditions.append(dr_or_cr.lte(self.maximum_payment_amount))
|
||||||
|
|
||||||
return frappe.db.sql(
|
sub_query = (
|
||||||
""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
|
qb.from_(doc)
|
||||||
(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
|
.select(doc.name)
|
||||||
account_currency as currency
|
.where(Criterion.all(sub_query_conditions))
|
||||||
FROM `tab{doc}` doc, `tabGL Entry` gl
|
.where(
|
||||||
WHERE
|
(doc.docstatus == 1)
|
||||||
(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
|
& (doc.is_return == 1)
|
||||||
and doc.{party_type_field} = %(party)s
|
& ((doc.return_against == "") | (doc.return_against.isnull()))
|
||||||
and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
|
)
|
||||||
and gl.against_voucher_type = %(voucher_type)s
|
|
||||||
and doc.docstatus = 1 and gl.party = %(party)s
|
|
||||||
and gl.party_type = %(party_type)s and gl.account = %(account)s
|
|
||||||
and gl.is_cancelled = 0 {condition}
|
|
||||||
GROUP BY doc.name
|
|
||||||
Having
|
|
||||||
amount > 0
|
|
||||||
ORDER BY doc.posting_date
|
|
||||||
""".format(
|
|
||||||
doc=voucher_type,
|
|
||||||
dr_or_cr=dr_or_cr,
|
|
||||||
reconciled_dr_or_cr=reconciled_dr_or_cr,
|
|
||||||
party_type_field=frappe.scrub(self.party_type),
|
|
||||||
condition=condition or "",
|
|
||||||
),
|
|
||||||
{
|
|
||||||
"party": self.party,
|
|
||||||
"party_type": self.party_type,
|
|
||||||
"voucher_type": voucher_type,
|
|
||||||
"account": self.receivable_payable_account,
|
|
||||||
},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
query = (
|
||||||
|
qb.from_(gl)
|
||||||
|
.select(
|
||||||
|
gl.voucher_type.as_("reference_type"),
|
||||||
|
gl.voucher_no.as_("reference_name"),
|
||||||
|
(Sum(dr_or_cr) - Sum(reconciled_dr_or_cr)).as_("amount"),
|
||||||
|
gl.posting_date,
|
||||||
|
gl.account_currency.as_("currency"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(gl.voucher_type == voucher_type)
|
||||||
|
& (gl.voucher_no.isin(sub_query))
|
||||||
|
& (gl.is_cancelled == 0)
|
||||||
|
& (gl.account == self.receivable_payable_account)
|
||||||
|
& (gl.party_type == self.party_type)
|
||||||
|
& (gl.party == self.party)
|
||||||
|
)
|
||||||
|
.where(Criterion.all(conditions))
|
||||||
|
.groupby(gl.voucher_no)
|
||||||
|
.having(qb.Field("amount") > 0)
|
||||||
|
)
|
||||||
|
dr_cr_notes = query.run(as_dict=True)
|
||||||
|
return dr_cr_notes
|
||||||
|
|
||||||
def add_payment_entries(self, non_reconciled_payments):
|
def add_payment_entries(self, non_reconciled_payments):
|
||||||
self.set("payments", [])
|
self.set("payments", [])
|
||||||
|
|
||||||
@@ -369,7 +390,7 @@ class PaymentReconciliation(Document):
|
|||||||
if not invoices_to_reconcile:
|
if not invoices_to_reconcile:
|
||||||
frappe.throw(_("No records found in Allocation table"))
|
frappe.throw(_("No records found in Allocation table"))
|
||||||
|
|
||||||
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
|
def get_conditions(self, get_invoices=False, get_payments=False):
|
||||||
condition = " and company = '{0}' ".format(self.company)
|
condition = " and company = '{0}' ".format(self.company)
|
||||||
|
|
||||||
if get_invoices:
|
if get_invoices:
|
||||||
@@ -397,35 +418,7 @@ class PaymentReconciliation(Document):
|
|||||||
condition += " and {dr_or_cr} <= {amount}".format(
|
condition += " and {dr_or_cr} <= {amount}".format(
|
||||||
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
|
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
|
||||||
)
|
)
|
||||||
|
elif get_payments:
|
||||||
elif get_return_invoices:
|
|
||||||
condition = " and doc.company = '{0}' ".format(self.company)
|
|
||||||
condition += (
|
|
||||||
" and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
|
|
||||||
if self.from_payment_date
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
condition += (
|
|
||||||
" and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
|
|
||||||
if self.to_payment_date
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
dr_or_cr = (
|
|
||||||
"debit_in_account_currency"
|
|
||||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
|
||||||
else "credit_in_account_currency"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.minimum_invoice_amount:
|
|
||||||
condition += " and gl.{dr_or_cr} >= {amount}".format(
|
|
||||||
dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
|
|
||||||
)
|
|
||||||
if self.maximum_invoice_amount:
|
|
||||||
condition += " and gl.{dr_or_cr} <= {amount}".format(
|
|
||||||
dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
condition += (
|
condition += (
|
||||||
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
|
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
|
||||||
if self.from_payment_date
|
if self.from_payment_date
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from six import iteritems
|
|||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
get_columns,
|
get_columns,
|
||||||
|
get_cost_centers_with_children,
|
||||||
get_data,
|
get_data,
|
||||||
get_filtered_list_for_consolidated_report,
|
get_filtered_list_for_consolidated_report,
|
||||||
get_period_list,
|
get_period_list,
|
||||||
@@ -161,10 +162,11 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
|
|||||||
total = 0
|
total = 0
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
start_date = get_start_date(period, accumulated_values, company)
|
start_date = get_start_date(period, accumulated_values, company)
|
||||||
|
filters.start_date = start_date
|
||||||
|
filters.end_date = period["to_date"]
|
||||||
|
filters.account_type = account_type
|
||||||
|
|
||||||
amount = get_account_type_based_gl_data(
|
amount = get_account_type_based_gl_data(company, filters)
|
||||||
company, start_date, period["to_date"], account_type, filters
|
|
||||||
)
|
|
||||||
|
|
||||||
if amount and account_type == "Depreciation":
|
if amount and account_type == "Depreciation":
|
||||||
amount *= -1
|
amount *= -1
|
||||||
@@ -176,7 +178,7 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
|
def get_account_type_based_gl_data(company, filters=None):
|
||||||
cond = ""
|
cond = ""
|
||||||
filters = frappe._dict(filters or {})
|
filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
@@ -192,17 +194,21 @@ def get_account_type_based_gl_data(company, start_date, end_date, account_type,
|
|||||||
frappe.db.escape(cstr(filters.finance_book))
|
frappe.db.escape(cstr(filters.finance_book))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filters.get("cost_center"):
|
||||||
|
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||||
|
cond += " and cost_center in %(cost_center)s"
|
||||||
|
|
||||||
gl_sum = frappe.db.sql_list(
|
gl_sum = frappe.db.sql_list(
|
||||||
"""
|
"""
|
||||||
select sum(credit) - sum(debit)
|
select sum(credit) - sum(debit)
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where company=%s and posting_date >= %s and posting_date <= %s
|
where company=%(company)s and posting_date >= %(start_date)s and posting_date <= %(end_date)s
|
||||||
and voucher_type != 'Period Closing Voucher'
|
and voucher_type != 'Period Closing Voucher'
|
||||||
and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond}
|
and account in ( SELECT name FROM tabAccount WHERE account_type = %(account_type)s) {cond}
|
||||||
""".format(
|
""".format(
|
||||||
cond=cond
|
cond=cond
|
||||||
),
|
),
|
||||||
(company, start_date, end_date, account_type),
|
filters,
|
||||||
)
|
)
|
||||||
|
|
||||||
return gl_sum[0] if gl_sum and gl_sum[0] else 0
|
return gl_sum[0] if gl_sum and gl_sum[0] else 0
|
||||||
|
|||||||
@@ -268,10 +268,12 @@ def get_cash_flow_data(fiscal_year, companies, filters):
|
|||||||
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
|
def get_account_type_based_data(account_type, companies, fiscal_year, filters):
|
||||||
data = {}
|
data = {}
|
||||||
total = 0
|
total = 0
|
||||||
|
filters.account_type = account_type
|
||||||
|
filters.start_date = fiscal_year.year_start_date
|
||||||
|
filters.end_date = fiscal_year.year_end_date
|
||||||
|
|
||||||
for company in companies:
|
for company in companies:
|
||||||
amount = get_account_type_based_gl_data(
|
amount = get_account_type_based_gl_data(company, filters)
|
||||||
company, fiscal_year.year_start_date, fiscal_year.year_end_date, account_type, filters
|
|
||||||
)
|
|
||||||
|
|
||||||
if amount and account_type == "Depreciation":
|
if amount and account_type == "Depreciation":
|
||||||
amount *= -1
|
amount *= -1
|
||||||
|
|||||||
@@ -333,16 +333,21 @@ class StatusUpdater(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
||||||
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
|
if qty_or_amount == "qty":
|
||||||
|
msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.")
|
||||||
|
else:
|
||||||
|
msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.")
|
||||||
|
|
||||||
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
|
frappe.msgprint(
|
||||||
action,
|
msg.format(
|
||||||
_(item["target_ref_field"].title()),
|
_(item["target_ref_field"].title()),
|
||||||
frappe.bold(item["reduce_by"]),
|
frappe.bold(item["reduce_by"]),
|
||||||
frappe.bold(item.get("item_code")),
|
frappe.bold(item.get("item_code")),
|
||||||
role,
|
role,
|
||||||
|
),
|
||||||
|
indicator="orange",
|
||||||
|
alert=True,
|
||||||
)
|
)
|
||||||
frappe.msgprint(msg, indicator="orange", alert=True)
|
|
||||||
|
|
||||||
def update_qty(self, update_modified=True):
|
def update_qty(self, update_modified=True):
|
||||||
"""Updates qty or amount at row level
|
"""Updates qty or amount at row level
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ scheduler_events = {
|
|||||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||||
"erpnext.crm.doctype.lead.lead.daily_open_lead",
|
"erpnext.crm.doctype.lead.lead.daily_open_lead",
|
||||||
|
"erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries",
|
||||||
],
|
],
|
||||||
"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
|
"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
|
||||||
"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
|
"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
|
||||||
|
|||||||
@@ -1145,6 +1145,37 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
except frappe.MandatoryError:
|
except frappe.MandatoryError:
|
||||||
self.fail("Batch generation causing failing in Work Order")
|
self.fail("Batch generation causing failing in Work Order")
|
||||||
|
|
||||||
|
@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
|
||||||
|
def test_auto_serial_no_creation(self):
|
||||||
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
fg_item = frappe.generate_hash(length=20)
|
||||||
|
child_item = frappe.generate_hash(length=20)
|
||||||
|
|
||||||
|
bom_tree = {fg_item: {child_item: {}}}
|
||||||
|
|
||||||
|
create_nested_bom(bom_tree, prefix="")
|
||||||
|
|
||||||
|
item = frappe.get_doc("Item", fg_item)
|
||||||
|
item.has_serial_no = 1
|
||||||
|
item.serial_no_series = f"{item.name}.#####"
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True)
|
||||||
|
serial_nos = wo_order.serial_no
|
||||||
|
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
|
stock_entry.set_work_order_details()
|
||||||
|
stock_entry.set_serial_no_batch_for_finished_good()
|
||||||
|
for row in stock_entry.items:
|
||||||
|
if row.item_code == fg_item:
|
||||||
|
self.assertTrue(row.serial_no)
|
||||||
|
self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
|
||||||
|
|
||||||
|
except frappe.MandatoryError:
|
||||||
|
self.fail("Batch generation causing failing in Work Order")
|
||||||
|
|
||||||
@change_settings(
|
@change_settings(
|
||||||
"Manufacturing Settings",
|
"Manufacturing Settings",
|
||||||
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
|
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
|
||||||
|
|||||||
@@ -181,13 +181,13 @@ class PickList(Document):
|
|||||||
|
|
||||||
if item_map.get(key):
|
if item_map.get(key):
|
||||||
item_map[key].qty += item.qty
|
item_map[key].qty += item.qty
|
||||||
item_map[key].stock_qty += item.stock_qty
|
item_map[key].stock_qty += flt(item.stock_qty, item.precision("stock_qty"))
|
||||||
else:
|
else:
|
||||||
item_map[key] = item
|
item_map[key] = item
|
||||||
|
|
||||||
# maintain count of each item (useful to limit get query)
|
# maintain count of each item (useful to limit get query)
|
||||||
self.item_count_map.setdefault(item_code, 0)
|
self.item_count_map.setdefault(item_code, 0)
|
||||||
self.item_count_map[item_code] += item.stock_qty
|
self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty"))
|
||||||
|
|
||||||
return item_map.values()
|
return item_map.values()
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,25 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
|
||||||
from six import iteritems, itervalues, string_types
|
from six import iteritems, itervalues, string_types
|
||||||
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
cint,
|
||||||
|
comma_or,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
format_time,
|
||||||
|
formatdate,
|
||||||
|
getdate,
|
||||||
|
nowdate,
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
@@ -2192,16 +2204,16 @@ class StockEntry(StockController):
|
|||||||
d.qty -= process_loss_dict[d.item_code][1]
|
d.qty -= process_loss_dict[d.item_code][1]
|
||||||
|
|
||||||
def set_serial_no_batch_for_finished_good(self):
|
def set_serial_no_batch_for_finished_good(self):
|
||||||
serial_nos = ""
|
serial_nos = []
|
||||||
if self.pro_doc.serial_no:
|
if self.pro_doc.serial_no:
|
||||||
serial_nos = self.get_serial_nos_for_fg()
|
serial_nos = self.get_serial_nos_for_fg() or []
|
||||||
|
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
|
||||||
if serial_nos:
|
if serial_nos:
|
||||||
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
|
row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
|
||||||
|
|
||||||
def get_serial_nos_for_fg(self, args):
|
def get_serial_nos_for_fg(self):
|
||||||
fields = [
|
fields = [
|
||||||
"`tabStock Entry`.`name`",
|
"`tabStock Entry`.`name`",
|
||||||
"`tabStock Entry Detail`.`qty`",
|
"`tabStock Entry Detail`.`qty`",
|
||||||
@@ -2217,9 +2229,7 @@ class StockEntry(StockController):
|
|||||||
]
|
]
|
||||||
|
|
||||||
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||||
|
return self.get_available_serial_nos(stock_entries)
|
||||||
if self.pro_doc.serial_no:
|
|
||||||
return self.get_available_serial_nos(stock_entries)
|
|
||||||
|
|
||||||
def get_available_serial_nos(self, stock_entries):
|
def get_available_serial_nos(self, stock_entries):
|
||||||
used_serial_nos = []
|
used_serial_nos = []
|
||||||
@@ -2556,3 +2566,63 @@ def get_supplied_items(purchase_order):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return supplied_item_details
|
return supplied_item_details
|
||||||
|
|
||||||
|
|
||||||
|
def audit_incorrect_valuation_entries():
|
||||||
|
# Audit of stock transfer entries having incorrect valuation
|
||||||
|
from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
|
||||||
|
for stock_entry, values in stock_entries.items():
|
||||||
|
reposting_data = frappe._dict(
|
||||||
|
{
|
||||||
|
"posting_date": values.posting_date,
|
||||||
|
"posting_time": values.posting_time,
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"voucher_no": stock_entry,
|
||||||
|
"company": values.company,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
create_repost_item_valuation_entry(reposting_data)
|
||||||
|
|
||||||
|
|
||||||
|
def get_incorrect_stock_entries() -> Dict:
|
||||||
|
stock_entry = frappe.qb.DocType("Stock Entry")
|
||||||
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
transfer_purposes = [
|
||||||
|
"Material Transfer",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
]
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(stock_entry)
|
||||||
|
.inner_join(stock_ledger_entry)
|
||||||
|
.on(stock_entry.name == stock_ledger_entry.voucher_no)
|
||||||
|
.select(
|
||||||
|
stock_entry.name,
|
||||||
|
stock_entry.company,
|
||||||
|
stock_entry.posting_date,
|
||||||
|
stock_entry.posting_time,
|
||||||
|
Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(stock_entry.docstatus == 1)
|
||||||
|
& (stock_entry.purpose.isin(transfer_purposes))
|
||||||
|
& (stock_ledger_entry.modified > add_days(today(), -2))
|
||||||
|
)
|
||||||
|
.groupby(stock_ledger_entry.voucher_detail_no)
|
||||||
|
.having(Sum(stock_ledger_entry.stock_value_difference) != 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = query.run(as_dict=True)
|
||||||
|
stock_entries = {}
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
if abs(row.stock_value) > 0.1 and row.name not in stock_entries:
|
||||||
|
stock_entries.setdefault(row.name, row)
|
||||||
|
|
||||||
|
return stock_entries
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission
|
from frappe.permissions import add_user_permission, remove_user_permission
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, flt, nowdate, nowtime, today
|
from frappe.utils import add_days, flt, now, nowdate, nowtime, today
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
@@ -18,6 +18,8 @@ from erpnext.stock.doctype.item.test_item import (
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
|
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
from erpnext.stock.doctype.stock_entry.stock_entry import (
|
||||||
FinishedGoodError,
|
FinishedGoodError,
|
||||||
|
audit_incorrect_valuation_entries,
|
||||||
|
get_incorrect_stock_entries,
|
||||||
move_sample_to_retention_warehouse,
|
move_sample_to_retention_warehouse,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
@@ -1571,6 +1573,44 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertRaises(BatchExpiredError, se.save)
|
self.assertRaises(BatchExpiredError, se.save)
|
||||||
|
|
||||||
|
def test_audit_incorrect_stock_entries(self):
|
||||||
|
item_code = "Test Incorrect Valuation Rate Item - 001"
|
||||||
|
create_item(item_code=item_code, is_stock_item=1)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
posting_date=add_days(nowdate(), -10),
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
to_warehouse="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
transfer_entry = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
from_warehouse="_Test Warehouse - _TC",
|
||||||
|
to_warehouse="_Test Warehouse 1 - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle_name = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10}
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
self.assertTrue(transfer_entry.name in stock_entries)
|
||||||
|
|
||||||
|
audit_incorrect_valuation_entries()
|
||||||
|
|
||||||
|
stock_entries = get_incorrect_stock_entries()
|
||||||
|
self.assertFalse(transfer_entry.name in stock_entries)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -813,9 +813,9 @@ def insert_item_price(args):
|
|||||||
):
|
):
|
||||||
if frappe.has_permission("Item Price", "write"):
|
if frappe.has_permission("Item Price", "write"):
|
||||||
price_list_rate = (
|
price_list_rate = (
|
||||||
(args.rate + args.discount_amount) / args.get("conversion_factor")
|
(flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor")
|
||||||
if args.get("conversion_factor")
|
if args.get("conversion_factor")
|
||||||
else (args.rate + args.discount_amount)
|
else (flt(args.rate) + flt(args.discount_amount))
|
||||||
)
|
)
|
||||||
|
|
||||||
item_price = frappe.db.get_value(
|
item_price = frappe.db.get_value(
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ def get_item_info(filters):
|
|||||||
if filters.get("brand"):
|
if filters.get("brand"):
|
||||||
conditions.append("item.brand=%(brand)s")
|
conditions.append("item.brand=%(brand)s")
|
||||||
conditions.append("is_stock_item = 1")
|
conditions.append("is_stock_item = 1")
|
||||||
|
conditions.append("disabled = 0")
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""select name, item_name, description, brand, item_group,
|
"""select name, item_name, description, brand, item_group,
|
||||||
|
|||||||
@@ -1844,6 +1844,8 @@ Outstanding Amt,Offener Betrag,
|
|||||||
Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
|
Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
|
||||||
Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
|
Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
|
||||||
Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
|
Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
|
||||||
|
Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
|
||||||
|
Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
|
||||||
Overdue,Überfällig,
|
Overdue,Überfällig,
|
||||||
Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
|
Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
|
||||||
Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
|
Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user