mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-19 06:52:12 +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.model.document import Document
|
||||
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
|
||||
|
||||
import erpnext
|
||||
@@ -96,8 +96,11 @@ class BankClearance(Document):
|
||||
|
||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
||||
d.idx, d.clearance_date, d.cheque_date
|
||||
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
|
||||
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)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
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)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
return {
|
||||
filters: {
|
||||
account_type: ["in", account_types],
|
||||
@@ -412,6 +420,12 @@ frappe.ui.form.on("Payment Entry", {
|
||||
return {
|
||||
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")
|
||||
|
||||
# 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(
|
||||
args.get("party_type"),
|
||||
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.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
|
||||
|
||||
|
||||
@@ -552,7 +552,7 @@ def get_amount(ref_doc, payment_account=None):
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
if grand_total > 0:
|
||||
return grand_total
|
||||
return flt(grand_total, get_currency_precision())
|
||||
else:
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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][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):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -737,10 +737,7 @@ class Subscription(Document):
|
||||
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
||||
|
||||
process_subscription = frappe.new_doc("Process Subscription")
|
||||
process_subscription.posting_date = processing_date
|
||||
process_subscription.subscription = self.name
|
||||
process_subscription.save().submit()
|
||||
self.process(posting_date=processing_date)
|
||||
|
||||
|
||||
def is_prorate() -> int:
|
||||
|
||||
@@ -46,5 +46,11 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
||||
options: "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 = ""
|
||||
if filters.get("asset_category"):
|
||||
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
|
||||
return frappe.db.sql(
|
||||
f"""
|
||||
@@ -119,6 +122,7 @@ def get_asset_categories_for_grouped_by_category(filters):
|
||||
"from_date": filters.from_date,
|
||||
"company": filters.company,
|
||||
"asset_category": filters.get("asset_category"),
|
||||
"finance_book": filters.get("finance_book"),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -128,6 +132,10 @@ def get_asset_details_for_grouped_by_category(filters):
|
||||
condition = ""
|
||||
if filters.get("asset"):
|
||||
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(
|
||||
f"""
|
||||
SELECT name,
|
||||
@@ -176,6 +184,7 @@ def get_asset_details_for_grouped_by_category(filters):
|
||||
"from_date": filters.from_date,
|
||||
"company": filters.company,
|
||||
"asset": filters.get("asset"),
|
||||
"finance_book": filters.get("finance_book"),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -154,8 +154,8 @@ def get_payment_entries(filters):
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
|
||||
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,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
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
|
||||
|
||||
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(
|
||||
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)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class Supplier(TransactionBase):
|
||||
elif supp_master_name == "Naming Series":
|
||||
set_name_by_naming_series(self)
|
||||
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):
|
||||
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,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-05-03 14:55:53",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:08:57.446613",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Item-wise Purchase History",
|
||||
"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",
|
||||
"ref_doctype": "Purchase Order",
|
||||
"report_name": "Item-wise Purchase History",
|
||||
"report_type": "Query Report",
|
||||
"add_total_row": 1,
|
||||
"creation": "2013-05-03 14:55:53",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 5,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2024-06-19 12:12:15.418799",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Item-wise Purchase History",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"report_name": "Item-wise Purchase History",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
},
|
||||
{
|
||||
"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.
|
||||
# see accounts/utils.py:cancel_exchange_gain_loss_journal()
|
||||
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":
|
||||
# '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.
|
||||
|
||||
@@ -185,7 +185,8 @@
|
||||
{
|
||||
"fieldname": "expected_closing",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Closing Date"
|
||||
"label": "Expected Closing Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
@@ -357,6 +358,7 @@
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Opportunity Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date",
|
||||
"reqd": 1,
|
||||
@@ -388,6 +390,7 @@
|
||||
"fieldname": "first_response_time",
|
||||
"fieldtype": "Duration",
|
||||
"label": "First Response Time",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -622,7 +625,7 @@
|
||||
"icon": "fa fa-info-sign",
|
||||
"idx": 195,
|
||||
"links": [],
|
||||
"modified": "2022-10-13 12:42:21.545636",
|
||||
"modified": "2024-08-20 04:12:29.095761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
||||
@@ -2053,6 +2053,55 @@ class TestWorkOrder(FrappeTestCase):
|
||||
"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):
|
||||
kwargs = frappe._dict(kwargs)
|
||||
@@ -2370,6 +2419,7 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.batch_size = args.batch_size or 0
|
||||
|
||||
if args.source_warehouse:
|
||||
wo_order.source_warehouse = args.source_warehouse
|
||||
for item in wo_order.get("required_items"):
|
||||
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 (
|
||||
frm.doc.status == "Completed" &&
|
||||
frm.doc.__onload.backflush_raw_materials_based_on == "Material Transferred for Manufacture"
|
||||
frm.doc.docstatus === 1 &&
|
||||
["Closed", "Completed"].includes(frm.doc.status) &&
|
||||
frm.doc.produced_qty > 0
|
||||
) {
|
||||
frm.add_custom_button(__("Create BOM"), () => {
|
||||
frm.trigger("make_bom");
|
||||
});
|
||||
frm.add_custom_button(
|
||||
__("Disassembly Order"),
|
||||
() => {
|
||||
frm.trigger("make_disassembly_order");
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
var bars = [];
|
||||
var message = "";
|
||||
@@ -745,6 +779,10 @@ erpnext.work_order = {
|
||||
|
||||
get_max_transferable_qty: (frm, purpose) => {
|
||||
let max = 0;
|
||||
if (purpose === "Disassemble") {
|
||||
return flt(frm.doc.produced_qty);
|
||||
}
|
||||
|
||||
if (frm.doc.skip_transfer) {
|
||||
max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
||||
} else {
|
||||
@@ -759,15 +797,38 @@ erpnext.work_order = {
|
||||
|
||||
show_prompt_for_qty_input: function (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) => {
|
||||
frappe.prompt(
|
||||
{
|
||||
fieldtype: "Float",
|
||||
label: __("Qty for {0}", [__(purpose)]),
|
||||
fieldname: "qty",
|
||||
description: __("Max: {0}", [max]),
|
||||
default: max,
|
||||
},
|
||||
fields,
|
||||
(data) => {
|
||||
max += (frm.doc.qty * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100;
|
||||
|
||||
|
||||
@@ -1359,7 +1359,7 @@ def set_work_order_ops(name):
|
||||
|
||||
|
||||
@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)
|
||||
if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group"):
|
||||
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.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.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()
|
||||
|
||||
|
||||
|
||||
@@ -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.do_not_use_batchwise_valuation
|
||||
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) ||
|
||||
((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);
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
change_grid_labels(company_currency) {
|
||||
|
||||
@@ -308,13 +308,15 @@
|
||||
"fetch_from": "customer_primary_contact.mobile_no",
|
||||
"fieldname": "mobile_no",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Mobile No"
|
||||
"label": "Mobile No",
|
||||
"options": "Mobile"
|
||||
},
|
||||
{
|
||||
"fetch_from": "customer_primary_contact.email_id",
|
||||
"fieldname": "email_id",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Email Id"
|
||||
"label": "Email Id",
|
||||
"options": "Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_26",
|
||||
@@ -592,7 +594,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2024-05-08 18:03:20.716169",
|
||||
"modified": "2024-06-17 03:24:59.612974",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
@@ -677,4 +679,4 @@
|
||||
"states": [],
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class Customer(TransactionBase):
|
||||
elif cust_master_name == "Naming Series":
|
||||
set_name_by_naming_series(self)
|
||||
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):
|
||||
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"),
|
||||
},
|
||||
# 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",
|
||||
"name": "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",
|
||||
"name": "Send to Subcontractor",
|
||||
"purpose": "Send to Subcontractor",
|
||||
"is_standard": 1,
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Entry Type",
|
||||
"name": "Material Transfer for Manufacture",
|
||||
"purpose": "Material Transfer for Manufacture",
|
||||
"is_standard": 1,
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Entry Type",
|
||||
"name": "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
|
||||
{
|
||||
|
||||
@@ -267,6 +267,7 @@ class MaterialRequest(BuyingController):
|
||||
mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
|
||||
|
||||
for d in self.get("items"):
|
||||
precision = d.precision("ordered_qty")
|
||||
if d.name in mr_items:
|
||||
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
||||
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")
|
||||
)
|
||||
|
||||
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(
|
||||
_(
|
||||
"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)
|
||||
)
|
||||
|
||||
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(
|
||||
_(
|
||||
"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:
|
||||
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)
|
||||
|
||||
percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
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
|
||||
|
||||
import erpnext
|
||||
@@ -3592,6 +3592,71 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
inter_transfer_dn.cancel()
|
||||
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():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
"label": "Purpose",
|
||||
"oldfieldname": "purpose",
|
||||
"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,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -143,7 +143,7 @@
|
||||
"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",
|
||||
"fieldtype": "Link",
|
||||
"label": "Work Order",
|
||||
@@ -242,7 +242,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"label": "From BOM",
|
||||
@@ -697,7 +697,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-08-13 19:02:42.386955",
|
||||
"modified": "2024-08-13 19:05:42.386955",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
||||
@@ -132,6 +132,7 @@ class StockEntry(StockController):
|
||||
"Manufacture",
|
||||
"Repack",
|
||||
"Send to Subcontractor",
|
||||
"Disassemble",
|
||||
]
|
||||
remarks: DF.Text | None
|
||||
sales_invoice_no: DF.Link | None
|
||||
@@ -337,6 +338,7 @@ class StockEntry(StockController):
|
||||
"Repack",
|
||||
"Send to Subcontractor",
|
||||
"Material Consumption for Manufacture",
|
||||
"Disassemble",
|
||||
]
|
||||
|
||||
if self.purpose not in valid_purposes:
|
||||
@@ -616,6 +618,7 @@ class StockEntry(StockController):
|
||||
"Manufacture",
|
||||
"Material Transfer for Manufacture",
|
||||
"Material Consumption for Manufacture",
|
||||
"Disassemble",
|
||||
):
|
||||
# check if work order is entered
|
||||
|
||||
@@ -983,7 +986,7 @@ class StockEntry(StockController):
|
||||
def set_stock_entry_type(self):
|
||||
if self.purpose:
|
||||
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):
|
||||
@@ -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()
|
||||
def get_items(self):
|
||||
self.set("items", [])
|
||||
self.validate_work_order()
|
||||
|
||||
if self.purpose == "Disassemble":
|
||||
return self.get_items_for_disassembly()
|
||||
|
||||
if not self.posting_date or not self.posting_time:
|
||||
frappe.throw(_("Posting date and posting time is mandatory"))
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"purpose",
|
||||
"add_to_transit"
|
||||
"add_to_transit",
|
||||
"is_standard"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -16,7 +17,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"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,
|
||||
"set_only_once": 1
|
||||
},
|
||||
@@ -26,13 +27,21 @@
|
||||
"fieldname": "add_to_transit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add to Transit"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-07-08 08:41:19.385020",
|
||||
"modified": "2024-08-24 16:00:22.696958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Type",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class StockEntryType(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
add_to_transit: DF.Check
|
||||
is_standard: DF.Check
|
||||
purpose: DF.Literal[
|
||||
"",
|
||||
"Material Issue",
|
||||
@@ -26,9 +27,25 @@ class StockEntryType(Document):
|
||||
"Manufacture",
|
||||
"Repack",
|
||||
"Send to Subcontractor",
|
||||
"Disassemble",
|
||||
]
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_standard_type()
|
||||
if self.add_to_transit and self.purpose != "Material Transfer":
|
||||
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 frappe
|
||||
|
||||
|
||||
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",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Posting Datetime"
|
||||
"label": "Posting Datetime",
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -361,7 +362,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-27 16:23:18.820049",
|
||||
"modified": "2024-08-27 09:28:03.961443",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"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", ["batch_no", "item_code", "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,
|
||||
posting_date="2021-01-01",
|
||||
rate=10,
|
||||
posting_time="02:00:00.1234",
|
||||
posting_time="02:00:00",
|
||||
)
|
||||
|
||||
time.sleep(3)
|
||||
@@ -1199,7 +1199,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
qty=100,
|
||||
rate=10,
|
||||
posting_date="2021-01-01",
|
||||
posting_time="02:00:00",
|
||||
posting_time="02:00:00.1234",
|
||||
)
|
||||
|
||||
sle = frappe.get_all(
|
||||
|
||||
@@ -168,7 +168,7 @@ def get_stock_ledger_entries_for_batch_bundle(filters):
|
||||
& (sle.has_batch_no == 1)
|
||||
& (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)
|
||||
)
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ class SerialBatchBundle:
|
||||
}
|
||||
|
||||
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(
|
||||
"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 (
|
||||
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
|
||||
for update""",
|
||||
{
|
||||
@@ -1636,7 +1636,7 @@ def get_stock_ledger_entries(
|
||||
where item_code = %(item_code)s
|
||||
and is_cancelled = 0
|
||||
{conditions}
|
||||
order by posting_datetime {order}, creation {order}
|
||||
order by posting_date {order}, posting_time {order}, creation {order}
|
||||
{limit} {for_update}""".format(
|
||||
conditions=conditions,
|
||||
limit=limit or "",
|
||||
@@ -1753,7 +1753,7 @@ def get_valuation_rate(
|
||||
AND valuation_rate >= 0
|
||||
AND is_cancelled = 0
|
||||
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),
|
||||
):
|
||||
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 is_cancelled = 0
|
||||
and qty_after_transaction < 0
|
||||
order by posting_datetime asc
|
||||
order by posting_date asc, posting_time asc
|
||||
limit 1
|
||||
""",
|
||||
args,
|
||||
@@ -2018,14 +2018,14 @@ def get_future_sle_with_negative_batch_qty(args):
|
||||
with batch_ledger as (
|
||||
select
|
||||
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`
|
||||
where
|
||||
item_code = %(item_code)s
|
||||
and warehouse = %(warehouse)s
|
||||
and batch_no=%(batch_no)s
|
||||
and is_cancelled = 0
|
||||
order by posting_datetime, creation
|
||||
order by posting_date, posting_time, creation
|
||||
)
|
||||
select * from batch_ledger
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user