mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
Merge pull request #42937 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -6,7 +6,7 @@ import frappe
|
|||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
from frappe.utils import flt, fmt_money, getdate
|
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
|
||||||
from pypika import Order
|
from pypika import Order
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -96,8 +96,11 @@ class BankClearance(Document):
|
|||||||
|
|
||||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
|
||||||
d.idx, d.clearance_date, d.cheque_date
|
d.idx,
|
||||||
|
get_link_to_form(d.payment_document, d.payment_entry),
|
||||||
|
d.clearance_date,
|
||||||
|
d.cheque_date,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
|
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||||
? ["Bank", "Cash"]
|
? ["Bank", "Cash"]
|
||||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
|
||||||
|
if (frm.doc.party_type == "Shareholder") {
|
||||||
|
account_types.push("Equity");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
account_type: ["in", account_types],
|
account_type: ["in", account_types],
|
||||||
@@ -90,6 +95,9 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
|
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||||
? ["Bank", "Cash"]
|
? ["Bank", "Cash"]
|
||||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
if (frm.doc.party_type == "Shareholder") {
|
||||||
|
account_types.push("Equity");
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
account_type: ["in", account_types],
|
account_type: ["in", account_types],
|
||||||
@@ -412,6 +420,12 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.employee_query",
|
query: "erpnext.controllers.queries.employee_query",
|
||||||
};
|
};
|
||||||
|
} else if (frm.doc.party_type == "Shareholder") {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1740,7 +1740,7 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||||
|
|
||||||
# Get negative outstanding sales /purchase invoices
|
# Get negative outstanding sales /purchase invoices
|
||||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
if args.get("party_type") != "Employee":
|
||||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||||
args.get("party_type"),
|
args.get("party_type"),
|
||||||
args.get("party"),
|
args.get("party"),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency, get_currency_precision
|
||||||
from erpnext.utilities import payment_app_import_guard
|
from erpnext.utilities import payment_app_import_guard
|
||||||
|
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ def get_amount(ref_doc, payment_account=None):
|
|||||||
grand_total = ref_doc.outstanding_amount
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
if grand_total > 0:
|
if grand_total > 0:
|
||||||
return grand_total
|
return flt(grand_total, get_currency_precision())
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Payment Entry is already created"))
|
frappe.throw(_("Payment Entry is already created"))
|
||||||
|
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0)
|
stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0)
|
||||||
amount = stock_qty * (row.get("price_list_rate") or row.get("rate"))
|
amount = stock_qty * (flt(row.get("price_list_rate")) or flt(row.get("rate")))
|
||||||
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row)
|
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row)
|
||||||
|
|
||||||
if pricing_rules and pricing_rules[0]:
|
if pricing_rules and pricing_rules[0]:
|
||||||
|
|||||||
@@ -2236,6 +2236,62 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
self.assertEqual(pi_expected_values[i][1], gle.debit)
|
self.assertEqual(pi_expected_values[i][1], gle.debit)
|
||||||
self.assertEqual(pi_expected_values[i][2], gle.credit)
|
self.assertEqual(pi_expected_values[i][2], gle.credit)
|
||||||
|
|
||||||
|
def test_adjust_incoming_rate_from_pi_with_multi_currency(self):
|
||||||
|
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||||
|
make_landed_cost_voucher,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
|
||||||
|
|
||||||
|
# Increase the cost of the item
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
qty=10, rate=1, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
|
||||||
|
)
|
||||||
|
pr.conversion_rate = 6300
|
||||||
|
pr.plc_conversion_rate = 1
|
||||||
|
pr.save()
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pr.conversion_rate, 6300)
|
||||||
|
self.assertEqual(pr.plc_conversion_rate, 1)
|
||||||
|
self.assertEqual(pr.base_grand_total, 6300 * 10)
|
||||||
|
|
||||||
|
stock_value_difference = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
self.assertEqual(stock_value_difference, 6300 * 10)
|
||||||
|
|
||||||
|
make_landed_cost_voucher(
|
||||||
|
company=pr.company,
|
||||||
|
receipt_document_type="Purchase Receipt",
|
||||||
|
receipt_document=pr.name,
|
||||||
|
charges=3000,
|
||||||
|
distribute_charges_based_on="Qty",
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||||
|
for row in pi.items:
|
||||||
|
row.rate = 1.1
|
||||||
|
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
stock_value_difference = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
self.assertEqual(stock_value_difference, 7230 * 10)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
|||||||
@@ -737,10 +737,7 @@ class Subscription(Document):
|
|||||||
elif self.generate_invoice_at == "Days before the current subscription period":
|
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||||
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
||||||
|
|
||||||
process_subscription = frappe.new_doc("Process Subscription")
|
self.process(posting_date=processing_date)
|
||||||
process_subscription.posting_date = processing_date
|
|
||||||
process_subscription.subscription = self.name
|
|
||||||
process_subscription.save().submit()
|
|
||||||
|
|
||||||
|
|
||||||
def is_prorate() -> int:
|
def is_prorate() -> int:
|
||||||
|
|||||||
@@ -46,5 +46,11 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
|||||||
options: "Asset",
|
options: "Asset",
|
||||||
depends_on: "eval: doc.group_by == 'Asset'",
|
depends_on: "eval: doc.group_by == 'Asset'",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "finance_book",
|
||||||
|
label: __("Finance Book"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Finance Book",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ def get_asset_categories_for_grouped_by_category(filters):
|
|||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
condition += " and asset_category = %(asset_category)s"
|
condition += " and asset_category = %(asset_category)s"
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
|
||||||
|
|
||||||
# nosemgrep
|
# nosemgrep
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
@@ -119,6 +122,7 @@ def get_asset_categories_for_grouped_by_category(filters):
|
|||||||
"from_date": filters.from_date,
|
"from_date": filters.from_date,
|
||||||
"company": filters.company,
|
"company": filters.company,
|
||||||
"asset_category": filters.get("asset_category"),
|
"asset_category": filters.get("asset_category"),
|
||||||
|
"finance_book": filters.get("finance_book"),
|
||||||
},
|
},
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
@@ -128,6 +132,10 @@ def get_asset_details_for_grouped_by_category(filters):
|
|||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset"):
|
if filters.get("asset"):
|
||||||
condition += " and name = %(asset)s"
|
condition += " and name = %(asset)s"
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = `tabAsset`.name and ads.finance_book = %(finance_book)s)"
|
||||||
|
|
||||||
|
# nosemgrep
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
SELECT name,
|
SELECT name,
|
||||||
@@ -176,6 +184,7 @@ def get_asset_details_for_grouped_by_category(filters):
|
|||||||
"from_date": filters.from_date,
|
"from_date": filters.from_date,
|
||||||
"company": filters.company,
|
"company": filters.company,
|
||||||
"asset": filters.get("asset"),
|
"asset": filters.get("asset"),
|
||||||
|
"finance_book": filters.get("finance_book"),
|
||||||
},
|
},
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -154,8 +154,8 @@ def get_payment_entries(filters):
|
|||||||
select
|
select
|
||||||
"Payment Entry" as payment_document, name as payment_entry,
|
"Payment Entry" as payment_document, name as payment_entry,
|
||||||
reference_no, reference_date as ref_date,
|
reference_no, reference_date as ref_date,
|
||||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
|
||||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit,
|
||||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||||
from `tabPayment Entry`
|
from `tabPayment Entry`
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.query_reports["Cheques and Deposits Incorrectly cleared"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "company",
|
||||||
|
label: __("Company"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Company",
|
||||||
|
reqd: 1,
|
||||||
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "account",
|
||||||
|
label: __("Bank Account"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Account",
|
||||||
|
default: frappe.defaults.get_user_default("Company")
|
||||||
|
? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]
|
||||||
|
: "",
|
||||||
|
reqd: 1,
|
||||||
|
get_query: function () {
|
||||||
|
var company = frappe.query_report.get_filter_value("company");
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_account_list",
|
||||||
|
filters: [
|
||||||
|
["Account", "account_type", "in", "Bank, Cash"],
|
||||||
|
["Account", "is_group", "=", 0],
|
||||||
|
["Account", "disabled", "=", 0],
|
||||||
|
["Account", "company", "=", company],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "report_date",
|
||||||
|
label: __("Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2024-07-30 17:20:07.570971",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"letterhead": null,
|
||||||
|
"modified": "2024-07-30 17:20:07.570971",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Cheques and Deposits Incorrectly cleared",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Payment Entry",
|
||||||
|
"report_name": "Cheques and Deposits Incorrectly cleared",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import CustomFunction
|
||||||
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns = get_columns()
|
||||||
|
data = build_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def build_payment_entry_dict(row: dict) -> dict:
|
||||||
|
row_dict = frappe._dict()
|
||||||
|
row_dict.update(
|
||||||
|
{
|
||||||
|
"payment_document": row.get("doctype"),
|
||||||
|
"payment_entry": row.get("name"),
|
||||||
|
"posting_date": row.get("posting_date"),
|
||||||
|
"clearance_date": row.get("clearance_date"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if row.get("payment_type") == "Receive" and row.get("party_type") in ["Customer", "Supplier"]:
|
||||||
|
row_dict.update(
|
||||||
|
{
|
||||||
|
"debit": row.get("amount"),
|
||||||
|
"credit": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
row_dict.update(
|
||||||
|
{
|
||||||
|
"debit": 0,
|
||||||
|
"credit": row.get("amount"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return row_dict
|
||||||
|
|
||||||
|
|
||||||
|
def build_journal_entry_dict(row: dict) -> dict:
|
||||||
|
row_dict = frappe._dict()
|
||||||
|
row_dict.update(
|
||||||
|
{
|
||||||
|
"payment_document": row.get("doctype"),
|
||||||
|
"payment_entry": row.get("name"),
|
||||||
|
"posting_date": row.get("posting_date"),
|
||||||
|
"clearance_date": row.get("clearance_date"),
|
||||||
|
"debit": row.get("debit_in_account_currency"),
|
||||||
|
"credit": row.get("credit_in_account_currency"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return row_dict
|
||||||
|
|
||||||
|
|
||||||
|
def build_data(filters):
|
||||||
|
vouchers = get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters)
|
||||||
|
data = []
|
||||||
|
for x in vouchers:
|
||||||
|
if x.doctype == "Payment Entry":
|
||||||
|
data.append(build_payment_entry_dict(x))
|
||||||
|
elif x.doctype == "Journal Entry":
|
||||||
|
data.append(build_journal_entry_dict(x))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters):
|
||||||
|
je = qb.DocType("Journal Entry")
|
||||||
|
jea = qb.DocType("Journal Entry Account")
|
||||||
|
doctype_name = ConstantColumn("Journal Entry")
|
||||||
|
|
||||||
|
journals = (
|
||||||
|
qb.from_(je)
|
||||||
|
.inner_join(jea)
|
||||||
|
.on(je.name == jea.parent)
|
||||||
|
.select(
|
||||||
|
doctype_name.as_("doctype"),
|
||||||
|
je.name,
|
||||||
|
jea.debit_in_account_currency,
|
||||||
|
jea.credit_in_account_currency,
|
||||||
|
je.posting_date,
|
||||||
|
je.clearance_date,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
je.docstatus.eq(1)
|
||||||
|
& jea.account.eq(filters.account)
|
||||||
|
& je.posting_date.gt(filters.report_date)
|
||||||
|
& je.clearance_date.lte(filters.report_date)
|
||||||
|
& (je.is_opening.isnull() | je.is_opening.eq("No"))
|
||||||
|
)
|
||||||
|
.run(as_dict=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
||||||
|
pe = qb.DocType("Payment Entry")
|
||||||
|
doctype_name = ConstantColumn("Payment Entry")
|
||||||
|
payments = (
|
||||||
|
qb.from_(pe)
|
||||||
|
.select(
|
||||||
|
doctype_name.as_("doctype"),
|
||||||
|
pe.name,
|
||||||
|
ifelse(pe.paid_from.eq(filters.account), pe.paid_amount, pe.received_amount).as_("amount"),
|
||||||
|
pe.payment_type,
|
||||||
|
pe.party_type,
|
||||||
|
pe.posting_date,
|
||||||
|
pe.clearance_date,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
pe.docstatus.eq(1)
|
||||||
|
& (pe.paid_from.eq(filters.account) | pe.paid_to.eq(filters.account))
|
||||||
|
& pe.posting_date.gt(filters.report_date)
|
||||||
|
& pe.clearance_date.lte(filters.report_date)
|
||||||
|
)
|
||||||
|
.run(as_dict=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return journals + payments
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "payment_document",
|
||||||
|
"label": _("Payment Document Type"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_entry",
|
||||||
|
"label": _("Payment Document"),
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "payment_document",
|
||||||
|
"width": 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "debit",
|
||||||
|
"label": _("Debit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "account_currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "credit",
|
||||||
|
"label": _("Credit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "account_currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 110},
|
||||||
|
{"fieldname": "clearance_date", "label": _("Clearance Date"), "fieldtype": "Date", "width": 110},
|
||||||
|
]
|
||||||
@@ -767,8 +767,12 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount):
|
|||||||
|
|
||||||
every_year_depr = amount / total_years
|
every_year_depr = amount / total_years
|
||||||
|
|
||||||
|
depr_period_start_date = add_days(
|
||||||
|
get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1
|
||||||
|
)
|
||||||
|
|
||||||
year_start_date = add_years(
|
year_start_date = add_years(
|
||||||
row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12
|
depr_period_start_date, ((row.frequency_of_depreciation * schedule_idx) // 12)
|
||||||
)
|
)
|
||||||
year_end_date = add_days(add_years(year_start_date, 1), -1)
|
year_end_date = add_days(add_years(year_start_date, 1), -1)
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class Supplier(TransactionBase):
|
|||||||
elif supp_master_name == "Naming Series":
|
elif supp_master_name == "Naming Series":
|
||||||
set_name_by_naming_series(self)
|
set_name_by_naming_series(self)
|
||||||
else:
|
else:
|
||||||
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.create_primary_contact()
|
self.create_primary_contact()
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.query_reports["Item-wise Purchase History"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "company",
|
||||||
|
label: __("Company"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Company",
|
||||||
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "from_date",
|
||||||
|
reqd: 1,
|
||||||
|
label: __("From Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "to_date",
|
||||||
|
reqd: 1,
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
label: __("To Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_group",
|
||||||
|
label: __("Item Group"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item Group",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_code",
|
||||||
|
label: __("Item"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.item_query",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "supplier",
|
||||||
|
label: __("Supplier"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Supplier",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
formatter: function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
let format_fields = ["received_qty", "billed_amt"];
|
||||||
|
|
||||||
|
if (format_fields.includes(column.fieldname) && data && data[column.fieldname] > 0) {
|
||||||
|
value = "<span style='color:green;'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 1,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"creation": "2013-05-03 14:55:53",
|
||||||
"creation": "2013-05-03 14:55:53",
|
"disable_prepared_report": 0,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
"idx": 3,
|
"idx": 5,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2017-02-24 20:08:57.446613",
|
"modified": "2024-06-19 12:12:15.418799",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Item-wise Purchase History",
|
"name": "Item-wise Purchase History",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"query": "select\n po_item.item_code as \"Item Code:Link/Item:120\",\n\tpo_item.item_name as \"Item Name::120\",\n po_item.item_group as \"Item Group:Link/Item Group:120\",\n\tpo_item.description as \"Description::150\",\n\tpo_item.qty as \"Qty:Float:100\",\n\tpo_item.uom as \"UOM:Link/UOM:80\",\n\tpo_item.base_rate as \"Rate:Currency:120\",\n\tpo_item.base_amount as \"Amount:Currency:120\",\n\tpo.name as \"Purchase Order:Link/Purchase Order:120\",\n\tpo.transaction_date as \"Transaction Date:Date:140\",\n\tpo.supplier as \"Supplier:Link/Supplier:130\",\n sup.supplier_name as \"Supplier Name::150\",\n\tpo_item.project as \"Project:Link/Project:130\",\n\tifnull(po_item.received_qty, 0) as \"Received Qty:Float:120\",\n\tpo.company as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order` po, `tabPurchase Order Item` po_item, `tabSupplier` sup\nwhere\n\tpo.name = po_item.parent and po.supplier = sup.name and po.docstatus = 1\norder by po.name desc",
|
"prepared_report": 0,
|
||||||
"ref_doctype": "Purchase Order",
|
"ref_doctype": "Purchase Order",
|
||||||
"report_name": "Item-wise Purchase History",
|
"report_name": "Item-wise Purchase History",
|
||||||
"report_type": "Query Report",
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Stock User"
|
"role": "Stock User"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Purchase Manager"
|
"role": "Purchase Manager"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Purchase User"
|
"role": "Purchase User"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt
|
||||||
|
from frappe.utils.nestedset import get_descendants_of
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
if filters.from_date > filters.to_date:
|
||||||
|
frappe.throw(_("From Date cannot be greater than To Date"))
|
||||||
|
|
||||||
|
columns = get_columns(filters)
|
||||||
|
data = get_data(filters)
|
||||||
|
|
||||||
|
chart_data = get_chart_data(data)
|
||||||
|
|
||||||
|
return columns, data, None, chart_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns(filters):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"label": _("Item Code"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"options": "Item",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Item Name"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"width": 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Item Group"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"options": "Item Group",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Description"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fieldname": "description",
|
||||||
|
"width": 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Quantity"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"fieldname": "quantity",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("UOM"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "uom",
|
||||||
|
"options": "UOM",
|
||||||
|
"width": 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Rate"),
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Amount"),
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Purchase Order"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"width": 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Transaction Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"fieldname": "transaction_date",
|
||||||
|
"width": 110,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Supplier"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "supplier",
|
||||||
|
"options": "Supplier",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Supplier Name"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fieldname": "supplier_name",
|
||||||
|
"width": 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Supplier Group"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "supplier_group",
|
||||||
|
"options": "Supplier Group",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Project"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "project",
|
||||||
|
"options": "Project",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Received Quantity"),
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"fieldname": "received_qty",
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Billed Amount"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"fieldname": "billed_amt",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "company",
|
||||||
|
"options": "Company",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
company_list = get_descendants_of("Company", filters.get("company"))
|
||||||
|
company_list.append(filters.get("company"))
|
||||||
|
|
||||||
|
supplier_details = get_supplier_details()
|
||||||
|
item_details = get_item_details()
|
||||||
|
purchase_order_records = get_purchase_order_details(company_list, filters)
|
||||||
|
|
||||||
|
for record in purchase_order_records:
|
||||||
|
supplier_record = supplier_details.get(record.supplier)
|
||||||
|
item_record = item_details.get(record.item_code)
|
||||||
|
row = {
|
||||||
|
"item_code": record.get("item_code"),
|
||||||
|
"item_name": item_record.get("item_name"),
|
||||||
|
"item_group": item_record.get("item_group"),
|
||||||
|
"description": record.get("description"),
|
||||||
|
"quantity": record.get("qty"),
|
||||||
|
"uom": record.get("uom"),
|
||||||
|
"rate": record.get("base_rate"),
|
||||||
|
"amount": record.get("base_amount"),
|
||||||
|
"purchase_order": record.get("name"),
|
||||||
|
"transaction_date": record.get("transaction_date"),
|
||||||
|
"supplier": record.get("supplier"),
|
||||||
|
"supplier_name": supplier_record.get("supplier_name"),
|
||||||
|
"supplier_group": supplier_record.get("supplier_group"),
|
||||||
|
"project": record.get("project"),
|
||||||
|
"received_qty": flt(record.get("received_qty")),
|
||||||
|
"billed_amt": flt(record.get("billed_amt")),
|
||||||
|
"company": record.get("company"),
|
||||||
|
}
|
||||||
|
row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency")
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_supplier_details():
|
||||||
|
details = frappe.get_all("Supplier", fields=["name", "supplier_name", "supplier_group"])
|
||||||
|
supplier_details = {}
|
||||||
|
for d in details:
|
||||||
|
supplier_details.setdefault(
|
||||||
|
d.name,
|
||||||
|
frappe._dict({"supplier_name": d.supplier_name, "supplier_group": d.supplier_group}),
|
||||||
|
)
|
||||||
|
return supplier_details
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_details():
|
||||||
|
details = frappe.db.get_all("Item", fields=["name", "item_name", "item_group"])
|
||||||
|
item_details = {}
|
||||||
|
for d in details:
|
||||||
|
item_details.setdefault(d.name, frappe._dict({"item_name": d.item_name, "item_group": d.item_group}))
|
||||||
|
return item_details
|
||||||
|
|
||||||
|
|
||||||
|
def get_purchase_order_details(company_list, filters):
|
||||||
|
db_po = frappe.qb.DocType("Purchase Order")
|
||||||
|
db_po_item = frappe.qb.DocType("Purchase Order Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(db_po)
|
||||||
|
.inner_join(db_po_item)
|
||||||
|
.on(db_po_item.parent == db_po.name)
|
||||||
|
.select(
|
||||||
|
db_po.name,
|
||||||
|
db_po.supplier,
|
||||||
|
db_po.transaction_date,
|
||||||
|
db_po.project,
|
||||||
|
db_po.company,
|
||||||
|
db_po_item.item_code,
|
||||||
|
db_po_item.description,
|
||||||
|
db_po_item.qty,
|
||||||
|
db_po_item.uom,
|
||||||
|
db_po_item.base_rate,
|
||||||
|
db_po_item.base_amount,
|
||||||
|
db_po_item.received_qty,
|
||||||
|
(db_po_item.billed_amt * db_po.conversion_rate).as_("billed_amt"),
|
||||||
|
)
|
||||||
|
.where(db_po.docstatus == 1)
|
||||||
|
.where(db_po.company.isin(tuple(company_list)))
|
||||||
|
)
|
||||||
|
|
||||||
|
for field in ("item_code", "item_group"):
|
||||||
|
if filters.get(field):
|
||||||
|
query = query.where(db_po_item[field] == filters[field])
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query = query.where(db_po.transaction_date >= filters.from_date)
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query = query.where(db_po.transaction_date <= filters.to_date)
|
||||||
|
|
||||||
|
if filters.get("supplier"):
|
||||||
|
query = query.where(db_po.supplier == filters.supplier)
|
||||||
|
|
||||||
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chart_data(data):
|
||||||
|
item_wise_purchase_map = {}
|
||||||
|
labels, datapoints = [], []
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
item_key = row.get("item_code")
|
||||||
|
|
||||||
|
if item_key not in item_wise_purchase_map:
|
||||||
|
item_wise_purchase_map[item_key] = 0
|
||||||
|
|
||||||
|
item_wise_purchase_map[item_key] = flt(item_wise_purchase_map[item_key]) + flt(row.get("amount"))
|
||||||
|
|
||||||
|
item_wise_purchase_map = {
|
||||||
|
item: value
|
||||||
|
for item, value in (sorted(item_wise_purchase_map.items(), key=lambda i: i[1], reverse=True))
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in item_wise_purchase_map:
|
||||||
|
labels.append(key)
|
||||||
|
datapoints.append(item_wise_purchase_map[key])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"data": {
|
||||||
|
"labels": labels[:30], # show max of 30 items in chart
|
||||||
|
"datasets": [{"name": _("Total Purchase Amount"), "values": datapoints[:30]}],
|
||||||
|
},
|
||||||
|
"type": "bar",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
}
|
||||||
@@ -1334,6 +1334,12 @@ class AccountsController(TransactionBase):
|
|||||||
# Cancelling existing exchange gain/loss journals is handled during the `on_cancel` event.
|
# Cancelling existing exchange gain/loss journals is handled during the `on_cancel` event.
|
||||||
# see accounts/utils.py:cancel_exchange_gain_loss_journal()
|
# see accounts/utils.py:cancel_exchange_gain_loss_journal()
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
|
if dimensions_dict is None:
|
||||||
|
dimensions_dict = frappe._dict()
|
||||||
|
active_dimensions = get_dimensions()[0]
|
||||||
|
for dim in active_dimensions:
|
||||||
|
dimensions_dict[dim.fieldname] = self.get(dim.fieldname)
|
||||||
|
|
||||||
if self.get("doctype") == "Journal Entry":
|
if self.get("doctype") == "Journal Entry":
|
||||||
# 'args' is populated with exchange gain/loss account and the amount to be booked.
|
# 'args' is populated with exchange gain/loss account and the amount to be booked.
|
||||||
# These are generated by Sales/Purchase Invoice during reconciliation and advance allocation.
|
# These are generated by Sales/Purchase Invoice during reconciliation and advance allocation.
|
||||||
|
|||||||
@@ -185,7 +185,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "expected_closing",
|
"fieldname": "expected_closing",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Expected Closing Date"
|
"label": "Expected Closing Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_14",
|
"fieldname": "section_break_14",
|
||||||
@@ -357,6 +358,7 @@
|
|||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Opportunity Date",
|
"label": "Opportunity Date",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "transaction_date",
|
"oldfieldname": "transaction_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
@@ -388,6 +390,7 @@
|
|||||||
"fieldname": "first_response_time",
|
"fieldname": "first_response_time",
|
||||||
"fieldtype": "Duration",
|
"fieldtype": "Duration",
|
||||||
"label": "First Response Time",
|
"label": "First Response Time",
|
||||||
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -622,7 +625,7 @@
|
|||||||
"icon": "fa fa-info-sign",
|
"icon": "fa fa-info-sign",
|
||||||
"idx": 195,
|
"idx": 195,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-13 12:42:21.545636",
|
"modified": "2024-08-20 04:12:29.095761",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Opportunity",
|
"name": "Opportunity",
|
||||||
|
|||||||
@@ -2053,6 +2053,55 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
"BOM",
|
"BOM",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_disassemby_order(self):
|
||||||
|
fg_item = "Test Disassembly Item"
|
||||||
|
source_warehouse = "Stores - _TC"
|
||||||
|
raw_materials = ["Test Disassembly RM Item 1", "Test Disassembly RM Item 2"]
|
||||||
|
|
||||||
|
make_item(fg_item, {"is_stock_item": 1})
|
||||||
|
for item in raw_materials:
|
||||||
|
make_item(item, {"is_stock_item": 1})
|
||||||
|
test_stock_entry.make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
target=source_warehouse,
|
||||||
|
qty=1,
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)
|
||||||
|
|
||||||
|
wo = make_wo_order_test_record(
|
||||||
|
item=fg_item,
|
||||||
|
qty=1,
|
||||||
|
source_warehouse=source_warehouse,
|
||||||
|
skip_transfer=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
|
||||||
|
for row in stock_entry.items:
|
||||||
|
if row.item_code in raw_materials:
|
||||||
|
row.s_warehouse = source_warehouse
|
||||||
|
|
||||||
|
stock_entry.submit()
|
||||||
|
|
||||||
|
wo.reload()
|
||||||
|
self.assertEqual(wo.status, "Completed")
|
||||||
|
|
||||||
|
stock_entry = frappe.get_doc(make_stock_entry(wo.name, "Disassemble", 1))
|
||||||
|
stock_entry.save()
|
||||||
|
|
||||||
|
self.assertEqual(stock_entry.purpose, "Disassemble")
|
||||||
|
|
||||||
|
for row in stock_entry.items:
|
||||||
|
if row.item_code == fg_item:
|
||||||
|
self.assertTrue(row.s_warehouse)
|
||||||
|
self.assertFalse(row.t_warehouse)
|
||||||
|
else:
|
||||||
|
self.assertFalse(row.s_warehouse)
|
||||||
|
self.assertTrue(row.t_warehouse)
|
||||||
|
|
||||||
|
stock_entry.submit()
|
||||||
|
|
||||||
|
|
||||||
def make_operation(**kwargs):
|
def make_operation(**kwargs):
|
||||||
kwargs = frappe._dict(kwargs)
|
kwargs = frappe._dict(kwargs)
|
||||||
@@ -2370,6 +2419,7 @@ def make_wo_order_test_record(**args):
|
|||||||
wo_order.batch_size = args.batch_size or 0
|
wo_order.batch_size = args.batch_size or 0
|
||||||
|
|
||||||
if args.source_warehouse:
|
if args.source_warehouse:
|
||||||
|
wo_order.source_warehouse = args.source_warehouse
|
||||||
for item in wo_order.get("required_items"):
|
for item in wo_order.get("required_items"):
|
||||||
item.source_warehouse = args.source_warehouse
|
item.source_warehouse = args.source_warehouse
|
||||||
|
|
||||||
|
|||||||
@@ -183,13 +183,30 @@ frappe.ui.form.on("Work Order", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.status == "Completed") {
|
||||||
|
if (frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture") {
|
||||||
|
frm.add_custom_button(
|
||||||
|
__("BOM"),
|
||||||
|
() => {
|
||||||
|
frm.trigger("make_bom");
|
||||||
|
},
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
frm.doc.status == "Completed" &&
|
frm.doc.docstatus === 1 &&
|
||||||
frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture"
|
["Closed", "Completed"].includes(frm.doc.status) &&
|
||||||
|
frm.doc.produced_qty > 0
|
||||||
) {
|
) {
|
||||||
frm.add_custom_button(__("Create BOM"), () => {
|
frm.add_custom_button(
|
||||||
frm.trigger("make_bom");
|
__("Disassembly Order"),
|
||||||
});
|
() => {
|
||||||
|
frm.trigger("make_disassembly_order");
|
||||||
|
},
|
||||||
|
__("Create")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("add_custom_button_to_return_components");
|
frm.trigger("add_custom_button_to_return_components");
|
||||||
@@ -337,6 +354,23 @@ frappe.ui.form.on("Work Order", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_disassembly_order(frm) {
|
||||||
|
erpnext.work_order
|
||||||
|
.show_prompt_for_qty_input(frm, "Disassemble")
|
||||||
|
.then((data) => {
|
||||||
|
return frappe.xcall("erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", {
|
||||||
|
work_order_id: frm.doc.name,
|
||||||
|
purpose: "Disassemble",
|
||||||
|
qty: data.qty,
|
||||||
|
target_warehouse: data.target_warehouse,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stock_entry) => {
|
||||||
|
frappe.model.sync(stock_entry);
|
||||||
|
frappe.set_route("Form", stock_entry.doctype, stock_entry.name);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
show_progress_for_items: function (frm) {
|
show_progress_for_items: function (frm) {
|
||||||
var bars = [];
|
var bars = [];
|
||||||
var message = "";
|
var message = "";
|
||||||
@@ -745,6 +779,10 @@ erpnext.work_order = {
|
|||||||
|
|
||||||
get_max_transferable_qty: (frm, purpose) => {
|
get_max_transferable_qty: (frm, purpose) => {
|
||||||
let max = 0;
|
let max = 0;
|
||||||
|
if (purpose === "Disassemble") {
|
||||||
|
return flt(frm.doc.produced_qty);
|
||||||
|
}
|
||||||
|
|
||||||
if (frm.doc.skip_transfer) {
|
if (frm.doc.skip_transfer) {
|
||||||
max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
||||||
} else {
|
} else {
|
||||||
@@ -759,15 +797,38 @@ erpnext.work_order = {
|
|||||||
|
|
||||||
show_prompt_for_qty_input: function (frm, purpose) {
|
show_prompt_for_qty_input: function (frm, purpose) {
|
||||||
let max = this.get_max_transferable_qty(frm, purpose);
|
let max = this.get_max_transferable_qty(frm, purpose);
|
||||||
|
|
||||||
|
let fields = [
|
||||||
|
{
|
||||||
|
fieldtype: "Float",
|
||||||
|
label: __("Qty for {0}", [__(purpose)]),
|
||||||
|
fieldname: "qty",
|
||||||
|
description: __("Max: {0}", [max]),
|
||||||
|
default: max,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (purpose === "Disassemble") {
|
||||||
|
fields.push({
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Warehouse",
|
||||||
|
fieldname: "target_warehouse",
|
||||||
|
label: __("Target Warehouse"),
|
||||||
|
default: frm.doc.source_warehouse || frm.doc.wip_warehouse,
|
||||||
|
get_query() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_group: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
frappe.prompt(
|
frappe.prompt(
|
||||||
{
|
fields,
|
||||||
fieldtype: "Float",
|
|
||||||
label: __("Qty for {0}", [__(purpose)]),
|
|
||||||
fieldname: "qty",
|
|
||||||
description: __("Max: {0}", [max]),
|
|
||||||
default: max,
|
|
||||||
},
|
|
||||||
(data) => {
|
(data) => {
|
||||||
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
||||||
|
|
||||||
|
|||||||
@@ -1359,7 +1359,7 @@ def set_work_order_ops(name):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_stock_entry(work_order_id, purpose, qty=None):
|
def make_stock_entry(work_order_id, purpose, qty=None, target_warehouse=None):
|
||||||
work_order = frappe.get_doc("Work Order", work_order_id)
|
work_order = frappe.get_doc("Work Order", work_order_id)
|
||||||
if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"):
|
if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"):
|
||||||
wip_warehouse = work_order.wip_warehouse
|
wip_warehouse = work_order.wip_warehouse
|
||||||
@@ -1389,9 +1389,16 @@ def make_stock_entry(work_order_id, purpose, qty=None):
|
|||||||
stock_entry.to_warehouse = work_order.fg_warehouse
|
stock_entry.to_warehouse = work_order.fg_warehouse
|
||||||
stock_entry.project = work_order.project
|
stock_entry.project = work_order.project
|
||||||
|
|
||||||
|
if purpose == "Disassemble":
|
||||||
|
stock_entry.from_warehouse = work_order.fg_warehouse
|
||||||
|
stock_entry.to_warehouse = target_warehouse or work_order.source_warehouse
|
||||||
|
|
||||||
stock_entry.set_stock_entry_type()
|
stock_entry.set_stock_entry_type()
|
||||||
stock_entry.get_items()
|
stock_entry.get_items()
|
||||||
stock_entry.set_serial_no_batch_for_finished_good()
|
|
||||||
|
if purpose != "Disassemble":
|
||||||
|
stock_entry.set_serial_no_batch_for_finished_good()
|
||||||
|
|
||||||
return stock_entry.as_dict()
|
return stock_entry.as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -372,3 +372,5 @@ erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
|
|||||||
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
|
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
|
||||||
erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
||||||
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
||||||
|
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
||||||
|
erpnext.patches.v15_0.set_standard_stock_entry_type
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if not frappe.db.exists("Stock Entry Type", "Disassemble"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Stock Entry Type",
|
||||||
|
"name": "Disassemble",
|
||||||
|
"purpose": "Disassemble",
|
||||||
|
"is_standard": 1,
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
17
erpnext/patches/v15_0/set_standard_stock_entry_type.py
Normal file
17
erpnext/patches/v15_0/set_standard_stock_entry_type.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for stock_entry_type in [
|
||||||
|
"Material Issue",
|
||||||
|
"Material Receipt",
|
||||||
|
"Material Transfer",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
"Material Consumption for Manufacture",
|
||||||
|
"Manufacture",
|
||||||
|
"Repack",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
"Disassemble",
|
||||||
|
]:
|
||||||
|
if frappe.db.exists("Stock Entry Type", stock_entry_type):
|
||||||
|
frappe.db.set_value("Stock Entry Type", stock_entry_type, "is_standard", 1)
|
||||||
@@ -1421,12 +1421,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
let show = cint(this.frm.doc.discount_amount) ||
|
let show = cint(this.frm.doc.discount_amount) ||
|
||||||
((this.frm.doc.taxes || []).filter(function(d) {return d.included_in_print_rate===1}).length);
|
((this.frm.doc.taxes || []).filter(function(d) {return d.included_in_print_rate===1}).length);
|
||||||
|
|
||||||
if(frappe.meta.get_docfield(cur_frm.doctype, "net_total"))
|
if(this.frm.doc.doctype && frappe.meta.get_docfield(this.frm.doc.doctype, "net_total")) {
|
||||||
this.frm.toggle_display("net_total", show);
|
this.frm.toggle_display("net_total", show);
|
||||||
|
}
|
||||||
|
|
||||||
if(frappe.meta.get_docfield(cur_frm.doctype, "base_net_total"))
|
if(this.frm.doc.doctype && frappe.meta.get_docfield(this.frm.doc.doctype, "base_net_total")) {
|
||||||
this.frm.toggle_display("base_net_total", (show && (me.frm.doc.currency != company_currency)));
|
this.frm.toggle_display("base_net_total", (show && (me.frm.doc.currency != company_currency)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
change_grid_labels(company_currency) {
|
change_grid_labels(company_currency) {
|
||||||
|
|||||||
@@ -308,13 +308,15 @@
|
|||||||
"fetch_from": "customer_primary_contact.mobile_no",
|
"fetch_from": "customer_primary_contact.mobile_no",
|
||||||
"fieldname": "mobile_no",
|
"fieldname": "mobile_no",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Read Only",
|
||||||
"label": "Mobile No"
|
"label": "Mobile No",
|
||||||
|
"options": "Mobile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "customer_primary_contact.email_id",
|
"fetch_from": "customer_primary_contact.email_id",
|
||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Read Only",
|
||||||
"label": "Email Id"
|
"label": "Email Id",
|
||||||
|
"options": "Email"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_26",
|
"fieldname": "column_break_26",
|
||||||
@@ -592,7 +594,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-05-08 18:03:20.716169",
|
"modified": "2024-06-17 03:24:59.612974",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
@@ -677,4 +679,4 @@
|
|||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "customer_name",
|
"title_field": "customer_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class Customer(TransactionBase):
|
|||||||
elif cust_master_name == "Naming Series":
|
elif cust_master_name == "Naming Series":
|
||||||
set_name_by_naming_series(self)
|
set_name_by_naming_series(self)
|
||||||
else:
|
else:
|
||||||
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||||
|
|
||||||
def get_customer_name(self):
|
def get_customer_name(self):
|
||||||
if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
|
if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
|
||||||
|
|||||||
@@ -66,29 +66,54 @@ def install(country=None):
|
|||||||
"parent_item_group": _("All Item Groups"),
|
"parent_item_group": _("All Item Groups"),
|
||||||
},
|
},
|
||||||
# Stock Entry Type
|
# Stock Entry Type
|
||||||
{"doctype": "Stock Entry Type", "name": "Material Issue", "purpose": "Material Issue"},
|
{
|
||||||
{"doctype": "Stock Entry Type", "name": "Material Receipt", "purpose": "Material Receipt"},
|
"doctype": "Stock Entry Type",
|
||||||
|
"name": "Material Issue",
|
||||||
|
"purpose": "Material Issue",
|
||||||
|
"is_standard": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Stock Entry Type",
|
||||||
|
"name": "Material Receipt",
|
||||||
|
"purpose": "Material Receipt",
|
||||||
|
"is_standard": 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Stock Entry Type",
|
"doctype": "Stock Entry Type",
|
||||||
"name": "Material Transfer",
|
"name": "Material Transfer",
|
||||||
"purpose": "Material Transfer",
|
"purpose": "Material Transfer",
|
||||||
|
"is_standard": 1,
|
||||||
},
|
},
|
||||||
{"doctype": "Stock Entry Type", "name": "Manufacture", "purpose": "Manufacture"},
|
{
|
||||||
{"doctype": "Stock Entry Type", "name": "Repack", "purpose": "Repack"},
|
"doctype": "Stock Entry Type",
|
||||||
|
"name": "Manufacture",
|
||||||
|
"purpose": "Manufacture",
|
||||||
|
"is_standard": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Stock Entry Type",
|
||||||
|
"name": "Repack",
|
||||||
|
"purpose": "Repack",
|
||||||
|
"is_standard": 1,
|
||||||
|
},
|
||||||
|
{"doctype": "Stock Entry Type", "name": "Disassemble", "purpose": "Disassemble", "is_standard": 1},
|
||||||
{
|
{
|
||||||
"doctype": "Stock Entry Type",
|
"doctype": "Stock Entry Type",
|
||||||
"name": "Send to Subcontractor",
|
"name": "Send to Subcontractor",
|
||||||
"purpose": "Send to Subcontractor",
|
"purpose": "Send to Subcontractor",
|
||||||
|
"is_standard": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Stock Entry Type",
|
"doctype": "Stock Entry Type",
|
||||||
"name": "Material Transfer for Manufacture",
|
"name": "Material Transfer for Manufacture",
|
||||||
"purpose": "Material Transfer for Manufacture",
|
"purpose": "Material Transfer for Manufacture",
|
||||||
|
"is_standard": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Stock Entry Type",
|
"doctype": "Stock Entry Type",
|
||||||
"name": "Material Consumption for Manufacture",
|
"name": "Material Consumption for Manufacture",
|
||||||
"purpose": "Material Consumption for Manufacture",
|
"purpose": "Material Consumption for Manufacture",
|
||||||
|
"is_standard": 1,
|
||||||
},
|
},
|
||||||
# territory: with two default territories, one for home country and one named Rest of the World
|
# territory: with two default territories, one for home country and one named Rest of the World
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ class MaterialRequest(BuyingController):
|
|||||||
mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
|
mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
precision = d.precision("ordered_qty")
|
||||||
if d.name in mr_items:
|
if d.name in mr_items:
|
||||||
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
||||||
d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
|
d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
|
||||||
@@ -276,14 +277,14 @@ class MaterialRequest(BuyingController):
|
|||||||
(d.qty + (d.qty * (mr_qty_allowance / 100))), d.precision("ordered_qty")
|
(d.qty + (d.qty * (mr_qty_allowance / 100))), d.precision("ordered_qty")
|
||||||
)
|
)
|
||||||
|
|
||||||
if d.ordered_qty and d.ordered_qty > allowed_qty:
|
if d.ordered_qty and flt(d.ordered_qty, precision) > flt(allowed_qty, precision):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}"
|
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}"
|
||||||
).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
|
).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
|
elif d.ordered_qty and flt(d.ordered_qty, precision) > flt(d.stock_qty, precision):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than requested quantity {2} for Item {3}"
|
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than requested quantity {2} for Item {3}"
|
||||||
|
|||||||
@@ -1075,6 +1075,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
if item.billed_amt and item.amount:
|
if item.billed_amt and item.amount:
|
||||||
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
|
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
|
||||||
|
|
||||||
|
adjusted_amt = adjusted_amt * flt(pr_doc.conversion_rate)
|
||||||
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
|
||||||
|
|
||||||
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, cint, cstr, flt, getdate, nowtime, today
|
from frappe.utils import add_days, cint, cstr, flt, get_datetime, getdate, nowtime, today
|
||||||
from pypika import functions as fn
|
from pypika import functions as fn
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -3592,6 +3592,71 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
inter_transfer_dn.cancel()
|
inter_transfer_dn.cancel()
|
||||||
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
|
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
|
||||||
|
|
||||||
|
def test_sles_with_same_posting_datetime_and_creation(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
from erpnext.stock.report.stock_balance.stock_balance import execute
|
||||||
|
|
||||||
|
item_code = "Test Item for SLE with same posting datetime and creation"
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
posting_date="2023-11-06",
|
||||||
|
posting_time="00:00:00",
|
||||||
|
)
|
||||||
|
|
||||||
|
sr = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
source=pr.items[0].warehouse,
|
||||||
|
qty=10,
|
||||||
|
posting_date="2023-11-07",
|
||||||
|
posting_time="14:28:0.330404",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
|
||||||
|
sle_doc.db_set("creation", "2023-11-07 14:28:01.208930")
|
||||||
|
|
||||||
|
sle_doc.reload()
|
||||||
|
self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.208930"))
|
||||||
|
|
||||||
|
sr = make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target=pr.items[0].warehouse,
|
||||||
|
qty=50,
|
||||||
|
posting_date="2023-11-07",
|
||||||
|
posting_time="14:28:0.920825",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": sr.doctype, "voucher_no": sr.name, "item_code": sr.items[0].item_code},
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
sle_doc = frappe.get_doc("Stock Ledger Entry", sle)
|
||||||
|
sle_doc.db_set("creation", "2023-11-07 14:28:01.044561")
|
||||||
|
|
||||||
|
sle_doc.reload()
|
||||||
|
self.assertEqual(get_datetime(sle_doc.creation), get_datetime("2023-11-07 14:28:01.044561"))
|
||||||
|
|
||||||
|
pr.repost_future_sle_and_gle(force=True)
|
||||||
|
|
||||||
|
columns, data = execute(
|
||||||
|
filters=frappe._dict(
|
||||||
|
{"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(data[0].get("bal_qty"), 50.0)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
"label": "Purpose",
|
"label": "Purpose",
|
||||||
"oldfieldname": "purpose",
|
"oldfieldname": "purpose",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
|
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nDisassemble",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
|
"depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\", \"Material Consumption for Manufacture\", \"Disassemble\"], doc.purpose)",
|
||||||
"fieldname": "work_order",
|
"fieldname": "work_order",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Work Order",
|
"label": "Work Order",
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \"Send to Subcontractor\", \"Material Transfer for Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
|
"depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \"Send to Subcontractor\", \"Material Transfer for Manufacture\", \"Material Consumption for Manufacture\", \"Disassemble\"], doc.purpose)",
|
||||||
"fieldname": "from_bom",
|
"fieldname": "from_bom",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "From BOM",
|
"label": "From BOM",
|
||||||
@@ -697,7 +697,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-08-13 19:02:42.386955",
|
"modified": "2024-08-13 19:05:42.386955",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry",
|
"name": "Stock Entry",
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ class StockEntry(StockController):
|
|||||||
"Manufacture",
|
"Manufacture",
|
||||||
"Repack",
|
"Repack",
|
||||||
"Send to Subcontractor",
|
"Send to Subcontractor",
|
||||||
|
"Disassemble",
|
||||||
]
|
]
|
||||||
remarks: DF.Text | None
|
remarks: DF.Text | None
|
||||||
sales_invoice_no: DF.Link | None
|
sales_invoice_no: DF.Link | None
|
||||||
@@ -337,6 +338,7 @@ class StockEntry(StockController):
|
|||||||
"Repack",
|
"Repack",
|
||||||
"Send to Subcontractor",
|
"Send to Subcontractor",
|
||||||
"Material Consumption for Manufacture",
|
"Material Consumption for Manufacture",
|
||||||
|
"Disassemble",
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.purpose not in valid_purposes:
|
if self.purpose not in valid_purposes:
|
||||||
@@ -616,6 +618,7 @@ class StockEntry(StockController):
|
|||||||
"Manufacture",
|
"Manufacture",
|
||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
"Material Consumption for Manufacture",
|
"Material Consumption for Manufacture",
|
||||||
|
"Disassemble",
|
||||||
):
|
):
|
||||||
# check if work order is entered
|
# check if work order is entered
|
||||||
|
|
||||||
@@ -983,7 +986,7 @@ class StockEntry(StockController):
|
|||||||
def set_stock_entry_type(self):
|
def set_stock_entry_type(self):
|
||||||
if self.purpose:
|
if self.purpose:
|
||||||
self.stock_entry_type = frappe.get_cached_value(
|
self.stock_entry_type = frappe.get_cached_value(
|
||||||
"Stock Entry Type", {"purpose": self.purpose}, "name"
|
"Stock Entry Type", {"purpose": self.purpose, "is_standard": 1}, "name"
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_purpose_for_stock_entry(self):
|
def set_purpose_for_stock_entry(self):
|
||||||
@@ -1703,11 +1706,63 @@ class StockEntry(StockController):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_items_for_disassembly(self):
|
||||||
|
"""Get items for Disassembly Order"""
|
||||||
|
|
||||||
|
if not self.work_order:
|
||||||
|
frappe.throw(_("The Work Order is mandatory for Disassembly Order"))
|
||||||
|
|
||||||
|
items = self.get_items_from_manufacture_entry()
|
||||||
|
|
||||||
|
s_warehouse = ""
|
||||||
|
if self.work_order:
|
||||||
|
s_warehouse = frappe.db.get_value("Work Order", self.work_order, "fg_warehouse")
|
||||||
|
|
||||||
|
for row in items:
|
||||||
|
child_row = self.append("items", {})
|
||||||
|
for field, value in row.items():
|
||||||
|
if value is not None:
|
||||||
|
child_row.set(field, value)
|
||||||
|
|
||||||
|
child_row.s_warehouse = (self.from_warehouse or s_warehouse) if row.is_finished_item else ""
|
||||||
|
child_row.t_warehouse = self.to_warehouse if not row.is_finished_item else ""
|
||||||
|
child_row.is_finished_item = 0 if row.is_finished_item else 1
|
||||||
|
|
||||||
|
def get_items_from_manufacture_entry(self):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Stock Entry",
|
||||||
|
fields=[
|
||||||
|
"`tabStock Entry Detail`.`item_code`",
|
||||||
|
"`tabStock Entry Detail`.`item_name`",
|
||||||
|
"`tabStock Entry Detail`.`description`",
|
||||||
|
"`tabStock Entry Detail`.`qty`",
|
||||||
|
"`tabStock Entry Detail`.`transfer_qty`",
|
||||||
|
"`tabStock Entry Detail`.`stock_uom`",
|
||||||
|
"`tabStock Entry Detail`.`uom`",
|
||||||
|
"`tabStock Entry Detail`.`basic_rate`",
|
||||||
|
"`tabStock Entry Detail`.`conversion_factor`",
|
||||||
|
"`tabStock Entry Detail`.`is_finished_item`",
|
||||||
|
"`tabStock Entry Detail`.`batch_no`",
|
||||||
|
"`tabStock Entry Detail`.`serial_no`",
|
||||||
|
"`tabStock Entry Detail`.`use_serial_batch_fields`",
|
||||||
|
],
|
||||||
|
filters=[
|
||||||
|
["Stock Entry", "purpose", "=", "Manufacture"],
|
||||||
|
["Stock Entry", "work_order", "=", self.work_order],
|
||||||
|
["Stock Entry", "docstatus", "=", 1],
|
||||||
|
["Stock Entry Detail", "docstatus", "=", 1],
|
||||||
|
],
|
||||||
|
order_by="`tabStock Entry Detail`.`idx` desc, `tabStock Entry Detail`.`is_finished_item` desc",
|
||||||
|
)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items(self):
|
def get_items(self):
|
||||||
self.set("items", [])
|
self.set("items", [])
|
||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
|
|
||||||
|
if self.purpose == "Disassemble":
|
||||||
|
return self.get_items_for_disassembly()
|
||||||
|
|
||||||
if not self.posting_date or not self.posting_time:
|
if not self.posting_date or not self.posting_time:
|
||||||
frappe.throw(_("Posting date and posting time is mandatory"))
|
frappe.throw(_("Posting date and posting time is mandatory"))
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"purpose",
|
"purpose",
|
||||||
"add_to_transit"
|
"add_to_transit",
|
||||||
|
"is_standard"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Purpose",
|
"label": "Purpose",
|
||||||
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
|
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nDisassemble",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"set_only_once": 1
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
@@ -26,13 +27,21 @@
|
|||||||
"fieldname": "add_to_transit",
|
"fieldname": "add_to_transit",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Add to Transit"
|
"label": "Add to Transit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_standard",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Standard",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-07-08 08:41:19.385020",
|
"modified": "2024-08-24 16:00:22.696958",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry Type",
|
"name": "Stock Entry Type",
|
||||||
|
"naming_rule": "Set by user",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ class StockEntryType(Document):
|
|||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
add_to_transit: DF.Check
|
add_to_transit: DF.Check
|
||||||
|
is_standard: DF.Check
|
||||||
purpose: DF.Literal[
|
purpose: DF.Literal[
|
||||||
"",
|
"",
|
||||||
"Material Issue",
|
"Material Issue",
|
||||||
@@ -26,9 +27,25 @@ class StockEntryType(Document):
|
|||||||
"Manufacture",
|
"Manufacture",
|
||||||
"Repack",
|
"Repack",
|
||||||
"Send to Subcontractor",
|
"Send to Subcontractor",
|
||||||
|
"Disassemble",
|
||||||
]
|
]
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.validate_standard_type()
|
||||||
if self.add_to_transit and self.purpose != "Material Transfer":
|
if self.add_to_transit and self.purpose != "Material Transfer":
|
||||||
self.add_to_transit = 0
|
self.add_to_transit = 0
|
||||||
|
|
||||||
|
def validate_standard_type(self):
|
||||||
|
if self.is_standard and self.name not in [
|
||||||
|
"Material Issue",
|
||||||
|
"Material Receipt",
|
||||||
|
"Material Transfer",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
"Material Consumption for Manufacture",
|
||||||
|
"Manufacture",
|
||||||
|
"Repack",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
"Disassemble",
|
||||||
|
]:
|
||||||
|
frappe.throw(f"Stock Entry Type {self.name} cannot be set as standard")
|
||||||
|
|||||||
@@ -3,6 +3,33 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
class TestStockEntryType(unittest.TestCase):
|
class TestStockEntryType(unittest.TestCase):
|
||||||
pass
|
def test_stock_entry_type_non_standard(self):
|
||||||
|
stock_entry_type = "Test Manufacturing"
|
||||||
|
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Stock Entry Type",
|
||||||
|
"__newname": stock_entry_type,
|
||||||
|
"purpose": "Manufacture",
|
||||||
|
"is_standard": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, doc.insert)
|
||||||
|
|
||||||
|
def test_stock_entry_type_is_standard(self):
|
||||||
|
for stock_entry_type in [
|
||||||
|
"Material Issue",
|
||||||
|
"Material Receipt",
|
||||||
|
"Material Transfer",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
"Material Consumption for Manufacture",
|
||||||
|
"Manufacture",
|
||||||
|
"Repack",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
]:
|
||||||
|
self.assertTrue(frappe.db.get_value("Stock Entry Type", stock_entry_type, "is_standard"))
|
||||||
|
|||||||
@@ -352,7 +352,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "posting_datetime",
|
"fieldname": "posting_datetime",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "Posting Datetime"
|
"label": "Posting Datetime",
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
@@ -361,7 +362,7 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-06-27 16:23:18.820049",
|
"modified": "2024-08-27 09:28:03.961443",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Ledger Entry",
|
"name": "Stock Ledger Entry",
|
||||||
|
|||||||
@@ -351,3 +351,4 @@ def on_doctype_update():
|
|||||||
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
|
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
|
||||||
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
|
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
|
||||||
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
|
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
|
||||||
|
frappe.db.add_index("Stock Ledger Entry", ["posting_datetime", "creation"])
|
||||||
|
|||||||
@@ -1187,7 +1187,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
|||||||
qty=5,
|
qty=5,
|
||||||
posting_date="2021-01-01",
|
posting_date="2021-01-01",
|
||||||
rate=10,
|
rate=10,
|
||||||
posting_time="02:00:00.1234",
|
posting_time="02:00:00",
|
||||||
)
|
)
|
||||||
|
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
@@ -1199,7 +1199,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
|||||||
qty=100,
|
qty=100,
|
||||||
rate=10,
|
rate=10,
|
||||||
posting_date="2021-01-01",
|
posting_date="2021-01-01",
|
||||||
posting_time="02:00:00",
|
posting_time="02:00:00.1234",
|
||||||
)
|
)
|
||||||
|
|
||||||
sle = frappe.get_all(
|
sle = frappe.get_all(
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ def get_stock_ledger_entries_for_batch_bundle(filters):
|
|||||||
& (sle.has_batch_no == 1)
|
& (sle.has_batch_no == 1)
|
||||||
& (sle.posting_date <= filters["to_date"])
|
& (sle.posting_date <= filters["to_date"])
|
||||||
)
|
)
|
||||||
.groupby(batch_package.batch_no, batch_package.warehouse)
|
.groupby(sle.voucher_no, batch_package.batch_no, batch_package.warehouse)
|
||||||
.orderby(sle.item_code, sle.warehouse)
|
.orderby(sle.item_code, sle.warehouse)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class SerialBatchBundle:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.sle.actual_qty < 0 and self.is_material_transfer():
|
if self.sle.actual_qty < 0 and self.is_material_transfer():
|
||||||
values_to_update["valuation_rate"] = sn_doc.avg_rate
|
values_to_update["valuation_rate"] = flt(sn_doc.avg_rate)
|
||||||
|
|
||||||
if not frappe.db.get_single_value(
|
if not frappe.db.get_single_value(
|
||||||
"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle"
|
"Stock Settings", "do_not_update_serial_batch_on_creation_of_auto_bundle"
|
||||||
|
|||||||
@@ -1543,7 +1543,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
|||||||
and (
|
and (
|
||||||
posting_datetime {operator} %(posting_datetime)s
|
posting_datetime {operator} %(posting_datetime)s
|
||||||
)
|
)
|
||||||
order by posting_datetime desc, creation desc
|
order by posting_date desc, posting_time desc, creation desc
|
||||||
limit 1
|
limit 1
|
||||||
for update""",
|
for update""",
|
||||||
{
|
{
|
||||||
@@ -1636,7 +1636,7 @@ def get_stock_ledger_entries(
|
|||||||
where item_code = %(item_code)s
|
where item_code = %(item_code)s
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
{conditions}
|
{conditions}
|
||||||
order by posting_datetime {order}, creation {order}
|
order by posting_date {order}, posting_time {order}, creation {order}
|
||||||
{limit} {for_update}""".format(
|
{limit} {for_update}""".format(
|
||||||
conditions=conditions,
|
conditions=conditions,
|
||||||
limit=limit or "",
|
limit=limit or "",
|
||||||
@@ -1753,7 +1753,7 @@ def get_valuation_rate(
|
|||||||
AND valuation_rate >= 0
|
AND valuation_rate >= 0
|
||||||
AND is_cancelled = 0
|
AND is_cancelled = 0
|
||||||
AND NOT (voucher_no = %s AND voucher_type = %s)
|
AND NOT (voucher_no = %s AND voucher_type = %s)
|
||||||
order by posting_datetime desc, name desc limit 1""",
|
order by posting_date desc, posting_time desc, name desc limit 1""",
|
||||||
(item_code, warehouse, voucher_no, voucher_type),
|
(item_code, warehouse, voucher_no, voucher_type),
|
||||||
):
|
):
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
@@ -2004,7 +2004,7 @@ def get_future_sle_with_negative_qty(args):
|
|||||||
and posting_datetime >= %(posting_datetime)s
|
and posting_datetime >= %(posting_datetime)s
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
and qty_after_transaction < 0
|
and qty_after_transaction < 0
|
||||||
order by posting_datetime asc
|
order by posting_date asc, posting_time asc
|
||||||
limit 1
|
limit 1
|
||||||
""",
|
""",
|
||||||
args,
|
args,
|
||||||
@@ -2018,14 +2018,14 @@ def get_future_sle_with_negative_batch_qty(args):
|
|||||||
with batch_ledger as (
|
with batch_ledger as (
|
||||||
select
|
select
|
||||||
posting_date, posting_time, posting_datetime, voucher_type, voucher_no,
|
posting_date, posting_time, posting_datetime, voucher_type, voucher_no,
|
||||||
sum(actual_qty) over (order by posting_datetime, creation) as cumulative_total
|
sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where
|
where
|
||||||
item_code = %(item_code)s
|
item_code = %(item_code)s
|
||||||
and warehouse = %(warehouse)s
|
and warehouse = %(warehouse)s
|
||||||
and batch_no=%(batch_no)s
|
and batch_no=%(batch_no)s
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
order by posting_datetime, creation
|
order by posting_date, posting_time, creation
|
||||||
)
|
)
|
||||||
select * from batch_ledger
|
select * from batch_ledger
|
||||||
where
|
where
|
||||||
|
|||||||
Reference in New Issue
Block a user