mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-25 13:45:04 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef07f97c9 | ||
|
|
670579bde3 | ||
|
|
8bd1e8ff74 | ||
|
|
49f66a8a51 | ||
|
|
0a09bb8d17 | ||
|
|
ba700bce90 | ||
|
|
22767410d5 | ||
|
|
89e7a260fd | ||
|
|
2a90bffb5f | ||
|
|
7434522ea1 | ||
|
|
158e1b28a9 | ||
|
|
a7672530f4 | ||
|
|
ad33735541 | ||
|
|
977f385006 | ||
|
|
b52b04a10c | ||
|
|
05b89b4121 | ||
|
|
36983c97cb | ||
|
|
23006601b2 | ||
|
|
37e9461ece | ||
|
|
126d79dcd2 | ||
|
|
93b2786865 | ||
|
|
2680f85430 | ||
|
|
2d7fde024d | ||
|
|
85bf9366b0 | ||
|
|
86f507bb6e | ||
|
|
81f19b950d | ||
|
|
7b4eac2294 | ||
|
|
aff4b62258 | ||
|
|
5798409f69 | ||
|
|
1b227b8b4f | ||
|
|
4eca7f0524 | ||
|
|
4bb66179ab | ||
|
|
d84a29e1a1 | ||
|
|
482a796212 | ||
|
|
41694d3751 | ||
|
|
82ca729e2b | ||
|
|
e67361257e | ||
|
|
e9f4a34d8d | ||
|
|
c6c4e31b32 | ||
|
|
ee5e4eccec | ||
|
|
a82fa8c26b | ||
|
|
97147a484d | ||
|
|
d8eddbfd03 | ||
|
|
977464e6a7 | ||
|
|
db84df0cbd | ||
|
|
1a7092d7b6 | ||
|
|
fcd05bb7e4 | ||
|
|
6c0694ff17 | ||
|
|
4988ff84df | ||
|
|
e5d0116924 | ||
|
|
38d3b17dfe | ||
|
|
89d305ac9f | ||
|
|
8b2be43897 | ||
|
|
0bd3b5aac2 | ||
|
|
8a995f28c9 | ||
|
|
e714b82802 | ||
|
|
7ec6ef3139 | ||
|
|
afc2d95736 | ||
|
|
1e9ffe351b | ||
|
|
0571ed0735 | ||
|
|
43941ecd5e | ||
|
|
8b9788ca74 | ||
|
|
eb38bcdab7 | ||
|
|
9876d1c7e6 | ||
|
|
5e8e6ef2f3 | ||
|
|
2055c4e8d3 | ||
|
|
97cdac10d7 | ||
|
|
df22902035 | ||
|
|
d67a439051 | ||
|
|
58a1383380 | ||
|
|
f5af5dd8c0 | ||
|
|
b9dd05f292 | ||
|
|
44f39e0b43 | ||
|
|
2b0281c510 | ||
|
|
03766e8980 | ||
|
|
e7b7377b7e | ||
|
|
a3a6d39436 | ||
|
|
76cfa28a42 | ||
|
|
afa7d2b595 | ||
|
|
c64dcf3423 | ||
|
|
3fa38d0b4e | ||
|
|
abb210bd18 | ||
|
|
3d3e116797 | ||
|
|
bd3a132868 |
@@ -4,7 +4,7 @@ import inspect
|
||||
import frappe
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "15.83.1"
|
||||
__version__ = "15.85.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -132,7 +132,8 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "IBAN",
|
||||
"length": 30
|
||||
"length": 34,
|
||||
"options": "IBAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
@@ -208,6 +209,7 @@
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"links": [
|
||||
{
|
||||
"group": "Transactions",
|
||||
@@ -250,7 +252,7 @@
|
||||
"link_fieldname": "default_bank_account"
|
||||
}
|
||||
],
|
||||
"modified": "2024-10-30 09:41:14.113414",
|
||||
"modified": "2025-08-29 12:32:01.081687",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Account",
|
||||
@@ -282,6 +284,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "bank,account",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
|
||||
@@ -9,7 +9,8 @@ from frappe.contacts.address_and_contact import (
|
||||
load_address_and_contact,
|
||||
)
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_and, get_link_to_form
|
||||
from frappe.utils import comma_and, get_link_to_form, validate_iban
|
||||
from frappe.utils.deprecations import deprecated
|
||||
|
||||
|
||||
class BankAccount(Document):
|
||||
@@ -52,7 +53,6 @@ class BankAccount(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_company()
|
||||
self.validate_iban()
|
||||
self.validate_account()
|
||||
self.update_default_bank_account()
|
||||
|
||||
@@ -72,34 +72,10 @@ class BankAccount(Document):
|
||||
if self.is_company_account and not self.company:
|
||||
frappe.throw(_("Company is manadatory for company account"))
|
||||
|
||||
@deprecated
|
||||
def validate_iban(self):
|
||||
"""
|
||||
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
||||
"""
|
||||
# IBAN field is optional
|
||||
if not self.iban:
|
||||
return
|
||||
|
||||
def encode_char(c):
|
||||
# Position in the alphabet (A=1, B=2, ...) plus nine
|
||||
return str(9 + ord(c) - 64)
|
||||
|
||||
# remove whitespaces, upper case to get the right number from ord()
|
||||
iban = "".join(self.iban.split(" ")).upper()
|
||||
|
||||
# Move country code and checksum from the start to the end
|
||||
flipped = iban[4:] + iban[:4]
|
||||
|
||||
# Encode characters as numbers
|
||||
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
|
||||
|
||||
try:
|
||||
to_check = int("".join(encoded))
|
||||
except ValueError:
|
||||
frappe.throw(_("IBAN is not valid"))
|
||||
|
||||
if to_check % 97 != 1:
|
||||
frappe.throw(_("IBAN is not valid"))
|
||||
"""Kept for backward compatibility, will be removed in v16."""
|
||||
validate_iban(self.iban, throw=True)
|
||||
|
||||
def update_default_bank_account(self):
|
||||
if self.is_default and not self.disabled:
|
||||
|
||||
@@ -3,45 +3,8 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError
|
||||
|
||||
# test_records = frappe.get_test_records('Bank Account')
|
||||
|
||||
|
||||
class TestBankAccount(unittest.TestCase):
|
||||
def test_validate_iban(self):
|
||||
valid_ibans = [
|
||||
"GB82 WEST 1234 5698 7654 32",
|
||||
"DE91 1000 0000 0123 4567 89",
|
||||
"FR76 3000 6000 0112 3456 7890 189",
|
||||
]
|
||||
|
||||
invalid_ibans = [
|
||||
# wrong checksum (3rd place)
|
||||
"GB72 WEST 1234 5698 7654 32",
|
||||
"DE81 1000 0000 0123 4567 89",
|
||||
"FR66 3000 6000 0112 3456 7890 189",
|
||||
]
|
||||
|
||||
bank_account = frappe.get_doc({"doctype": "Bank Account"})
|
||||
|
||||
try:
|
||||
bank_account.validate_iban()
|
||||
except AttributeError:
|
||||
msg = "BankAccount.validate_iban() failed for empty IBAN"
|
||||
self.fail(msg=msg)
|
||||
|
||||
for iban in valid_ibans:
|
||||
bank_account.iban = iban
|
||||
try:
|
||||
bank_account.validate_iban()
|
||||
except ValidationError:
|
||||
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
|
||||
self.fail(msg=msg)
|
||||
|
||||
for not_iban in invalid_ibans:
|
||||
bank_account.iban = not_iban
|
||||
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
|
||||
with self.assertRaises(ValidationError, msg=msg):
|
||||
bank_account.validate_iban()
|
||||
pass
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"fieldname": "iban",
|
||||
"fieldtype": "Data",
|
||||
"label": "IBAN",
|
||||
"options": "IBAN",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -216,7 +217,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-25 23:44:12.473583",
|
||||
"modified": "2025-09-26 00:38:17.584694",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Guarantee",
|
||||
|
||||
@@ -123,8 +123,7 @@
|
||||
"fieldname": "transaction_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Transaction ID",
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -223,7 +222,8 @@
|
||||
{
|
||||
"fieldname": "bank_party_iban",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party IBAN (Bank Statement)"
|
||||
"label": "Party IBAN (Bank Statement)",
|
||||
"options": "IBAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_party_account_number",
|
||||
@@ -238,7 +238,7 @@
|
||||
"grid_page_length": 50,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-26 17:06:29.207673",
|
||||
"modified": "2025-10-23 17:32:58.514807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
|
||||
@@ -99,7 +99,7 @@ class FiscalYear(Document):
|
||||
)
|
||||
|
||||
overlap = False
|
||||
if not self.get("companies") or not company_for_existing:
|
||||
if not self.get("companies") and not company_for_existing:
|
||||
overlap = True
|
||||
|
||||
for d in self.get("companies"):
|
||||
|
||||
@@ -26,6 +26,27 @@ class TestFiscalYear(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
|
||||
|
||||
def test_company_fiscal_year_overlap(self):
|
||||
for name in ["_Test Global FY 2001", "_Test Company FY 2001"]:
|
||||
if frappe.db.exists("Fiscal Year", name):
|
||||
frappe.delete_doc("Fiscal Year", name)
|
||||
|
||||
global_fy = frappe.new_doc("Fiscal Year")
|
||||
global_fy.year = "_Test Global FY 2001"
|
||||
global_fy.year_start_date = "2001-04-01"
|
||||
global_fy.year_end_date = "2002-03-31"
|
||||
global_fy.insert()
|
||||
|
||||
company_fy = frappe.new_doc("Fiscal Year")
|
||||
company_fy.year = "_Test Company FY 2001"
|
||||
company_fy.year_start_date = "2001-01-01"
|
||||
company_fy.year_end_date = "2001-12-31"
|
||||
company_fy.append("companies", {"company": "_Test Company"})
|
||||
|
||||
company_fy.insert()
|
||||
self.assertTrue(frappe.db.exists("Fiscal Year", global_fy.name))
|
||||
self.assertTrue(frappe.db.exists("Fiscal Year", company_fy.name))
|
||||
|
||||
|
||||
def test_record_generator():
|
||||
test_records = [
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"fieldname": "account_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"no_copy": 1,
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
@@ -272,7 +271,8 @@
|
||||
"label": "Advance Voucher Type",
|
||||
"no_copy": 1,
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "advance_voucher_no",
|
||||
@@ -280,13 +280,14 @@
|
||||
"label": "Advance Voucher No",
|
||||
"no_copy": 1,
|
||||
"options": "advance_voucher_type",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-29 13:01:48.916517",
|
||||
"modified": "2025-10-27 13:48:32.805100",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -385,6 +385,16 @@ frappe.ui.form.on("Payment Reconciliation Allocation", {
|
||||
// filter payment
|
||||
let payment = frm.doc.payments.filter((x) => x.reference_name == row.reference_name);
|
||||
|
||||
let amount = payment[0].amount;
|
||||
for (const d of frm.doc.allocation) {
|
||||
if (row.reference_name == d.reference_name && amount) {
|
||||
if (d.allocated_amount <= amount) {
|
||||
d.amount = amount;
|
||||
amount -= d.allocated_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "calculate_difference_on_allocation_change",
|
||||
|
||||
@@ -226,7 +226,8 @@
|
||||
"fetch_from": "bank_account.iban",
|
||||
"fieldname": "iban",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "IBAN"
|
||||
"label": "IBAN",
|
||||
"options": "IBAN"
|
||||
},
|
||||
{
|
||||
"fetch_from": "bank_account.branch_code",
|
||||
@@ -443,11 +444,12 @@
|
||||
"label": "Phone Number"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-12-27 21:29:10.361894",
|
||||
"modified": "2025-08-29 11:52:48.555415",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
@@ -482,8 +484,9 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_preview_popup": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,8 @@ def get_pos_invoices(start, end, pos_profile, user):
|
||||
`tabPOS Invoice`
|
||||
where
|
||||
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||
order by
|
||||
timestamp
|
||||
""",
|
||||
(user, pos_profile),
|
||||
as_dict=1,
|
||||
|
||||
@@ -2411,6 +2411,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
target.purchase_order = source.purchase_order
|
||||
target.po_detail = source.purchase_order_item
|
||||
|
||||
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
|
||||
target.use_serial_batch_fields = 1
|
||||
|
||||
item_field_map = {
|
||||
"doctype": target_doctype + " Item",
|
||||
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
|
||||
|
||||
@@ -648,6 +648,7 @@ def make_reverse_gl_entries(
|
||||
adv_adj=False,
|
||||
update_outstanding="Yes",
|
||||
partial_cancel=False,
|
||||
posting_date=None,
|
||||
):
|
||||
"""
|
||||
Get original gl entries of the voucher
|
||||
@@ -745,6 +746,8 @@ def make_reverse_gl_entries(
|
||||
if immutable_ledger_enabled:
|
||||
new_gle["is_cancelled"] = 0
|
||||
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
|
||||
elif posting_date:
|
||||
new_gle["posting_date"] = posting_date
|
||||
|
||||
if new_gle["debit"] or new_gle["credit"]:
|
||||
make_entry(new_gle, adv_adj, "Yes")
|
||||
|
||||
@@ -273,6 +273,7 @@ def get_asset_value_adjustment_map_by_category(filters):
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
AND gle.is_opening = 'No'
|
||||
GROUP BY a.asset_category
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
@@ -543,6 +544,7 @@ def get_asset_value_adjustment_map(filters):
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
AND gle.is_opening = 'No'
|
||||
GROUP BY a.name
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
|
||||
@@ -231,6 +231,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
|
||||
|
||||
group_columns = group_wise_columns.get(scrub(filters.group_by))
|
||||
|
||||
# removing customer_name from group columns
|
||||
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
if "customer_name" in group_columns and (
|
||||
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
|
||||
):
|
||||
group_columns = [col for col in group_columns if col != "customer_name"]
|
||||
|
||||
for src in gross_profit_data.grouped_data:
|
||||
total_base_amount += src.base_amount or 0.00
|
||||
total_buying_amount += src.buying_amount or 0.00
|
||||
|
||||
@@ -518,6 +518,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus > 0",
|
||||
"fieldname": "additional_asset_cost",
|
||||
"fieldtype": "Currency",
|
||||
@@ -596,7 +597,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2025-05-20 00:44:06.229177",
|
||||
"modified": "2025-10-23 22:43:33.634452",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
@@ -641,4 +642,4 @@
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ class Asset(AccountsController):
|
||||
default_finance_book: DF.Link | None
|
||||
department: DF.Link | None
|
||||
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
|
||||
depreciation_completed: DF.Check
|
||||
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
|
||||
disposal_date: DF.Date | None
|
||||
finance_books: DF.Table[AssetFinanceBook]
|
||||
@@ -152,7 +153,9 @@ class Asset(AccountsController):
|
||||
)
|
||||
self.validate_expected_value_after_useful_life()
|
||||
self.set_total_booked_depreciations()
|
||||
self.total_asset_cost = self.gross_purchase_amount
|
||||
|
||||
def before_save(self):
|
||||
self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost
|
||||
self.status = self.get_status()
|
||||
|
||||
def on_submit(self):
|
||||
|
||||
@@ -644,9 +644,8 @@ class PurchaseOrder(BuyingController):
|
||||
if not self.is_against_so():
|
||||
return
|
||||
for item in removed_items:
|
||||
prev_ordered_qty = (
|
||||
prev_ordered_qty = flt(
|
||||
frappe.get_cached_value("Sales Order Item", item.get("sales_order_item"), "ordered_qty")
|
||||
or 0.0
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -3073,9 +3073,7 @@ def set_balance_in_account_currency(
|
||||
_("Account: {0} with currency: {1} can not be selected").format(gl_dict.account, account_currency)
|
||||
)
|
||||
|
||||
gl_dict["account_currency"] = (
|
||||
company_currency if account_currency == company_currency else account_currency
|
||||
)
|
||||
gl_dict["account_currency"] = account_currency
|
||||
|
||||
# set debit/credit in account currency if not provided
|
||||
if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):
|
||||
|
||||
@@ -878,6 +878,7 @@ class BuyingController(SubcontractingController):
|
||||
"asset_category": item_data.get("asset_category"),
|
||||
"location": row.asset_location,
|
||||
"company": self.company,
|
||||
"status": "Draft",
|
||||
"supplier": self.supplier,
|
||||
"purchase_date": self.posting_date,
|
||||
"calculate_depreciation": 0,
|
||||
|
||||
@@ -253,6 +253,14 @@ class StockController(AccountsController):
|
||||
"do_not_submit": True if not via_landed_cost_voucher else False,
|
||||
}
|
||||
|
||||
if self.is_internal_transfer() and row.get("from_warehouse") and not self.is_return:
|
||||
self.update_bundle_details(bundle_details, table_name, row)
|
||||
bundle_details["type_of_transaction"] = "Outward"
|
||||
bundle_details["warehouse"] = row.get("from_warehouse")
|
||||
bundle_details["qty"] = row.get("stock_qty") or row.get("qty")
|
||||
self.create_serial_batch_bundle(bundle_details, row)
|
||||
continue
|
||||
|
||||
if row.get("qty") or row.get("consumed_qty") or row.get("stock_qty"):
|
||||
self.update_bundle_details(bundle_details, table_name, row, parent_details=parent_details)
|
||||
self.create_serial_batch_bundle(bundle_details, row)
|
||||
@@ -1544,7 +1552,9 @@ def get_stock_ledger_preview(doc, filters):
|
||||
|
||||
if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"):
|
||||
doc.docstatus = 1
|
||||
doc.make_bundle_using_old_serial_batch_fields()
|
||||
doc.update_stock_ledger()
|
||||
|
||||
columns = get_sl_columns(filters)
|
||||
sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name, fields)
|
||||
|
||||
|
||||
@@ -724,6 +724,9 @@ class ProductionPlan(Document):
|
||||
if not wo_list:
|
||||
frappe.msgprint(_("No Work Orders were created"))
|
||||
|
||||
if not po_list:
|
||||
frappe.msgprint(_("No Purchase Orders were created"))
|
||||
|
||||
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
|
||||
items_data = self.get_production_items()
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
||||
)
|
||||
|
||||
for item in exploded_items:
|
||||
print(item.bom_no, indent)
|
||||
item["indent"] = indent
|
||||
data.append(
|
||||
{
|
||||
|
||||
@@ -422,3 +422,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_fo
|
||||
erpnext.patches.v15_0.add_company_payment_gateway_account
|
||||
erpnext.patches.v15_0.update_uae_zero_rated_fetch
|
||||
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
|
||||
erpnext.patches.v15_0.set_asset_status_if_not_already_set
|
||||
|
||||
13
erpnext/patches/v15_0/set_asset_status_if_not_already_set.py
Normal file
13
erpnext/patches/v15_0/set_asset_status_if_not_already_set.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import frappe
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
|
||||
def execute():
|
||||
Asset = DocType("Asset")
|
||||
|
||||
query = (
|
||||
frappe.qb.update(Asset)
|
||||
.set(Asset.status, "Draft")
|
||||
.where((Asset.docstatus == 0) & ((Asset.status.isnull()) | (Asset.status == "")))
|
||||
)
|
||||
query.run()
|
||||
@@ -245,6 +245,7 @@
|
||||
"fieldname": "act_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Actual Start Date (via Timesheet)",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "act_start_date",
|
||||
"oldfieldtype": "Date",
|
||||
"read_only": 1
|
||||
@@ -253,6 +254,7 @@
|
||||
"fieldname": "actual_time",
|
||||
"fieldtype": "Float",
|
||||
"label": "Actual Time in Hours (via Timesheet)",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -263,6 +265,7 @@
|
||||
"fieldname": "act_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Actual End Date (via Timesheet)",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "act_end_date",
|
||||
"oldfieldtype": "Date",
|
||||
"read_only": 1
|
||||
@@ -277,6 +280,7 @@
|
||||
"fieldname": "total_costing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Costing Amount (via Timesheet)",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "actual_budget",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
@@ -290,6 +294,7 @@
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Billable Amount (via Timesheet)",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -399,7 +404,7 @@
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2024-01-08 16:00:41.296203",
|
||||
"modified": "2025-10-16 08:39:12.214577",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task",
|
||||
@@ -429,4 +434,4 @@
|
||||
"timeline_field": "project",
|
||||
"title_field": "subject",
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
|
||||
frappe.query_reports["Delayed Tasks Summary"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "project",
|
||||
label: __("Project"),
|
||||
fieldtype: "Link",
|
||||
options: "Project",
|
||||
},
|
||||
{
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
|
||||
@@ -23,6 +23,7 @@ def get_data(filters):
|
||||
fields=[
|
||||
"name",
|
||||
"subject",
|
||||
"project",
|
||||
"exp_start_date",
|
||||
"exp_end_date",
|
||||
"status",
|
||||
@@ -56,7 +57,7 @@ def get_data(filters):
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = frappe._dict()
|
||||
keys = ["priority", "status"]
|
||||
keys = ["priority", "status", "project"]
|
||||
for key in keys:
|
||||
if filters.get(key):
|
||||
conditions[key] = filters.get(key)
|
||||
@@ -89,6 +90,13 @@ def get_columns():
|
||||
columns = [
|
||||
{"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150},
|
||||
{"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": _("Project"),
|
||||
"options": "Project",
|
||||
"width": 150,
|
||||
},
|
||||
{"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100},
|
||||
{"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80},
|
||||
{"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120},
|
||||
|
||||
@@ -237,10 +237,10 @@ erpnext.accounts.pos = {
|
||||
frappe.ui.form.on(doctype, {
|
||||
mode_of_payment: function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
get_payment_mode_account(frm, d.mode_of_payment, function(account){
|
||||
frappe.model.set_value(cdt, cdn, 'account', account)
|
||||
})
|
||||
}
|
||||
erpnext.accounts.pos.get_payment_mode_account(frm, d.mode_of_payment, function (account) {
|
||||
frappe.model.set_value(cdt, cdn, "account", account);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -17,7 +17,17 @@ erpnext.buying = {
|
||||
this.setup_queries(doc, cdt, cdn);
|
||||
super.onload();
|
||||
|
||||
this.frm.set_query('shipping_rule', function() {
|
||||
if (["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype)) {
|
||||
this.frm.set_query("supplier", function () {
|
||||
return {
|
||||
filters: {
|
||||
is_transporter: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.set_query("shipping_rule", function () {
|
||||
return {
|
||||
filters: {
|
||||
"shipping_rule_type": "Buying"
|
||||
|
||||
@@ -394,6 +394,10 @@ erpnext.PointOfSale.Payment = class {
|
||||
const payments = doc.payments;
|
||||
const currency = doc.currency;
|
||||
|
||||
if (!this.$payment_modes.is(":visible")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$payment_modes.html(
|
||||
`${payments
|
||||
.map((p, i) => {
|
||||
@@ -612,6 +616,10 @@ erpnext.PointOfSale.Payment = class {
|
||||
const currency = doc.currency;
|
||||
const label = __("Change Amount");
|
||||
|
||||
if (!this.$totals.is(":visible")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$totals.html(
|
||||
`<div class="col">
|
||||
<div class="total-label">${__("Grand Total")}</div>
|
||||
|
||||
@@ -763,13 +763,11 @@ def update_company_current_month_sales(company):
|
||||
|
||||
def update_company_monthly_sales(company):
|
||||
"""Cache past year monthly sales of every company based on sales invoices"""
|
||||
import json
|
||||
|
||||
from frappe.utils.goal import get_monthly_results
|
||||
|
||||
filter_str = f"company = {frappe.db.escape(company)} and status != 'Draft' and docstatus=1"
|
||||
filter_dict = {"company": company, "status": ["!=", "Draft"], "docstatus": 1}
|
||||
month_to_value_dict = get_monthly_results(
|
||||
"Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum"
|
||||
"Sales Invoice", "base_grand_total", "posting_date", filter_dict, "sum"
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", company, "sales_monthly_history", json.dumps(month_to_value_dict))
|
||||
|
||||
@@ -24,6 +24,7 @@ frappe.ui.form.on("Driver", {
|
||||
|
||||
transporter: function (frm, cdt, cdn) {
|
||||
// this assumes that supplier's address has same title as supplier's name
|
||||
if (!frm.doc.transporter) return;
|
||||
frappe.db
|
||||
.get_doc("Address", null, { address_title: frm.doc.transporter })
|
||||
.then((r) => {
|
||||
|
||||
@@ -814,7 +814,8 @@
|
||||
"depends_on": "eval:doc.salary_mode == 'Bank'",
|
||||
"fieldname": "iban",
|
||||
"fieldtype": "Data",
|
||||
"label": "IBAN"
|
||||
"label": "IBAN",
|
||||
"options": "IBAN"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
@@ -822,7 +823,7 @@
|
||||
"image_field": "image",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-04 08:29:34.347269",
|
||||
"modified": "2025-08-29 11:52:12.819878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Employee",
|
||||
@@ -864,6 +865,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "employee_name",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
@@ -871,4 +873,4 @@
|
||||
"states": [],
|
||||
"title_field": "employee_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1310,6 +1310,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
if source.get("use_serial_batch_fields"):
|
||||
target.set("use_serial_batch_fields", 1)
|
||||
|
||||
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
|
||||
target.set("use_serial_batch_fields", 1)
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
doctype,
|
||||
source_name,
|
||||
|
||||
@@ -539,7 +539,7 @@ frappe.ui.form.on("Stock Entry", {
|
||||
const item = locals[cdt][cdn];
|
||||
item.transfer_qty = flt(item.qty) * flt(item.conversion_factor);
|
||||
|
||||
const args = {
|
||||
let args = {
|
||||
item_code: item.item_code,
|
||||
posting_date: frm.doc.posting_date,
|
||||
posting_time: frm.doc.posting_time,
|
||||
@@ -553,6 +553,10 @@ frappe.ui.form.on("Stock Entry", {
|
||||
allow_zero_valuation: 1,
|
||||
};
|
||||
|
||||
if (item.batch_no && frm.doc.purpose == "Material Receipt") {
|
||||
args.qty = Math.abs(args.qty) * -1;
|
||||
}
|
||||
|
||||
if (item.item_code || item.serial_no) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.utils.get_incoming_rate",
|
||||
|
||||
@@ -7,7 +7,10 @@ frappe.provide("erpnext.accounts.dimensions");
|
||||
frappe.ui.form.on("Stock Reconciliation", {
|
||||
setup(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
|
||||
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({ frm });
|
||||
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({
|
||||
frm: frm,
|
||||
uom_field: "stock_uom",
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
|
||||
@@ -36,17 +36,6 @@
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
"allow_internal_transfer_at_arms_length_price",
|
||||
"quality_inspection_settings_section",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"column_break_23",
|
||||
"action_if_quality_inspection_is_rejected",
|
||||
"stock_reservation_tab",
|
||||
"enable_stock_reservation",
|
||||
"column_break_rx3e",
|
||||
"allow_partial_reservation",
|
||||
"auto_reserve_stock_for_sales_order_on_purchase",
|
||||
"serial_and_batch_reservation_section",
|
||||
"auto_reserve_serial_and_batch",
|
||||
"serial_and_batch_item_settings_tab",
|
||||
"section_break_7",
|
||||
"allow_existing_serial_no",
|
||||
@@ -548,8 +537,8 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-06 02:39:24.284587",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2025-10-17 18:32:35.829395",
|
||||
"modified_by": "hello@aerele.in",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
"owner": "Administrator",
|
||||
@@ -569,8 +558,9 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -24,24 +24,26 @@ frappe.query_reports["Incorrect Serial and Batch Bundle"] = {
|
||||
},
|
||||
|
||||
onload(report) {
|
||||
report.page.add_inner_button(__("Remove SABB Entry"), () => {
|
||||
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
|
||||
report.page
|
||||
.add_inner_button(__("Fix SABB Entry"), () => {
|
||||
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
|
||||
|
||||
if (!selected_rows.length) {
|
||||
frappe.throw(__("Please select a row to create a Reposting Entry"));
|
||||
} else {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry",
|
||||
freeze: true,
|
||||
args: {
|
||||
selected_rows: selected_rows,
|
||||
},
|
||||
callback: function (r) {
|
||||
frappe.query_report.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!selected_rows.length) {
|
||||
frappe.throw(__("Please select at least one row to fix"));
|
||||
} else {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.fix_sabb_entries",
|
||||
freeze: true,
|
||||
args: {
|
||||
selected_rows: selected_rows,
|
||||
},
|
||||
callback: function (r) {
|
||||
frappe.query_report.refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.addClass("btn-primary");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,10 @@ def execute(filters: dict | None = None):
|
||||
every time the report is refreshed or a filter is updated.
|
||||
"""
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
unlinked_bundles = get_unlinked_serial_batch_bundles(filters) or []
|
||||
linked_cancelled_bundles = get_linked_cancelled_sabb(filters) or []
|
||||
|
||||
data = unlinked_bundles + linked_cancelled_bundles
|
||||
|
||||
return columns, data
|
||||
|
||||
@@ -50,14 +53,17 @@ def get_columns() -> list[dict]:
|
||||
"fieldtype": "Data",
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"label": _("Is Cancelled"),
|
||||
"fieldname": "is_cancelled",
|
||||
"fieldtype": "Check",
|
||||
"width": 200,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters) -> list[list]:
|
||||
"""Return data for the report.
|
||||
|
||||
The report data is a list of rows, with each row being a list of cell values.
|
||||
"""
|
||||
def get_unlinked_serial_batch_bundles(filters) -> list[list]:
|
||||
# SABB has not been linked to any SLE
|
||||
|
||||
SABB = frappe.qb.DocType("Serial and Batch Bundle")
|
||||
SLE = frappe.qb.DocType("Stock Ledger Entry")
|
||||
@@ -77,6 +83,7 @@ def get_data(filters) -> list[list]:
|
||||
SABB.voucher_type,
|
||||
SABB.voucher_no,
|
||||
SABB.voucher_detail_no,
|
||||
SABB.is_cancelled,
|
||||
)
|
||||
.where(
|
||||
(SLE.serial_and_batch_bundle.isnull())
|
||||
@@ -94,14 +101,63 @@ def get_data(filters) -> list[list]:
|
||||
return data
|
||||
|
||||
|
||||
def get_linked_cancelled_sabb(filters):
|
||||
# SABB has cancelled but voucher is not cancelled
|
||||
|
||||
SABB = frappe.qb.DocType("Serial and Batch Bundle")
|
||||
SLE = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(SABB)
|
||||
.inner_join(SLE)
|
||||
.on(SABB.name == SLE.serial_and_batch_bundle)
|
||||
.select(
|
||||
SABB.name,
|
||||
SABB.voucher_type,
|
||||
SABB.voucher_no,
|
||||
SABB.voucher_detail_no,
|
||||
SABB.is_cancelled,
|
||||
)
|
||||
.where(
|
||||
(SLE.serial_and_batch_bundle.isnotnull())
|
||||
& (SABB.docstatus == 2)
|
||||
& (SABB.is_cancelled == 1)
|
||||
& (SLE.is_cancelled == 0)
|
||||
)
|
||||
)
|
||||
|
||||
for field in filters:
|
||||
query = query.where(SABB[field] == filters[field])
|
||||
|
||||
data = query.run(as_dict=1)
|
||||
return data
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_sabb_entry(selected_rows):
|
||||
def fix_sabb_entries(selected_rows):
|
||||
if isinstance(selected_rows, str):
|
||||
selected_rows = frappe.parse_json(selected_rows)
|
||||
|
||||
for row in selected_rows:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", row.get("name"))
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
if doc.is_cancelled == 0 and not frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
|
||||
"name",
|
||||
):
|
||||
doc.db_set({"is_cancelled": 1, "docstatus": 2})
|
||||
|
||||
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))
|
||||
for row in doc.entries:
|
||||
row.db_set("docstatus", 2)
|
||||
|
||||
elif doc.is_cancelled == 1 and frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
|
||||
"name",
|
||||
):
|
||||
doc.db_set({"is_cancelled": 0, "docstatus": 1})
|
||||
|
||||
for row in doc.entries:
|
||||
row.db_set("docstatus", 1)
|
||||
|
||||
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been fixed."))
|
||||
|
||||
@@ -18,16 +18,41 @@ def execute(filters=None):
|
||||
|
||||
def get_data(filters):
|
||||
data = get_stock_ledger_entries(filters)
|
||||
serial_nos_data = prepare_serial_nos(data)
|
||||
bundles = get_bundles(data)
|
||||
serial_nos_data = prepare_serial_nos(data, bundles)
|
||||
data = get_incorrect_serial_nos(serial_nos_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def prepare_serial_nos(data):
|
||||
def get_bundles(data):
|
||||
bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle]
|
||||
bundle_dict = frappe._dict()
|
||||
serial_nos_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
fields=["parent", "serial_no", "incoming_rate", "qty"],
|
||||
filters={"parent": ("in", bundles), "serial_no": ("is", "set")},
|
||||
)
|
||||
|
||||
for entry in serial_nos_data:
|
||||
bundle_dict.setdefault(entry.parent, []).append(entry)
|
||||
|
||||
return bundle_dict
|
||||
|
||||
|
||||
def prepare_serial_nos(data, bundles):
|
||||
serial_no_wise_data = {}
|
||||
for row in data:
|
||||
if not row.serial_nos:
|
||||
if not row.serial_nos and not row.serial_and_batch_bundle:
|
||||
continue
|
||||
|
||||
if row.serial_and_batch_bundle:
|
||||
for bundle in bundles.get(row.serial_and_batch_bundle, []):
|
||||
sle = copy.deepcopy(row)
|
||||
sle.serial_no = bundle.serial_no
|
||||
sle.qty = bundle.qty
|
||||
sle.valuation_rate = bundle.incoming_rate * (1 if sle.qty > 0 else -1)
|
||||
serial_no_wise_data.setdefault(bundle.serial_no, []).append(sle)
|
||||
continue
|
||||
|
||||
for serial_no in get_serial_nos(row.serial_nos):
|
||||
@@ -54,6 +79,9 @@ def get_incorrect_serial_nos(serial_nos_data):
|
||||
total_value.qty += total_dict.qty
|
||||
total_value.valuation_rate += total_dict.valuation_rate
|
||||
|
||||
if total_dict.qty == 0 and abs(total_dict.valuation_rate) == 0:
|
||||
continue
|
||||
|
||||
result.append(total_dict)
|
||||
result.append({})
|
||||
|
||||
@@ -81,6 +109,7 @@ def get_stock_ledger_entries(report_filters):
|
||||
"voucher_no",
|
||||
"item_code",
|
||||
"serial_no as serial_nos",
|
||||
"serial_and_batch_bundle",
|
||||
"actual_qty",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
@@ -89,7 +118,8 @@ def get_stock_ledger_entries(report_filters):
|
||||
"(stock_value_difference / actual_qty) as valuation_rate",
|
||||
]
|
||||
|
||||
filters = {"serial_no": ("is", "set"), "is_cancelled": 0}
|
||||
filters = {"is_cancelled": 0}
|
||||
or_filters = {"serial_no": ("is", "set"), "serial_and_batch_bundle": ("is", "set")}
|
||||
|
||||
if report_filters.get("item_code"):
|
||||
filters["item_code"] = report_filters.get("item_code")
|
||||
@@ -104,6 +134,7 @@ def get_stock_ledger_entries(report_filters):
|
||||
"Stock Ledger Entry",
|
||||
fields=fields,
|
||||
filters=filters,
|
||||
or_filters=or_filters,
|
||||
order_by="posting_date asc, posting_time asc, creation asc",
|
||||
)
|
||||
|
||||
|
||||
@@ -731,13 +731,55 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
for ledger in entries:
|
||||
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
|
||||
self.available_qty[ledger.batch_no] += flt(ledger.qty)
|
||||
self.total_qty[ledger.batch_no] += flt(ledger.total_qty)
|
||||
|
||||
entries = self.get_batch_wise_total_available_qty()
|
||||
for row in entries:
|
||||
self.total_qty[row.batch_no] += flt(row.total_qty)
|
||||
|
||||
self.calculate_avg_rate_from_deprecarated_ledgers()
|
||||
self.calculate_avg_rate_for_non_batchwise_valuation()
|
||||
self.set_stock_value_difference()
|
||||
|
||||
def get_batch_wise_total_available_qty(self) -> list[dict]:
|
||||
# Get total qty of each batch no from Serial and Batch Bundle without checking time condition
|
||||
if not self.batchwise_valuation_batches:
|
||||
return []
|
||||
|
||||
parent = frappe.qb.DocType("Serial and Batch Bundle")
|
||||
child = frappe.qb.DocType("Serial and Batch Entry")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(parent)
|
||||
.inner_join(child)
|
||||
.on(parent.name == child.parent)
|
||||
.select(
|
||||
child.batch_no,
|
||||
Sum(child.qty).as_("total_qty"),
|
||||
)
|
||||
.where(
|
||||
(parent.warehouse == self.sle.warehouse)
|
||||
& (parent.item_code == self.sle.item_code)
|
||||
& (child.batch_no.isin(self.batchwise_valuation_batches))
|
||||
& (parent.docstatus == 1)
|
||||
& (parent.is_cancelled == 0)
|
||||
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
.for_update()
|
||||
.groupby(child.batch_no)
|
||||
)
|
||||
|
||||
# Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
|
||||
if self.sle.voucher_detail_no:
|
||||
query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no)
|
||||
elif self.sle.voucher_no:
|
||||
query = query.where(parent.voucher_no != self.sle.voucher_no)
|
||||
|
||||
query = query.where(parent.voucher_type != "Pick List")
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def get_batch_no_ledgers(self) -> list[dict]:
|
||||
# Get batch wise stock value difference from Serial and Batch Bundle considering time condition
|
||||
if not self.batchwise_valuation_batches:
|
||||
return []
|
||||
|
||||
@@ -765,11 +807,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
.on(parent.name == child.parent)
|
||||
.select(
|
||||
child.batch_no,
|
||||
Sum(Case().when(timestamp_condition, child.stock_value_difference).else_(0)).as_(
|
||||
"incoming_rate"
|
||||
),
|
||||
Sum(Case().when(timestamp_condition, child.qty).else_(0)).as_("qty"),
|
||||
Sum(child.qty).as_("total_qty"),
|
||||
Sum(child.stock_value_difference).as_("incoming_rate"),
|
||||
Sum(child.qty).as_("qty"),
|
||||
)
|
||||
.where(
|
||||
(parent.warehouse == self.sle.warehouse)
|
||||
@@ -790,6 +829,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
query = query.where(parent.voucher_no != self.sle.voucher_no)
|
||||
|
||||
query = query.where(parent.voucher_type != "Pick List")
|
||||
if timestamp_condition:
|
||||
query = query.where(timestamp_condition)
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
@@ -893,10 +893,19 @@ class update_entries_after:
|
||||
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
|
||||
|
||||
sle.stock_value_difference = stock_value_difference
|
||||
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
|
||||
if (
|
||||
sle.is_adjustment_entry
|
||||
and flt(sle.qty_after_transaction, self.flt_precision) == 0
|
||||
and flt(sle.stock_value, self.currency_precision) != 0
|
||||
):
|
||||
sle.stock_value_difference = (
|
||||
get_stock_value_difference(
|
||||
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
|
||||
sle.item_code,
|
||||
sle.warehouse,
|
||||
sle.posting_date,
|
||||
sle.posting_time,
|
||||
voucher_detail_no=sle.voucher_detail_no,
|
||||
creation=sle.creation,
|
||||
)
|
||||
* -1
|
||||
)
|
||||
@@ -2353,24 +2362,32 @@ def is_internal_transfer(sle):
|
||||
return True
|
||||
|
||||
|
||||
def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None):
|
||||
def get_stock_value_difference(
|
||||
item_code, warehouse, posting_date, posting_time, voucher_no=None, voucher_detail_no=None, creation=None
|
||||
):
|
||||
table = frappe.qb.DocType("Stock Ledger Entry")
|
||||
posting_datetime = get_combine_datetime(posting_date, posting_time)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Sum(table.stock_value_difference).as_("value"))
|
||||
.where(
|
||||
(table.is_cancelled == 0)
|
||||
& (table.item_code == item_code)
|
||||
& (table.warehouse == warehouse)
|
||||
& (table.posting_datetime <= posting_datetime)
|
||||
)
|
||||
.where((table.is_cancelled == 0) & (table.item_code == item_code) & (table.warehouse == warehouse))
|
||||
)
|
||||
|
||||
if voucher_no:
|
||||
if voucher_detail_no:
|
||||
query = query.where(table.voucher_detail_no != voucher_detail_no)
|
||||
|
||||
elif voucher_no:
|
||||
query = query.where(table.voucher_no != voucher_no)
|
||||
|
||||
if creation:
|
||||
query = query.where(
|
||||
(table.posting_datetime < posting_datetime)
|
||||
| ((table.posting_datetime == posting_datetime) & (table.creation < creation))
|
||||
)
|
||||
else:
|
||||
query = query.where(table.posting_datetime <= posting_datetime)
|
||||
|
||||
difference_amount = query.run()
|
||||
return flt(difference_amount[0][0]) if difference_amount else 0
|
||||
|
||||
|
||||
@@ -484,10 +484,16 @@ def get_documents_with_active_service_level_agreement():
|
||||
|
||||
|
||||
def set_documents_with_active_service_level_agreement():
|
||||
active = frozenset(
|
||||
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
|
||||
)
|
||||
frappe.cache.set_value("doctypes_with_active_sla", active)
|
||||
try:
|
||||
active = frozenset(
|
||||
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
|
||||
)
|
||||
frappe.cache.set_value("doctypes_with_active_sla", active)
|
||||
except (frappe.DoesNotExistError, frappe.db.TableMissingError):
|
||||
# This happens during install / uninstall when wildcard hook for SLA intercepts some doc action.
|
||||
# In both cases, the error can be safely ignored.
|
||||
active = frozenset()
|
||||
|
||||
return active
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user