mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 15:12:51 +00:00
Merge pull request #41941 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -683,6 +683,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""",
|
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
self.status_updater.append(
|
||||||
|
{
|
||||||
|
"source_dt": "Purchase Invoice Item",
|
||||||
|
"target_dt": "Material Request Item",
|
||||||
|
"join_field": "material_request_item",
|
||||||
|
"target_field": "received_qty",
|
||||||
|
"target_parent_dt": "Material Request",
|
||||||
|
"target_parent_field": "per_received",
|
||||||
|
"target_ref_field": "stock_qty",
|
||||||
|
"source_field": "stock_qty",
|
||||||
|
"percent_join_field": "material_request",
|
||||||
|
}
|
||||||
|
)
|
||||||
if cint(self.is_return):
|
if cint(self.is_return):
|
||||||
self.status_updater.append(
|
self.status_updater.append(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from erpnext.controllers.buying_controller import QtyMismatchError
|
|||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||||
|
from erpnext.stock.doctype.material_request.test_material_request import make_material_request
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||||
make_purchase_invoice as create_purchase_invoice_from_receipt,
|
make_purchase_invoice as create_purchase_invoice_from_receipt,
|
||||||
)
|
)
|
||||||
@@ -72,6 +74,31 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
# teardown
|
# teardown
|
||||||
pi.delete()
|
pi.delete()
|
||||||
|
|
||||||
|
def test_update_received_qty_in_material_request(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test if the received_qty in Material Request is updated correctly when
|
||||||
|
a Purchase Invoice with update_stock=True is submitted.
|
||||||
|
"""
|
||||||
|
mr = make_material_request(item_code="_Test Item", qty=10)
|
||||||
|
mr.save()
|
||||||
|
mr.submit()
|
||||||
|
po = make_purchase_order(mr.name)
|
||||||
|
po.supplier = "_Test Supplier"
|
||||||
|
po.save()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
# Create a Purchase Invoice with update_stock=True
|
||||||
|
pi = make_purchase_invoice(po.name)
|
||||||
|
pi.update_stock = True
|
||||||
|
pi.insert()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
# Check if the received quantity is updated in Material Request
|
||||||
|
mr.reload()
|
||||||
|
self.assertEqual(mr.items[0].received_qty, 10)
|
||||||
|
|
||||||
def test_gl_entries_without_perpetual_inventory(self):
|
def test_gl_entries_without_perpetual_inventory(self):
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||||
pi = frappe.copy_doc(test_records[0])
|
pi = frappe.copy_doc(test_records[0])
|
||||||
|
|||||||
@@ -105,6 +105,8 @@
|
|||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
"pr_detail",
|
"pr_detail",
|
||||||
"sales_invoice_item",
|
"sales_invoice_item",
|
||||||
|
"material_request",
|
||||||
|
"material_request_item",
|
||||||
"item_weight_details",
|
"item_weight_details",
|
||||||
"weight_per_unit",
|
"weight_per_unit",
|
||||||
"total_weight",
|
"total_weight",
|
||||||
@@ -934,12 +936,34 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_vbbb",
|
"fieldname": "column_break_vbbb",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "material_request",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Material Request",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Material Request",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "material_request_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Material Request Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"oldfieldname": "pr_detail",
|
||||||
|
"oldfieldtype": "Data",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-19 19:09:47.210965",
|
"modified": "2024-06-14 11:57:07.171700",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class PurchaseInvoiceItem(Document):
|
|||||||
manufacturer_part_no: DF.Data | None
|
manufacturer_part_no: DF.Data | None
|
||||||
margin_rate_or_amount: DF.Float
|
margin_rate_or_amount: DF.Float
|
||||||
margin_type: DF.Literal["", "Percentage", "Amount"]
|
margin_type: DF.Literal["", "Percentage", "Amount"]
|
||||||
|
material_request: DF.Link | None
|
||||||
|
material_request_item: DF.Data | None
|
||||||
net_amount: DF.Currency
|
net_amount: DF.Currency
|
||||||
net_rate: DF.Currency
|
net_rate: DF.Currency
|
||||||
page_break: DF.Check
|
page_break: DF.Check
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
|
|
||||||
|
|
||||||
def apply_conditions(query, pi, pii, filters):
|
def apply_conditions(query, pi, pii, filters):
|
||||||
for opts in ("company", "supplier", "item_code", "mode_of_payment"):
|
for opts in ("company", "supplier", "mode_of_payment"):
|
||||||
if filters.get(opts):
|
if filters.get(opts):
|
||||||
query = query.where(pi[opts] == filters[opts])
|
query = query.where(pi[opts] == filters[opts])
|
||||||
|
|
||||||
@@ -299,6 +299,9 @@ def apply_conditions(query, pi, pii, filters):
|
|||||||
if filters.get("to_date"):
|
if filters.get("to_date"):
|
||||||
query = query.where(pi.posting_date <= filters.get("to_date"))
|
query = query.where(pi.posting_date <= filters.get("to_date"))
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(pii.item_code == filters.get("item_code"))
|
||||||
|
|
||||||
if filters.get("item_group"):
|
if filters.get("item_group"):
|
||||||
query = query.where(pii.item_group == filters.get("item_group"))
|
query = query.where(pii.item_group == filters.get("item_group"))
|
||||||
|
|
||||||
@@ -322,7 +325,7 @@ def get_items(filters, additional_table_columns):
|
|||||||
.left_join(Item)
|
.left_join(Item)
|
||||||
.on(pii.item_code == Item.name)
|
.on(pii.item_code == Item.name)
|
||||||
.select(
|
.select(
|
||||||
pii.name.as_("pii_name"),
|
pii.name,
|
||||||
pii.parent,
|
pii.parent,
|
||||||
pi.posting_date,
|
pi.posting_date,
|
||||||
pi.credit_to,
|
pi.credit_to,
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = {
|
|||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Brand",
|
options: "Brand",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_code",
|
||||||
|
label: __("Item"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_group",
|
fieldname: "item_group",
|
||||||
label: __("Item Group"),
|
label: __("Item Group"),
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
|
|
||||||
|
|
||||||
def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
||||||
for opts in ("company", "customer", "item_code"):
|
for opts in ("company", "customer"):
|
||||||
if filters.get(opts):
|
if filters.get(opts):
|
||||||
query = query.where(si[opts] == filters[opts])
|
query = query.where(si[opts] == filters[opts])
|
||||||
|
|
||||||
@@ -371,6 +371,9 @@ def apply_conditions(query, si, sii, filters, additional_conditions=None):
|
|||||||
if filters.get("brand"):
|
if filters.get("brand"):
|
||||||
query = query.where(sii.brand == filters.get("brand"))
|
query = query.where(sii.brand == filters.get("brand"))
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(sii.item_code == filters.get("item_code"))
|
||||||
|
|
||||||
if filters.get("item_group"):
|
if filters.get("item_group"):
|
||||||
query = query.where(sii.item_group == filters.get("item_group"))
|
query = query.where(sii.item_group == filters.get("item_group"))
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
delivery_note = list(set(invoice_so_dn_map.get(inv.name, {}).get("delivery_note", [])))
|
delivery_note = list(set(invoice_so_dn_map.get(inv.name, {}).get("delivery_note", [])))
|
||||||
cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", [])))
|
cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", [])))
|
||||||
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||||
|
inv_customer_details = customer_details.get(inv.customer, {})
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
"voucher_type": inv.doctype,
|
"voucher_type": inv.doctype,
|
||||||
@@ -88,9 +89,9 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
"customer": inv.customer,
|
"customer": inv.customer,
|
||||||
"customer_name": inv.customer_name,
|
"customer_name": inv.customer_name,
|
||||||
**get_values_for_columns(additional_table_columns, inv),
|
**get_values_for_columns(additional_table_columns, inv),
|
||||||
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
"customer_group": inv_customer_details.get("customer_group"),
|
||||||
"territory": customer_details.get(inv.customer).get("territory"),
|
"territory": inv_customer_details.get("territory"),
|
||||||
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
"tax_id": inv_customer_details.get("tax_id"),
|
||||||
"receivable_account": inv.debit_to,
|
"receivable_account": inv.debit_to,
|
||||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||||
"project": inv.project,
|
"project": inv.project,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ erpnext.buying.setup_buying_controller();
|
|||||||
|
|
||||||
frappe.ui.form.on("Purchase Order", {
|
frappe.ui.form.on("Purchase Order", {
|
||||||
setup: function (frm) {
|
setup: function (frm) {
|
||||||
|
frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||||
if (frm.doc.is_old_subcontracting_flow) {
|
if (frm.doc.is_old_subcontracting_flow) {
|
||||||
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -484,7 +484,13 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.auto_create_subcontracting_order()
|
self.auto_create_subcontracting_order()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
|
self.ignore_linked_doctypes = (
|
||||||
|
"GL Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
"Unreconcile Payment",
|
||||||
|
"Unreconcile Payment Entries",
|
||||||
|
)
|
||||||
|
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
|
|
||||||
if self.is_against_so():
|
if self.is_against_so():
|
||||||
@@ -794,6 +800,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
|||||||
"field_map": {
|
"field_map": {
|
||||||
"name": "po_detail",
|
"name": "po_detail",
|
||||||
"parent": "purchase_order",
|
"parent": "purchase_order",
|
||||||
|
"material_request": "material_request",
|
||||||
|
"material_request_item": "material_request_item",
|
||||||
"wip_composite_asset": "wip_composite_asset",
|
"wip_composite_asset": "wip_composite_asset",
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from frappe.contacts.doctype.address.address import render_address
|
|||||||
from frappe.utils import cint, flt, getdate
|
from frappe.utils import cint, flt, getdate
|
||||||
from frappe.utils.data import nowtime
|
from frappe.utils.data import nowtime
|
||||||
|
|
||||||
|
import erpnext
|
||||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
|
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
|
||||||
@@ -332,6 +333,8 @@ class BuyingController(SubcontractingController):
|
|||||||
else:
|
else:
|
||||||
item.valuation_rate = 0.0
|
item.valuation_rate = 0.0
|
||||||
|
|
||||||
|
update_regional_item_valuation_rate(self)
|
||||||
|
|
||||||
def set_incoming_rate(self):
|
def set_incoming_rate(self):
|
||||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||||
return
|
return
|
||||||
@@ -935,3 +938,8 @@ def validate_item_type(doc, fieldname, message):
|
|||||||
).format(items, message)
|
).format(items, message)
|
||||||
|
|
||||||
frappe.throw(error_message)
|
frappe.throw(error_message)
|
||||||
|
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def update_regional_item_valuation_rate(doc):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -883,6 +883,9 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
|
|||||||
if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"):
|
if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"):
|
||||||
key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field)
|
key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field)
|
||||||
|
|
||||||
|
if row.voucher_type in ["Sales Invoice", "Delivery Note"]:
|
||||||
|
row.qty = -1 * row.qty
|
||||||
|
|
||||||
if key not in available_dict:
|
if key not in available_dict:
|
||||||
available_dict[key] = frappe._dict(
|
available_dict[key] = frappe._dict(
|
||||||
{"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)}
|
{"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class StockController(AccountsController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||||
|
|
||||||
@@ -118,7 +118,11 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries(warehouse_account)
|
gl_entries = (
|
||||||
|
self.get_gl_entries(warehouse_account, via_landed_cost_voucher)
|
||||||
|
if self.doctype == "Purchase Receipt"
|
||||||
|
else self.get_gl_entries(warehouse_account)
|
||||||
|
)
|
||||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||||
|
|
||||||
def validate_serialized_batch(self):
|
def validate_serialized_batch(self):
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"fieldname": "time_in_mins",
|
"fieldname": "time_in_mins",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Operation Time ",
|
"label": "Operation Time",
|
||||||
"oldfieldname": "time_in_mins",
|
"oldfieldname": "time_in_mins",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
|||||||
@@ -400,13 +400,20 @@ frappe.ui.form.on("Production Plan", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
download_materials_required(frm) {
|
download_materials_required(frm) {
|
||||||
|
const warehouses_data = [];
|
||||||
|
|
||||||
|
if (frm.doc.for_warehouse) {
|
||||||
|
warehouses_data.push({ warehouse: frm.doc.for_warehouse });
|
||||||
|
}
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
fieldname: "warehouses",
|
fieldname: "warehouses",
|
||||||
fieldtype: "Table MultiSelect",
|
fieldtype: "Table MultiSelect",
|
||||||
label: __("Warehouses"),
|
label: __("Warehouses"),
|
||||||
default: frm.doc.from_warehouse,
|
default: warehouses_data,
|
||||||
options: "Production Plan Material Request Warehouse",
|
options: "Production Plan Material Request Warehouse",
|
||||||
|
reqd: 1,
|
||||||
get_query: function () {
|
get_query: function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -9,15 +9,13 @@ def execute():
|
|||||||
|
|
||||||
dt = frappe.qb.DocType(doctype)
|
dt = frappe.qb.DocType(doctype)
|
||||||
records = (
|
records = (
|
||||||
frappe.qb.from_(dt)
|
frappe.qb.from_(dt).select(dt.name, dt.notes).where(dt.notes.isnotnull() & dt.notes != "")
|
||||||
.select(dt.name, dt.notes, dt.modified_by, dt.modified)
|
|
||||||
.where(dt.notes.isnotnull() & dt.notes != "")
|
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
for d in records:
|
for d in records:
|
||||||
if strip_html(cstr(d.notes)).strip():
|
if strip_html(cstr(d.notes)).strip():
|
||||||
doc = frappe.get_doc(doctype, d.name)
|
doc = frappe.get_doc(doctype, d.name)
|
||||||
doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified})
|
doc.append("notes", {"note": d.notes})
|
||||||
doc.update_child_table("notes")
|
doc.update_child_table("notes")
|
||||||
|
|
||||||
frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`")
|
frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
{% for(var i=0, l=notes.length; i<l; i++) { %}
|
{% for(var i=0, l=notes.length; i<l; i++) { %}
|
||||||
<div class="comment-content p-3 row" name="{{ notes[i].name }}">
|
<div class="comment-content p-3 row" name="{{ notes[i].name }}">
|
||||||
<div class="mb-2 head col-xs-3">
|
<div class="mb-2 head col-xs-3">
|
||||||
|
{% if (notes[i].added_by && notes[i].added_on) %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-2">
|
<div class="col-xs-2">
|
||||||
{{ frappe.avatar(notes[i].added_by) }}
|
{{ frappe.avatar(notes[i].added_by) }}
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% } %}
|
||||||
</div>
|
</div>
|
||||||
<div class="content col-xs-8">
|
<div class="content col-xs-8">
|
||||||
{{ notes[i].note }}
|
{{ notes[i].note }}
|
||||||
|
|||||||
@@ -933,7 +933,13 @@ erpnext.utils.map_current_doc = function (opts) {
|
|||||||
frappe.msgprint(__("Please select {0}", [opts.source_doctype]));
|
frappe.msgprint(__("Please select {0}", [opts.source_doctype]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.constructor === Array) {
|
||||||
|
opts.source_name = [...new Set(values)];
|
||||||
|
} else {
|
||||||
opts.source_name = values;
|
opts.source_name = values;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
opts.allow_child_item_selection ||
|
opts.allow_child_item_selection ||
|
||||||
["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)
|
["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)
|
||||||
|
|||||||
@@ -217,7 +217,11 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
frm.set_value("advance_paid", 0);
|
frm.set_value("advance_paid", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.ignore_doctypes_on_cancel_all = ["Purchase Order"];
|
frm.ignore_doctypes_on_cancel_all = [
|
||||||
|
"Purchase Order",
|
||||||
|
"Unreconcile Payment",
|
||||||
|
"Unreconcile Payment Entries",
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
delivery_date: function (frm) {
|
delivery_date: function (frm) {
|
||||||
|
|||||||
@@ -421,7 +421,13 @@ class SalesOrder(SellingController):
|
|||||||
self.create_stock_reservation_entries()
|
self.create_stock_reservation_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
|
self.ignore_linked_doctypes = (
|
||||||
|
"GL Entry",
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
"Unreconcile Payment",
|
||||||
|
"Unreconcile Payment Entries",
|
||||||
|
)
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
|
|
||||||
# Cannot cancel closed SO
|
# Cannot cancel closed SO
|
||||||
|
|||||||
@@ -857,7 +857,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
});
|
});
|
||||||
this.$customer_section.find(".customer-details").html(
|
this.$customer_section.find(".customer-details").html(
|
||||||
`<div class="header">
|
`<div class="header">
|
||||||
<div class="label">Contact Details</div>
|
<div class="label">${__("Contact Details")}</div>
|
||||||
<div class="close-details-btn">
|
<div class="close-details-btn">
|
||||||
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
|
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
|
||||||
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
|
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
|
||||||
@@ -877,7 +877,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
<div class="loyalty_program-field"></div>
|
<div class="loyalty_program-field"></div>
|
||||||
<div class="loyalty_points-field"></div>
|
<div class="loyalty_points-field"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transactions-label">Recent Transactions</div>`
|
<div class="transactions-label">${__("Recent Transactions")}</div>`
|
||||||
);
|
);
|
||||||
// transactions need to be in diff div from sticky elem for scrolling
|
// transactions need to be in diff div from sticky elem for scrolling
|
||||||
this.$customer_section.append(`<div class="customer-transactions"></div>`);
|
this.$customer_section.append(`<div class="customer-transactions"></div>`);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-28 17:26:46.826501",
|
"modified": "2024-06-12 16:10:31.451257",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Department",
|
"name": "Department",
|
||||||
@@ -132,6 +132,10 @@
|
|||||||
"role": "HR Manager",
|
"role": "HR Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Employee",
|
||||||
|
"select": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
|
|||||||
@@ -1814,6 +1814,110 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
self.assertEqual(sle_data.actual_qty, 1 * -1)
|
self.assertEqual(sle_data.actual_qty, 1 * -1)
|
||||||
self.assertEqual(sle_data.stock_value_difference, 200.0 * -1)
|
self.assertEqual(sle_data.stock_value_difference, 200.0 * -1)
|
||||||
|
|
||||||
|
def test_sales_return_batch_no_for_batched_item_in_dn(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Batched Item for Sales Return 11",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "B11-TESTBATCH.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn_return = make_sales_return(dn.name)
|
||||||
|
dn_return.save().submit()
|
||||||
|
returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle)
|
||||||
|
self.assertEqual(batch_no, returned_batch_no)
|
||||||
|
|
||||||
|
def test_partial_sales_return_batch_no_for_batched_item_in_dn(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Partial Batched Item for Sales Return 11",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BPART11-TESTBATCH.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn_return = make_sales_return(dn.name)
|
||||||
|
dn_return.items[0].qty = 3 * -1
|
||||||
|
dn_return.save().submit()
|
||||||
|
|
||||||
|
returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle)
|
||||||
|
self.assertEqual(batch_no, returned_batch_no)
|
||||||
|
sabb_qty = frappe.db.get_value(
|
||||||
|
"Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty"
|
||||||
|
)
|
||||||
|
self.assertEqual(sabb_qty, 3)
|
||||||
|
|
||||||
|
dn_return = make_sales_return(dn.name)
|
||||||
|
dn_return.items[0].qty = 2 * -1
|
||||||
|
dn_return.save().submit()
|
||||||
|
|
||||||
|
returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle)
|
||||||
|
self.assertEqual(batch_no, returned_batch_no)
|
||||||
|
|
||||||
|
sabb_qty = frappe.db.get_value(
|
||||||
|
"Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty"
|
||||||
|
)
|
||||||
|
self.assertEqual(sabb_qty, 2)
|
||||||
|
|
||||||
|
def test_sales_return_serial_no_for_serial_item_in_dn(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Serial Item for Sales Return 11",
|
||||||
|
properties={
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "SNN11-TESTBATCH.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
serial_no=serial_nos,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn_return = make_sales_return(dn.name)
|
||||||
|
dn_return.save().submit()
|
||||||
|
returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle)
|
||||||
|
self.assertEqual(serial_nos, returned_serial_nos)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ class LandedCostVoucher(Document):
|
|||||||
doc.docstatus = 1
|
doc.docstatus = 1
|
||||||
doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True)
|
doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True)
|
||||||
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
|
doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
|
||||||
|
if d.receipt_document_type == "Purchase Receipt":
|
||||||
|
doc.make_gl_entries(via_landed_cost_voucher=True)
|
||||||
|
else:
|
||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
doc.repost_future_sle_and_gle(via_landed_cost_voucher=True)
|
doc.repost_future_sle_and_gle(via_landed_cost_voucher=True)
|
||||||
|
|
||||||
|
|||||||
@@ -422,13 +422,13 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
self.set_consumed_qty_in_subcontract_order()
|
self.set_consumed_qty_in_subcontract_order()
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None):
|
def get_gl_entries(self, warehouse_account=None, via_landed_cost_voucher=False):
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
|
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher)
|
||||||
update_regional_gl_entries(gl_entries, self)
|
update_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
return process_gl_map(gl_entries)
|
return process_gl_map(gl_entries)
|
||||||
@@ -776,7 +776,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
posting_date=posting_date,
|
posting_date=posting_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False):
|
||||||
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
|
negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
|
||||||
is_asset_pr = any(d.is_fixed_asset for d in self.get("items"))
|
is_asset_pr = any(d.is_fixed_asset for d in self.get("items"))
|
||||||
# Cost center-wise amount breakup for other charges included for valuation
|
# Cost center-wise amount breakup for other charges included for valuation
|
||||||
@@ -811,6 +811,9 @@ class PurchaseReceipt(BuyingController):
|
|||||||
i = 1
|
i = 1
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
if valuation_tax.get(tax.name):
|
if valuation_tax.get(tax.name):
|
||||||
|
if via_landed_cost_voucher:
|
||||||
|
account = tax.account_head
|
||||||
|
else:
|
||||||
negative_expense_booked_in_pi = frappe.db.sql(
|
negative_expense_booked_in_pi = frappe.db.sql(
|
||||||
"""select name from `tabPurchase Invoice Item` pi
|
"""select name from `tabPurchase Invoice Item` pi
|
||||||
where docstatus = 1 and purchase_receipt=%s
|
where docstatus = 1 and purchase_receipt=%s
|
||||||
@@ -818,11 +821,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
and voucher_no=pi.parent and account=%s)""",
|
and voucher_no=pi.parent and account=%s)""",
|
||||||
(self.name, tax.account_head),
|
(self.name, tax.account_head),
|
||||||
)
|
)
|
||||||
|
account = stock_rbnb if negative_expense_booked_in_pi else tax.account_head
|
||||||
if negative_expense_booked_in_pi:
|
|
||||||
account = stock_rbnb
|
|
||||||
else:
|
|
||||||
account = tax.account_head
|
|
||||||
|
|
||||||
if i == len(valuation_tax):
|
if i == len(valuation_tax):
|
||||||
applicable_amount = amount_including_divisional_loss
|
applicable_amount = amount_including_divisional_loss
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from pypika import functions as fn
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.controllers.buying_controller import QtyMismatchError
|
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||||
|
from erpnext.stock import get_warehouse_account_map
|
||||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
@@ -1681,7 +1682,6 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
|
frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
|
||||||
|
|
||||||
def test_internal_pr_gl_entries(self):
|
def test_internal_pr_gl_entries(self):
|
||||||
from erpnext.stock import get_warehouse_account_map
|
|
||||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
@@ -2913,6 +2913,54 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_valuation_taxes_lcv_repost_after_billing(self):
|
||||||
|
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||||
|
make_landed_cost_voucher,
|
||||||
|
)
|
||||||
|
|
||||||
|
old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled("_Test Company")
|
||||||
|
frappe.local.enable_perpetual_inventory["_Test Company"] = 1
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company",
|
||||||
|
"_Test Company",
|
||||||
|
"stock_received_but_not_billed",
|
||||||
|
"Stock Received But Not Billed - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(qty=10, rate=1000, do_not_submit=1)
|
||||||
|
pr.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"category": "Valuation and Total",
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"account_head": "Freight and Forwarding Charges - _TC",
|
||||||
|
"tax_amount": 2000,
|
||||||
|
"description": "Test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pr.submit()
|
||||||
|
pi = make_purchase_invoice(pr.name)
|
||||||
|
pi.submit()
|
||||||
|
make_landed_cost_voucher(
|
||||||
|
company=pr.company,
|
||||||
|
receipt_document_type="Purchase Receipt",
|
||||||
|
receipt_document=pr.name,
|
||||||
|
charges=2000,
|
||||||
|
distribute_charges_based_on="Qty",
|
||||||
|
expense_account="Expenses Included In Valuation - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=False)
|
||||||
|
warehouse_account = get_warehouse_account_map("_Test Company")
|
||||||
|
expected_gle = (
|
||||||
|
("Stock Received But Not Billed - _TC", 0, 10000, "Main - _TC"),
|
||||||
|
("Freight and Forwarding Charges - _TC", 0, 2000, "Main - _TC"),
|
||||||
|
("Expenses Included In Valuation - _TC", 0, 2000, "Main - _TC"),
|
||||||
|
(warehouse_account[pr.items[0].warehouse]["account"], 14000, 0, "Main - _TC"),
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(expected_gle, gl_entries)
|
||||||
|
frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@@ -2959,14 +3007,24 @@ def get_sl_entries(voucher_type, voucher_no):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_gl_entries(voucher_type, voucher_no):
|
def get_gl_entries(voucher_type, voucher_no, skip_cancelled=False, as_dict=True):
|
||||||
return frappe.db.sql(
|
gl = frappe.qb.DocType("GL Entry")
|
||||||
"""select account, debit, credit, cost_center, is_cancelled
|
gl_query = (
|
||||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
frappe.qb.from_(gl)
|
||||||
order by account desc""",
|
.select(
|
||||||
(voucher_type, voucher_no),
|
gl.account,
|
||||||
as_dict=1,
|
gl.debit,
|
||||||
|
gl.credit,
|
||||||
|
gl.cost_center,
|
||||||
)
|
)
|
||||||
|
.where((gl.voucher_type == voucher_type) & (gl.voucher_no == voucher_no))
|
||||||
|
.orderby(gl.account, order=frappe.qb.desc)
|
||||||
|
)
|
||||||
|
if skip_cancelled:
|
||||||
|
gl_query = gl_query.where(gl.is_cancelled == 0)
|
||||||
|
else:
|
||||||
|
gl_query = gl_query.select(gl.is_cancelled)
|
||||||
|
return gl_query.run(as_dict=as_dict)
|
||||||
|
|
||||||
|
|
||||||
def get_taxes(**args):
|
def get_taxes(**args):
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class RepostItemValuation(Document):
|
|||||||
|
|
||||||
def validate_period_closing_voucher(self):
|
def validate_period_closing_voucher(self):
|
||||||
# Period Closing Voucher
|
# Period Closing Voucher
|
||||||
year_end_date = self.get_max_year_end_date(self.company)
|
year_end_date = self.get_max_period_closing_date(self.company)
|
||||||
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
||||||
date = frappe.format(year_end_date, "Date")
|
date = frappe.format(year_end_date, "Date")
|
||||||
msg = f"Due to period closing, you cannot repost item valuation before {date}"
|
msg = f"Due to period closing, you cannot repost item valuation before {date}"
|
||||||
@@ -120,24 +120,16 @@ class RepostItemValuation(Document):
|
|||||||
return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters)
|
return frappe.get_all("Closing Stock Balance", fields=["name", "to_date"], filters=filters)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_max_year_end_date(company):
|
def get_max_period_closing_date(company):
|
||||||
data = frappe.get_all(
|
table = frappe.qb.DocType("Period Closing Voucher")
|
||||||
"Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
|
|
||||||
fiscal_years = [d.fiscal_year for d in data]
|
|
||||||
table = frappe.qb.DocType("Fiscal Year")
|
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.select(Max(table.year_end_date))
|
.select(Max(table.posting_date))
|
||||||
.where((table.name.isin(fiscal_years)) & (table.disabled == 0))
|
.where((table.company == company) & (table.docstatus == 1))
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
return query[0][0] if query else None
|
return query[0][0] if query and query[0][0] else None
|
||||||
|
|
||||||
def validate_accounts_freeze(self):
|
def validate_accounts_freeze(self):
|
||||||
acc_settings = frappe.db.get_value(
|
acc_settings = frappe.db.get_value(
|
||||||
|
|||||||
Reference in New Issue
Block a user