mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-23 19:19:40 +00:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6648eebfa | ||
|
|
faa3c7c3a4 | ||
|
|
5ab5bd138f | ||
|
|
dc3265751c | ||
|
|
cf8f0e4096 | ||
|
|
8c09968e1b | ||
|
|
39885b2b01 | ||
|
|
6a6a6971ba | ||
|
|
13dfbe3d80 | ||
|
|
7e3c15e0b6 | ||
|
|
570985f40e | ||
|
|
eb418e8659 | ||
|
|
014486de39 | ||
|
|
fcb2f4f084 | ||
|
|
308f200793 | ||
|
|
4fcfbe6e9f | ||
|
|
5b2c13dacf | ||
|
|
76b7884fb7 | ||
|
|
518dad8ecb | ||
|
|
220ae118e8 | ||
|
|
f1f2f6e338 | ||
|
|
c16c41ee59 | ||
|
|
dcffb3886b | ||
|
|
7035969db7 | ||
|
|
0cf97f2559 | ||
|
|
459d136368 | ||
|
|
a2bfe31876 | ||
|
|
75b021dc23 | ||
|
|
b2e2e951da | ||
|
|
0f9b5074a6 | ||
|
|
190900cd1b | ||
|
|
3512f7d528 | ||
|
|
0dc2f78a2e | ||
|
|
c834a9de85 | ||
|
|
c910b8ab03 | ||
|
|
b9ebb50a02 | ||
|
|
38cc28a4c3 | ||
|
|
bbb9b9e3b6 | ||
|
|
94c3ee645d | ||
|
|
4d8c35aa5d | ||
|
|
5230d411bf | ||
|
|
a6bf7c1ebd | ||
|
|
50f6afd588 | ||
|
|
649c192abe | ||
|
|
b7d6a54bed | ||
|
|
05e4dae1b8 | ||
|
|
97fdda8a7c | ||
|
|
9802333397 | ||
|
|
82d206b709 | ||
|
|
80810c2ebb | ||
|
|
331a743d69 | ||
|
|
32a1fea8f0 | ||
|
|
dfb4c47089 | ||
|
|
0cbf049608 | ||
|
|
3f0cb47464 | ||
|
|
89d507e07e | ||
|
|
26595351cc | ||
|
|
dfaca93292 | ||
|
|
40de3f3481 | ||
|
|
fc2614612b | ||
|
|
e69e5404d3 | ||
|
|
14ec6351ae | ||
|
|
a8be5f0789 | ||
|
|
42312c5bba | ||
|
|
7b28d7d2b8 | ||
|
|
5366356400 | ||
|
|
7a380f584d | ||
|
|
256d6a47ac | ||
|
|
67e06615a3 | ||
|
|
1078a98cce | ||
|
|
d87ffae03f | ||
|
|
0404941fb2 | ||
|
|
c4dfcbec96 | ||
|
|
c9e7f450c5 | ||
|
|
690278042d | ||
|
|
754e193c76 | ||
|
|
9d5e4b3b3a | ||
|
|
d7709cf4e4 | ||
|
|
4647ec8892 | ||
|
|
339256bc71 | ||
|
|
c3d567b291 | ||
|
|
e068bec212 | ||
|
|
d2ce927891 | ||
|
|
a26ae64385 | ||
|
|
cd33199da2 | ||
|
|
27100401aa | ||
|
|
93b30d9f11 | ||
|
|
904f369e99 | ||
|
|
7b9c22775c | ||
|
|
6aa8d5fb4b | ||
|
|
e82ea12cbc | ||
|
|
d727c52421 | ||
|
|
5ae29655f9 | ||
|
|
81a99309d8 | ||
|
|
9aa054c400 | ||
|
|
82b2675aa8 | ||
|
|
8318286865 | ||
|
|
f87411f40d | ||
|
|
45c4167c86 | ||
|
|
e0d1f2f6eb | ||
|
|
ba45ea42f8 | ||
|
|
58b68b7597 | ||
|
|
4d56c46446 | ||
|
|
eb22fb9326 | ||
|
|
54313b5db9 | ||
|
|
866b0c6ac7 | ||
|
|
1e1319351d | ||
|
|
6b2874694e | ||
|
|
ff37706bef | ||
|
|
949aa9346c | ||
|
|
2f6fee9877 | ||
|
|
abe64aa1ab | ||
|
|
1a7b3c437d | ||
|
|
da69b1e71b | ||
|
|
d160f5b61a | ||
|
|
3fcdcef178 | ||
|
|
40ece3f5da | ||
|
|
c93840eb56 | ||
|
|
7656fe4bfc | ||
|
|
59010c9a61 | ||
|
|
113351e850 | ||
|
|
2026c986ba | ||
|
|
91c202f172 | ||
|
|
7e52f72bed | ||
|
|
b9d28fc1ad | ||
|
|
33d38ba3a7 | ||
|
|
ff8dba1cb7 | ||
|
|
c3244f009b | ||
|
|
30b2cac423 | ||
|
|
67be2ba9dc | ||
|
|
3e81f0f578 | ||
|
|
bb48440591 | ||
|
|
35230a9692 | ||
|
|
df3ff1097c | ||
|
|
521bf31fe3 | ||
|
|
8868cb147d | ||
|
|
0dfae12f14 | ||
|
|
0df80ad923 | ||
|
|
c71d1118a9 | ||
|
|
6017e7ac3e | ||
|
|
d5784cc629 | ||
|
|
a2e1d132df | ||
|
|
3e912993e1 | ||
|
|
5aef9d2ef2 | ||
|
|
f1b75e8c54 | ||
|
|
5b1c5e3fea | ||
|
|
f087ec8df5 | ||
|
|
ec4f07fd60 | ||
|
|
a22be6f9b9 | ||
|
|
e8accd0256 | ||
|
|
de3a423618 | ||
|
|
03ce9ee321 | ||
|
|
9d0c1dc46f | ||
|
|
5d05bf8d4e | ||
|
|
819ced4cb3 |
@@ -69,12 +69,14 @@ repos:
|
||||
rev: v0.2.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: "Run ruff linter and apply fixes"
|
||||
args: ["--fix"]
|
||||
name: "Run ruff import sorter"
|
||||
args: ["--select=I", "--fix"]
|
||||
|
||||
- id: ruff
|
||||
name: "Run ruff linter"
|
||||
|
||||
- id: ruff-format
|
||||
name: "Format Python code"
|
||||
|
||||
name: "Run ruff formatter"
|
||||
|
||||
ci:
|
||||
autoupdate_schedule: weekly
|
||||
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.67.2"
|
||||
__version__ = "14.70.4"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -194,12 +194,18 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
def validate_budget_records(args, budget_records, expense_amount):
|
||||
for budget in budget_records:
|
||||
if flt(budget.budget_amount):
|
||||
amount = expense_amount or get_amount(args, budget)
|
||||
yearly_action, monthly_action = get_actions(args, budget)
|
||||
args["for_material_request"] = budget.for_material_request
|
||||
args["for_purchase_order"] = budget.for_purchase_order
|
||||
|
||||
if yearly_action in ("Stop", "Warn"):
|
||||
compare_expense_with_budget(
|
||||
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
|
||||
args,
|
||||
flt(budget.budget_amount),
|
||||
_("Annual"),
|
||||
yearly_action,
|
||||
budget.budget_against,
|
||||
expense_amount,
|
||||
)
|
||||
|
||||
if monthly_action in ["Stop", "Warn"]:
|
||||
@@ -215,18 +221,27 @@ def validate_budget_records(args, budget_records, expense_amount):
|
||||
_("Accumulated Monthly"),
|
||||
monthly_action,
|
||||
budget.budget_against,
|
||||
amount,
|
||||
expense_amount,
|
||||
)
|
||||
|
||||
|
||||
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
|
||||
actual_expense = get_actual_expense(args)
|
||||
total_expense = actual_expense + amount
|
||||
args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0
|
||||
if not amount:
|
||||
args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args)
|
||||
|
||||
if args.get("doctype") == "Material Request" and args.for_material_request:
|
||||
amount = args.requested_amount + args.ordered_amount
|
||||
|
||||
elif args.get("doctype") == "Purchase Order" and args.for_purchase_order:
|
||||
amount = args.ordered_amount
|
||||
|
||||
total_expense = args.actual_expense + amount
|
||||
|
||||
if total_expense > budget_amount:
|
||||
if actual_expense > budget_amount:
|
||||
if args.actual_expense > budget_amount:
|
||||
error_tense = _("is already")
|
||||
diff = actual_expense - budget_amount
|
||||
diff = args.actual_expense - budget_amount
|
||||
else:
|
||||
error_tense = _("will be")
|
||||
diff = total_expense - budget_amount
|
||||
@@ -243,6 +258,8 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
|
||||
frappe.bold(fmt_money(diff, currency=currency)),
|
||||
)
|
||||
|
||||
msg += get_expense_breakup(args, currency, budget_against)
|
||||
|
||||
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
|
||||
frappe.session.user
|
||||
):
|
||||
@@ -254,6 +271,83 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
|
||||
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
|
||||
|
||||
|
||||
def get_expense_breakup(args, currency, budget_against):
|
||||
msg = "<hr>Total Expenses booked through - <ul>"
|
||||
|
||||
common_filters = frappe._dict(
|
||||
{
|
||||
args.budget_against_field: budget_against,
|
||||
"account": args.account,
|
||||
"company": args.company,
|
||||
}
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"General Ledger",
|
||||
label="Actual Expenses",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"),
|
||||
"to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"),
|
||||
"is_cancelled": 0,
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.actual_expense, currency=currency))
|
||||
+ "</li>"
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"Material Request",
|
||||
label="Material Requests",
|
||||
report_type="Report Builder",
|
||||
doctype="Material Request",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"status": [["!=", "Stopped"]],
|
||||
"docstatus": 1,
|
||||
"material_request_type": "Purchase",
|
||||
"schedule_date": [["fiscal year", "2023-2024"]],
|
||||
"item_code": args.item_code,
|
||||
"per_ordered": [["<", 100]],
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.requested_amount, currency=currency))
|
||||
+ "</li>"
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"Purchase Order",
|
||||
label="Unbilled Orders",
|
||||
report_type="Report Builder",
|
||||
doctype="Purchase Order",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"status": [["!=", "Closed"]],
|
||||
"docstatus": 1,
|
||||
"transaction_date": [["fiscal year", "2023-2024"]],
|
||||
"item_code": args.item_code,
|
||||
"per_billed": [["<", 100]],
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.ordered_amount, currency=currency))
|
||||
+ "</li></ul>"
|
||||
)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def get_actions(args, budget):
|
||||
yearly_action = budget.action_if_annual_budget_exceeded
|
||||
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
|
||||
@@ -269,23 +363,9 @@ def get_actions(args, budget):
|
||||
return yearly_action, monthly_action
|
||||
|
||||
|
||||
def get_amount(args, budget):
|
||||
amount = 0
|
||||
|
||||
if args.get("doctype") == "Material Request" and budget.for_material_request:
|
||||
amount = (
|
||||
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
|
||||
)
|
||||
|
||||
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
|
||||
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
def get_requested_amount(args, budget):
|
||||
def get_requested_amount(args):
|
||||
item_code = args.get("item_code")
|
||||
condition = get_other_condition(args, budget, "Material Request")
|
||||
condition = get_other_condition(args, "Material Request")
|
||||
|
||||
data = frappe.db.sql(
|
||||
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
|
||||
@@ -299,9 +379,9 @@ def get_requested_amount(args, budget):
|
||||
return data[0][0] if data else 0
|
||||
|
||||
|
||||
def get_ordered_amount(args, budget):
|
||||
def get_ordered_amount(args):
|
||||
item_code = args.get("item_code")
|
||||
condition = get_other_condition(args, budget, "Purchase Order")
|
||||
condition = get_other_condition(args, "Purchase Order")
|
||||
|
||||
data = frappe.db.sql(
|
||||
f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
|
||||
@@ -315,7 +395,7 @@ def get_ordered_amount(args, budget):
|
||||
return data[0][0] if data else 0
|
||||
|
||||
|
||||
def get_other_condition(args, budget, for_doc):
|
||||
def get_other_condition(args, for_doc):
|
||||
condition = "expense_account = '%s'" % (args.expense_account)
|
||||
budget_against_field = args.get("budget_against_field")
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-31 13:22:58.916273",
|
||||
"modified": "2024-04-24 10:55:54.083042",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cost Center",
|
||||
@@ -163,6 +163,15 @@
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "parent_cost_center, is_group",
|
||||
|
||||
@@ -118,9 +118,17 @@
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Employee"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Stock Manager"
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,7 +1088,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value(field, r.message.account);
|
||||
if (!frm.doc.mode_of_payment) {
|
||||
frm.set_value(field, r.message.account);
|
||||
}
|
||||
frm.set_value('bank', r.message.bank);
|
||||
frm.set_value('bank_account_no', r.message.bank_account_no);
|
||||
}
|
||||
@@ -1393,8 +1395,9 @@ frappe.ui.form.on('Payment Entry Reference', {
|
||||
args: {
|
||||
reference_doctype: row.reference_doctype,
|
||||
reference_name: row.reference_name,
|
||||
party_account_currency: frm.doc.payment_type=="Receive" ?
|
||||
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency
|
||||
party_account_currency: frm.doc.payment_type == "Receive" ? frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency,
|
||||
party_type: frm.doc.party_type,
|
||||
party: frm.doc.party,
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
|
||||
@@ -9,8 +9,7 @@ import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||
from frappe.utils.data import comma_and, fmt_money
|
||||
from pypika import Case
|
||||
from pypika.functions import Coalesce, Sum
|
||||
from pypika.functions import Sum
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||
@@ -348,7 +347,11 @@ class PaymentEntry(AccountsController):
|
||||
continue
|
||||
|
||||
ref_details = get_reference_details(
|
||||
d.reference_doctype, d.reference_name, self.party_account_currency
|
||||
d.reference_doctype,
|
||||
d.reference_name,
|
||||
self.party_account_currency,
|
||||
self.party_type,
|
||||
self.party,
|
||||
)
|
||||
|
||||
# Only update exchange rate when the reference is Journal Entry
|
||||
@@ -1883,33 +1886,42 @@ def get_company_defaults(company):
|
||||
return frappe.get_cached_value("Company", company, fields, as_dict=1)
|
||||
|
||||
|
||||
def get_outstanding_on_journal_entry(name):
|
||||
gl = frappe.qb.DocType("GL Entry")
|
||||
res = (
|
||||
frappe.qb.from_(gl)
|
||||
.select(
|
||||
Case()
|
||||
.when(
|
||||
gl.party_type == "Customer",
|
||||
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
|
||||
)
|
||||
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
|
||||
.as_("outstanding_amount")
|
||||
)
|
||||
def get_outstanding_on_journal_entry(voucher_no, party_type, party):
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
|
||||
outstanding = (
|
||||
frappe.qb.from_(ple)
|
||||
.select(Sum(ple.amount_in_account_currency))
|
||||
.where(
|
||||
(Coalesce(gl.party_type, "") != "")
|
||||
& (gl.is_cancelled == 0)
|
||||
& ((gl.voucher_no == name) | (gl.against_voucher == name))
|
||||
(ple.against_voucher_no == voucher_no)
|
||||
& (ple.party_type == party_type)
|
||||
& (ple.party == party)
|
||||
& (ple.delinked == 0)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
).run()
|
||||
|
||||
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
|
||||
outstanding_amount = outstanding[0][0] if outstanding else 0
|
||||
|
||||
return outstanding_amount
|
||||
total = (
|
||||
frappe.qb.from_(ple)
|
||||
.select(Sum(ple.amount_in_account_currency))
|
||||
.where(
|
||||
(ple.voucher_no == voucher_no)
|
||||
& (ple.party_type == party_type)
|
||||
& (ple.party == party)
|
||||
& (ple.delinked == 0)
|
||||
)
|
||||
).run()
|
||||
|
||||
total_amount = total[0][0] if total else 0
|
||||
|
||||
return outstanding_amount, total_amount
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_reference_details(reference_doctype, reference_name, party_account_currency):
|
||||
def get_reference_details(
|
||||
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
|
||||
):
|
||||
total_amount = outstanding_amount = exchange_rate = None
|
||||
|
||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
@@ -1920,12 +1932,13 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
exchange_rate = 1
|
||||
|
||||
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
|
||||
total_amount = ref_doc.get("total_amount")
|
||||
if ref_doc.multi_currency:
|
||||
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||
else:
|
||||
exchange_rate = 1
|
||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||
outstanding_amount, total_amount = get_outstanding_on_journal_entry(
|
||||
reference_name, party_type, party
|
||||
)
|
||||
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if not total_amount:
|
||||
|
||||
@@ -1087,7 +1087,9 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
pe.source_exchange_rate = 50
|
||||
pe.save()
|
||||
|
||||
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
|
||||
ref_details = get_reference_details(
|
||||
so.doctype, so.name, pe.paid_from_account_currency, "Customer", so.customer
|
||||
)
|
||||
expected_response = {
|
||||
"total_amount": 5000.0,
|
||||
"outstanding_amount": 5000.0,
|
||||
|
||||
@@ -71,6 +71,7 @@ frappe.ui.form.on("Payment Order", {
|
||||
target: frm,
|
||||
date_field: "posting_date",
|
||||
setters: {
|
||||
party_type: "Supplier",
|
||||
party: frm.doc.supplier || "",
|
||||
},
|
||||
get_query_filters: {
|
||||
@@ -91,6 +92,7 @@ frappe.ui.form.on("Payment Order", {
|
||||
source_doctype: "Payment Request",
|
||||
target: frm,
|
||||
setters: {
|
||||
party_type: "Supplier",
|
||||
party: frm.doc.supplier || "",
|
||||
},
|
||||
get_query_filters: {
|
||||
|
||||
@@ -37,7 +37,7 @@ class PaymentRequest(Document):
|
||||
self.status = "Draft"
|
||||
self.validate_reference_document()
|
||||
self.validate_payment_request_amount()
|
||||
self.validate_currency()
|
||||
# self.validate_currency()
|
||||
self.validate_subscription_details()
|
||||
|
||||
def validate_reference_document(self):
|
||||
@@ -276,21 +276,17 @@ class PaymentRequest(Document):
|
||||
}
|
||||
)
|
||||
|
||||
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
|
||||
amount = payment_entry.base_paid_amount
|
||||
else:
|
||||
amount = self.grand_total
|
||||
|
||||
payment_entry.received_amount = amount
|
||||
payment_entry.get("references")[0].allocated_amount = amount
|
||||
|
||||
for dimension in get_accounting_dimensions():
|
||||
payment_entry.update({dimension: self.get(dimension)})
|
||||
|
||||
if payment_entry.difference_amount:
|
||||
company_details = get_company_defaults(ref_doc.company)
|
||||
|
||||
payment_entry.append(
|
||||
"deductions",
|
||||
{
|
||||
"account": company_details.exchange_gain_loss_account,
|
||||
"cost_center": company_details.cost_center,
|
||||
"amount": payment_entry.difference_amount,
|
||||
},
|
||||
)
|
||||
|
||||
if submit:
|
||||
payment_entry.insert(ignore_permissions=True)
|
||||
payment_entry.submit()
|
||||
@@ -432,6 +428,12 @@ def make_payment_request(**args):
|
||||
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||
else:
|
||||
pr = frappe.new_doc("Payment Request")
|
||||
|
||||
if not args.get("payment_request_type"):
|
||||
args["payment_request_type"] = (
|
||||
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
|
||||
)
|
||||
|
||||
pr.update(
|
||||
{
|
||||
"payment_gateway_account": gateway_account.get("name"),
|
||||
@@ -490,9 +492,9 @@ def get_amount(ref_doc, payment_account=None):
|
||||
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if not ref_doc.get("is_pos"):
|
||||
if ref_doc.party_account_currency == ref_doc.currency:
|
||||
grand_total = flt(ref_doc.outstanding_amount)
|
||||
grand_total = flt(ref_doc.grand_total)
|
||||
else:
|
||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||
grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate
|
||||
elif dt == "Sales Invoice":
|
||||
for pay in ref_doc.payments:
|
||||
if pay.type == "Phone" and pay.account == payment_account:
|
||||
@@ -594,7 +596,11 @@ def update_payment_req_status(doc, method):
|
||||
|
||||
if payment_request_name:
|
||||
ref_details = get_reference_details(
|
||||
ref.reference_doctype, ref.reference_name, doc.party_account_currency
|
||||
ref.reference_doctype,
|
||||
ref.reference_name,
|
||||
doc.party_account_currency,
|
||||
doc.party_type,
|
||||
doc.party,
|
||||
)
|
||||
pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
|
||||
status = pay_req_doc.status
|
||||
|
||||
@@ -86,6 +86,8 @@ class TestPaymentRequest(unittest.TestCase):
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Invoice",
|
||||
dn=si_usd.name,
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier USD",
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
@@ -98,6 +100,51 @@ class TestPaymentRequest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(pr.status, "Paid")
|
||||
|
||||
def test_multiple_payment_entry_against_purchase_invoice(self):
|
||||
purchase_invoice = make_purchase_invoice(
|
||||
customer="_Test Supplier USD",
|
||||
debit_to="_Test Payable USD - _TC",
|
||||
currency="USD",
|
||||
conversion_rate=50,
|
||||
)
|
||||
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Invoice",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier USD",
|
||||
dn=purchase_invoice.name,
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
return_doc=1,
|
||||
)
|
||||
|
||||
pr.grand_total = pr.grand_total / 2
|
||||
|
||||
pr.submit()
|
||||
pr.create_payment_entry()
|
||||
|
||||
purchase_invoice.load_from_db()
|
||||
self.assertEqual(purchase_invoice.status, "Partly Paid")
|
||||
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Invoice",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier USD",
|
||||
dn=purchase_invoice.name,
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
return_doc=1,
|
||||
)
|
||||
|
||||
pr.save()
|
||||
pr.submit()
|
||||
pr.create_payment_entry()
|
||||
|
||||
purchase_invoice.load_from_db()
|
||||
self.assertEqual(purchase_invoice.status, "Paid")
|
||||
|
||||
def test_payment_entry(self):
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
|
||||
|
||||
@@ -33,7 +33,7 @@ class POSClosingEntry(StatusUpdater):
|
||||
for key, value in pos_occurences.items():
|
||||
if len(value) > 1:
|
||||
error_list.append(
|
||||
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
|
||||
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
|
||||
)
|
||||
|
||||
if error_list:
|
||||
|
||||
@@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document):
|
||||
for key, value in pos_occurences.items():
|
||||
if len(value) > 1:
|
||||
error_list.append(
|
||||
_(f"{frappe.bold(key)} is added multiple times on rows: {frappe.bold(value)}")
|
||||
_("{0} is added multiple times on rows: {1}").format(frappe.bold(key), frappe.bold(value))
|
||||
)
|
||||
|
||||
if error_list:
|
||||
@@ -441,7 +441,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status="Failed")
|
||||
if isinstance(error_message, list):
|
||||
error_message = frappe.json.dumps(error_message)
|
||||
error_message = json.dumps(error_message)
|
||||
closing_entry.db_set("error_message", error_message)
|
||||
raise
|
||||
|
||||
|
||||
@@ -74,15 +74,21 @@
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"for_price_list",
|
||||
"section_break_13",
|
||||
"threshold_percentage",
|
||||
"priority",
|
||||
"dynamic_condition_tab",
|
||||
"condition",
|
||||
"column_break_66",
|
||||
"section_break_13",
|
||||
"apply_multiple_pricing_rules",
|
||||
"apply_discount_on_rate",
|
||||
"column_break_66",
|
||||
"threshold_percentage",
|
||||
"validate_pricing_rule_section",
|
||||
"validate_applied_rule",
|
||||
"column_break_texp",
|
||||
"rule_description",
|
||||
"priority_section",
|
||||
"has_priority",
|
||||
"column_break_sayg",
|
||||
"priority",
|
||||
"help_section",
|
||||
"pricing_rule_help",
|
||||
"reference_section",
|
||||
@@ -477,7 +483,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Advanced Settings"
|
||||
},
|
||||
{
|
||||
@@ -487,6 +493,7 @@
|
||||
"label": "Threshold for Suggestion (In Percentage)"
|
||||
},
|
||||
{
|
||||
"depends_on": "has_priority",
|
||||
"description": "Higher the number, higher the priority",
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Select",
|
||||
@@ -513,6 +520,7 @@
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.price_or_product_discount == 'Price'",
|
||||
"description": "If enabled, then system will only validate the pricing rule and not apply automatically. User has to manually set the discount percentage / margin / free items to validate the pricing rule",
|
||||
"fieldname": "validate_applied_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Applied Rule"
|
||||
@@ -525,7 +533,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "help_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Help Article",
|
||||
"options": "Simple"
|
||||
},
|
||||
{
|
||||
@@ -603,12 +612,42 @@
|
||||
"fieldname": "apply_recursion_over",
|
||||
"fieldtype": "Float",
|
||||
"label": "Apply Recursion Over (As Per Transaction UOM)"
|
||||
},
|
||||
{
|
||||
"fieldname": "priority_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Priority"
|
||||
},
|
||||
{
|
||||
"fieldname": "dynamic_condition_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Dynamic Condition"
|
||||
},
|
||||
{
|
||||
"fieldname": "validate_pricing_rule_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Validate Pricing Rule"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_texp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sayg",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enable this checkbox even if you want to set the zero priority",
|
||||
"fieldname": "has_priority",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Priority"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-14 04:53:34.887358",
|
||||
"modified": "2024-05-17 13:16:34.496704",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -47,6 +47,12 @@ class PricingRule(Document):
|
||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||
|
||||
def validate_mandatory(self):
|
||||
if self.has_priority and not self.priority:
|
||||
throw(_("Priority is mandatory"), frappe.MandatoryError, _("Please Set Priority"))
|
||||
|
||||
if self.priority and not self.has_priority:
|
||||
self.has_priority = 1
|
||||
|
||||
for apply_on, field in apply_on_dict.items():
|
||||
if self.apply_on == apply_on and len(self.get(field) or []) < 1:
|
||||
throw(_("{0} is not added in the table").format(apply_on), frappe.MandatoryError)
|
||||
@@ -486,6 +492,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||
# Apply discount on discounted rate
|
||||
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
|
||||
elif args.price_list_rate:
|
||||
value = pricing_rule.get(field, 0)
|
||||
calculate_discount_percentage = False
|
||||
if field == "discount_percentage":
|
||||
field = "discount_amount"
|
||||
value = args.price_list_rate * (value / 100)
|
||||
calculate_discount_percentage = True
|
||||
|
||||
if field not in item_details:
|
||||
item_details.setdefault(field, 0)
|
||||
|
||||
item_details[field] += value if pricing_rule else args.get(field, 0)
|
||||
if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount:
|
||||
item_details.discount_percentage = flt(
|
||||
(flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100
|
||||
)
|
||||
else:
|
||||
if field not in item_details:
|
||||
item_details.setdefault(field, 0)
|
||||
|
||||
@@ -976,7 +976,116 @@ class TestPricingRule(unittest.TestCase):
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 4)
|
||||
self.assertEqual(so.items[1].qty, 3)
|
||||
|
||||
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule 1",
|
||||
"name": "_Test Pricing Rule 1",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"price_or_product_discount": "Price",
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"discount_percentage": 10,
|
||||
"apply_multiple_pricing_rules": 1,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule 2",
|
||||
"name": "_Test Pricing Rule 2",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"price_or_product_discount": "Price",
|
||||
"rate_or_discount": "Discount Amount",
|
||||
"discount_amount": 100,
|
||||
"apply_multiple_pricing_rules": 1,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
|
||||
self.assertEqual(so.items[0].discount_amount, 200)
|
||||
self.assertEqual(so.items[0].rate, 800)
|
||||
|
||||
frappe.delete_doc_if_exists("Sales Order", so.name)
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||
|
||||
def test_priority_of_multiple_pricing_rules(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule 1",
|
||||
"name": "_Test Pricing Rule 1",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"price_or_product_discount": "Price",
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"discount_percentage": 10,
|
||||
"has_priority": 1,
|
||||
"priority": 1,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule 2",
|
||||
"name": "_Test Pricing Rule 2",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"price_or_product_discount": "Price",
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"discount_percentage": 20,
|
||||
"has_priority": 1,
|
||||
"priority": 3,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
|
||||
self.assertEqual(so.items[0].discount_percentage, 20)
|
||||
self.assertEqual(so.items[0].rate, 800)
|
||||
|
||||
frappe.delete_doc_if_exists("Sales Order", so.name)
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
@@ -1006,6 +1115,7 @@ def make_pricing_rule(**args):
|
||||
"priority": args.priority or 1,
|
||||
"discount_amount": args.discount_amount or 0.0,
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
|
||||
"has_priority": args.has_priority or 0,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
import math
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
@@ -32,6 +33,9 @@ def get_pricing_rules(args, doc=None):
|
||||
|
||||
for apply_on in ["Item Code", "Item Group", "Brand"]:
|
||||
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
|
||||
if pricing_rules and pricing_rules[0].has_priority:
|
||||
continue
|
||||
|
||||
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
|
||||
break
|
||||
|
||||
@@ -638,7 +642,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||
if pricing_rule.round_free_qty:
|
||||
qty = round(qty)
|
||||
qty = math.floor(qty)
|
||||
|
||||
free_item_data_args = {
|
||||
"item_code": free_item,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
@@ -479,7 +481,7 @@ def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
|
||||
running_doc = None
|
||||
if for_filter:
|
||||
if isinstance(for_filter, str):
|
||||
for_filter = frappe.json.loads(for_filter)
|
||||
for_filter = json.loads(for_filter)
|
||||
|
||||
running_doc = frappe.db.get_value(
|
||||
"Process Payment Reconciliation",
|
||||
|
||||
@@ -104,7 +104,7 @@ def set_ageing(doc, entry):
|
||||
ageing_filters = frappe._dict(
|
||||
{
|
||||
"company": doc.company,
|
||||
"report_date": doc.to_date,
|
||||
"report_date": doc.posting_date,
|
||||
"ageing_based_on": doc.ageing_based_on,
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
|
||||
@@ -340,10 +340,11 @@
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%">30 Days</th>
|
||||
<th style="width: 25%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 25%">120 Days</th>
|
||||
<th style="width: 25%">0 - 30 Days</th>
|
||||
<th style="width: 25%">30 - 60 Days</th>
|
||||
<th style="width: 25%">60 - 90 Days</th>
|
||||
<th style="width: 25%">90 - 120 Days</th>
|
||||
<th style="width: 20%">Above 120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -352,6 +353,7 @@
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
{
|
||||
"fieldname": "cost_center_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
"options": "Cost Center",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 16:56:45.744905",
|
||||
"modified": "2024-05-03 17:16:51.666461",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "PSOA Cost Center",
|
||||
@@ -27,4 +29,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,8 +440,12 @@ function hide_fields(doc) {
|
||||
|
||||
var item_fields_stock = ['warehouse_section', 'received_qty', 'rejected_qty'];
|
||||
|
||||
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
|
||||
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
|
||||
if (cur_frm.fields_dict["items"]) {
|
||||
cur_frm.fields_dict["items"].grid.set_column_disp(
|
||||
item_fields_stock,
|
||||
cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false
|
||||
);
|
||||
}
|
||||
|
||||
cur_frm.refresh_fields();
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ class PurchaseInvoice(BuyingController):
|
||||
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
asset_received_but_not_billed = None
|
||||
self.asset_received_but_not_billed = None
|
||||
|
||||
if self.update_stock:
|
||||
self.validate_item_code()
|
||||
@@ -367,26 +367,45 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif item.is_fixed_asset:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
asset_category_account = get_asset_category_account(
|
||||
account_type, item=item.item_code, company=self.company
|
||||
)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account"),
|
||||
account = None
|
||||
if item.pr_detail:
|
||||
if not self.asset_received_but_not_billed:
|
||||
self.asset_received_but_not_billed = self.get_company_default(
|
||||
"asset_received_but_not_billed"
|
||||
)
|
||||
|
||||
# check if 'Asset Received But Not Billed' account is credited in Purchase receipt or not
|
||||
arbnb_booked_in_pr = frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": item.purchase_receipt,
|
||||
"account": self.asset_received_but_not_billed,
|
||||
},
|
||||
"name",
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
if arbnb_booked_in_pr:
|
||||
account = self.asset_received_but_not_billed
|
||||
|
||||
if not account:
|
||||
account_type = (
|
||||
"capital_work_in_progress_account"
|
||||
if is_cwip_accounting_enabled(item.asset_category)
|
||||
else "fixed_asset_account"
|
||||
)
|
||||
account = get_asset_category_account(
|
||||
account_type, item=item.item_code, company=self.company
|
||||
)
|
||||
if not account:
|
||||
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(
|
||||
form_link, self.company
|
||||
),
|
||||
title=_("Missing Account"),
|
||||
)
|
||||
item.expense_account = account
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
|
||||
@@ -982,7 +1001,7 @@ class PurchaseInvoice(BuyingController):
|
||||
pr_items = frappe.get_all(
|
||||
"Purchase Receipt Item",
|
||||
filters={"parent": ("in", linked_purchase_receipts)},
|
||||
fields=["name", "provisional_expense_account", "qty", "base_rate"],
|
||||
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
|
||||
)
|
||||
default_provisional_account = self.get_company_default("default_provisional_account")
|
||||
provisional_accounts = set(
|
||||
@@ -1010,6 +1029,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"provisional_account": item.provisional_expense_account or default_provisional_account,
|
||||
"qty": item.qty,
|
||||
"base_rate": item.base_rate,
|
||||
"rate": item.rate,
|
||||
"has_provisional_entry": item.name in rows_with_provisional_entries,
|
||||
}
|
||||
|
||||
@@ -1026,7 +1046,10 @@ class PurchaseInvoice(BuyingController):
|
||||
self.posting_date,
|
||||
pr_item.get("provisional_account"),
|
||||
reverse=1,
|
||||
item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
|
||||
item_amount=(
|
||||
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
|
||||
* purchase_receipt_doc.get("conversion_rate")
|
||||
),
|
||||
)
|
||||
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:ACC-REPOST-{#####}",
|
||||
"creation": "2023-07-04 13:07:32.923675",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
@@ -55,14 +53,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-26 14:21:27.362567",
|
||||
"modified": "2024-06-03 17:30:37.012593",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -71,7 +70,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2022-10-19 21:59:33.553852",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -99,13 +98,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-26 14:21:35.719727",
|
||||
"modified": "2024-06-03 17:31:04.472279",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Payment Ledger",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -114,7 +115,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2030,7 +2030,7 @@
|
||||
{
|
||||
"fieldname": "contact_and_address_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Contact & Address"
|
||||
"label": "Address & Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
@@ -2184,7 +2184,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-22 17:50:34.395602",
|
||||
"modified": "2024-05-08 18:02:28.549041",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
@@ -2978,10 +2979,8 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
["2021-06-30", 20000.0, 21366.12, True],
|
||||
["2022-06-30", 20000.0, 41366.12, False],
|
||||
["2023-06-30", 20000.0, 61366.12, False],
|
||||
["2024-06-30", 20000.0, 81366.12, False],
|
||||
["2025-06-06", 18633.88, 100000.0, False],
|
||||
["2024-06-06", 38633.88, 100000.0, False],
|
||||
]
|
||||
|
||||
for i, schedule in enumerate(asset.schedules):
|
||||
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
|
||||
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
|
||||
@@ -3479,9 +3478,9 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
map_docs(
|
||||
method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
||||
source_names=frappe.json.dumps([dn1.name, dn2.name]),
|
||||
source_names=json.dumps([dn1.name, dn2.name]),
|
||||
target_doc=si,
|
||||
args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
|
||||
args=json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
|
||||
)
|
||||
si.save().submit()
|
||||
|
||||
|
||||
@@ -867,7 +867,8 @@
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_92",
|
||||
@@ -892,7 +893,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:34:10.479329",
|
||||
"modified": "2024-05-23 16:36:18.970862",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
@@ -53,7 +53,7 @@
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
@@ -87,7 +87,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-13 18:44:25.055382",
|
||||
"modified": "2024-04-30 10:26:48.21829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Account",
|
||||
|
||||
@@ -253,6 +253,14 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
if taxable_vouchers:
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
|
||||
|
||||
# If advance is outside the current tax withholding period (usually a fiscal year), `get_deducted_tax` won't fetch it.
|
||||
# updating `tax_deducted` with correct advance tax value (from current and previous previous withholding periods), will allow the
|
||||
# rest of the below logic to function properly
|
||||
# ---FY 2023-------------||---------------------FY 2024-----------------------||--
|
||||
# ---Advance-------------||---------Inv_1--------Inv_2------------------------||--
|
||||
if tax_deducted_on_advances:
|
||||
tax_deducted += get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details)
|
||||
|
||||
tax_amount = 0
|
||||
|
||||
if party_type == "Supplier":
|
||||
@@ -389,7 +397,7 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||
frappe.qb.from_(at)
|
||||
.inner_join(pe)
|
||||
.on(pe.name == at.parent)
|
||||
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
|
||||
.select(pe.posting_date, at.parent, at.name, at.tax_amount, at.allocated_amount)
|
||||
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
|
||||
.where(at.parent.isin(advances))
|
||||
.where(at.account_head == tax_details.account_head)
|
||||
@@ -414,6 +422,16 @@ def get_deducted_tax(taxable_vouchers, tax_details):
|
||||
return sum(entries)
|
||||
|
||||
|
||||
def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
|
||||
"""
|
||||
Only applies for Taxes deducted on Advance Payments
|
||||
"""
|
||||
advance_tax_from_across_fiscal_year = sum(
|
||||
[adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date]
|
||||
)
|
||||
return advance_tax_from_across_fiscal_year
|
||||
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import today
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
|
||||
|
||||
test_dependencies = ["Supplier Group", "Customer Group"]
|
||||
|
||||
|
||||
class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
class TestTaxWithholdingCategory(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
# create relevant supplier, etc
|
||||
@@ -22,7 +25,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
make_pan_no_field()
|
||||
|
||||
def tearDown(self):
|
||||
cancel_invoices()
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_cumulative_threshold_tds(self):
|
||||
frappe.db.set_value(
|
||||
@@ -322,8 +325,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
d.cancel()
|
||||
|
||||
def test_tds_deduction_for_po_via_payment_entry(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||
)
|
||||
@@ -490,6 +491,133 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
pi2.cancel()
|
||||
pi3.cancel()
|
||||
|
||||
def set_previous_fy_and_tax_category(self):
|
||||
test_company = "_Test Company"
|
||||
category = "Cumulative Threshold TDS"
|
||||
|
||||
def add_company_to_fy(fy, company):
|
||||
if not [x.company for x in fy.companies if x.company == company]:
|
||||
fy.append("companies", {"company": company})
|
||||
fy.save()
|
||||
|
||||
# setup previous fiscal year
|
||||
fiscal_year = get_fiscal_year(today(), company=test_company)
|
||||
if prev_fiscal_year := get_fiscal_year(add_days(fiscal_year[1], -10)):
|
||||
self.prev_fy = frappe.get_doc("Fiscal Year", prev_fiscal_year[0])
|
||||
add_company_to_fy(self.prev_fy, test_company)
|
||||
else:
|
||||
# make previous fiscal year
|
||||
start = datetime.date(fiscal_year[1].year - 1, fiscal_year[1].month, fiscal_year[1].day)
|
||||
end = datetime.date(fiscal_year[2].year - 1, fiscal_year[2].month, fiscal_year[2].day)
|
||||
self.prev_fy = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year_start_date": start,
|
||||
"year_end_date": end,
|
||||
"companies": [{"company": test_company}],
|
||||
}
|
||||
)
|
||||
self.prev_fy.save()
|
||||
|
||||
# setup tax withholding category for previous fiscal year
|
||||
cat = frappe.get_doc("Tax Withholding Category", category)
|
||||
cat.append(
|
||||
"rates",
|
||||
{
|
||||
"from_date": self.prev_fy.year_start_date,
|
||||
"to_date": self.prev_fy.year_end_date,
|
||||
"tax_withholding_rate": 10,
|
||||
"single_threshold": 0,
|
||||
"cumulative_threshold": 30000,
|
||||
},
|
||||
)
|
||||
cat.save()
|
||||
|
||||
def test_tds_across_fiscal_year(self):
|
||||
"""
|
||||
Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year
|
||||
--||-----FY 2023-----||-----FY 2024-----||--
|
||||
--||-----Advance-----||---Inv1---Inv2---||--
|
||||
"""
|
||||
self.set_previous_fy_and_tax_category()
|
||||
supplier = "Test TDS Supplier"
|
||||
# Cumulative threshold 30000 and tax rate 10%
|
||||
category = "Cumulative Threshold TDS"
|
||||
frappe.db.set_value(
|
||||
"Supplier",
|
||||
supplier,
|
||||
{
|
||||
"tax_withholding_category": category,
|
||||
"pan": "ABCTY1234D",
|
||||
},
|
||||
)
|
||||
po_and_advance_posting_date = add_days(self.prev_fy.year_end_date, -10)
|
||||
po = create_purchase_order(supplier=supplier, qty=10, rate=10000)
|
||||
po.transaction_date = po_and_advance_posting_date
|
||||
po.taxes = []
|
||||
po.apply_tds = False
|
||||
po.tax_withholding_category = None
|
||||
po.save().submit()
|
||||
|
||||
# Partial advance
|
||||
payment = get_payment_entry(po.doctype, po.name)
|
||||
payment.posting_date = po_and_advance_posting_date
|
||||
payment.paid_amount = 60000
|
||||
payment.apply_tax_withholding_amount = 1
|
||||
payment.tax_withholding_category = category
|
||||
payment.references = []
|
||||
payment.taxes = []
|
||||
payment.save().submit()
|
||||
|
||||
self.assertEqual(len(payment.taxes), 1)
|
||||
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||
|
||||
# Multiple partial invoices
|
||||
payment.reload()
|
||||
pi1 = make_purchase_invoice(source_name=po.name)
|
||||
pi1.apply_tds = True
|
||||
pi1.tax_withholding_category = category
|
||||
pi1.items[0].qty = 3
|
||||
pi1.items[0].rate = 10000
|
||||
advances = pi1.get_advance_entries()
|
||||
pi1.append(
|
||||
"advances",
|
||||
{
|
||||
"reference_type": advances[0].reference_type,
|
||||
"reference_name": advances[0].reference_name,
|
||||
"advance_amount": advances[0].amount,
|
||||
"allocated_amount": 30000,
|
||||
},
|
||||
)
|
||||
pi1.save().submit()
|
||||
pi1.reload()
|
||||
payment.reload()
|
||||
self.assertEqual(pi1.taxes, [])
|
||||
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||
self.assertEqual(payment.taxes[0].allocated_amount, 3000)
|
||||
|
||||
pi2 = make_purchase_invoice(source_name=po.name)
|
||||
pi2.apply_tds = True
|
||||
pi2.tax_withholding_category = category
|
||||
pi2.items[0].qty = 3
|
||||
pi2.items[0].rate = 10000
|
||||
advances = pi2.get_advance_entries()
|
||||
pi2.append(
|
||||
"advances",
|
||||
{
|
||||
"reference_type": advances[0].reference_type,
|
||||
"reference_name": advances[0].reference_name,
|
||||
"advance_amount": advances[0].amount,
|
||||
"allocated_amount": 30000,
|
||||
},
|
||||
)
|
||||
pi2.save().submit()
|
||||
pi2.reload()
|
||||
payment.reload()
|
||||
self.assertEqual(pi2.taxes, [])
|
||||
self.assertEqual(payment.taxes[0].tax_amount, 6000)
|
||||
self.assertEqual(payment.taxes[0].allocated_amount, 6000)
|
||||
|
||||
|
||||
def cancel_invoices():
|
||||
purchase_invoices = frappe.get_all(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
@@ -142,7 +144,7 @@ def get_linked_payments_for_doc(
|
||||
@frappe.whitelist()
|
||||
def create_unreconcile_doc_for_selection(selections=None):
|
||||
if selections:
|
||||
selections = frappe.json.loads(selections)
|
||||
selections = json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
for row in selections:
|
||||
unrecon = frappe.new_doc("Unreconcile Payment")
|
||||
|
||||
@@ -188,7 +188,9 @@ def set_address_details(
|
||||
*,
|
||||
ignore_permissions=False,
|
||||
):
|
||||
billing_address_field = "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||
billing_address_field = (
|
||||
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
|
||||
)
|
||||
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
|
||||
if doctype:
|
||||
party_details.update(
|
||||
|
||||
@@ -49,7 +49,6 @@ def get_conditions(filters):
|
||||
|
||||
if filters.account_type:
|
||||
conditions["account_type"] = filters.account_type
|
||||
return conditions
|
||||
|
||||
if filters.company:
|
||||
conditions["company"] = filters.company
|
||||
|
||||
@@ -501,8 +501,9 @@ class ReceivablePayableReport:
|
||||
# Deduct that from paid amount pre allocation
|
||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||
|
||||
# If no or single payment terms, no need to split the row
|
||||
if len(payment_terms_details) <= 1:
|
||||
# If single payment terms, no need to split the row
|
||||
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
|
||||
self.append_payment_term(row, payment_terms_details[0], original_row)
|
||||
return
|
||||
|
||||
for d in payment_terms_details:
|
||||
@@ -1027,20 +1028,6 @@ class ReceivablePayableReport:
|
||||
fieldtype="Link",
|
||||
options="Contact",
|
||||
)
|
||||
if self.filters.party_type == "Customer":
|
||||
self.add_column(
|
||||
_("Customer Name"),
|
||||
fieldname="customer_name",
|
||||
fieldtype="Link",
|
||||
options="Customer",
|
||||
)
|
||||
elif self.filters.party_type == "Supplier":
|
||||
self.add_column(
|
||||
_("Supplier Name"),
|
||||
fieldname="supplier_name",
|
||||
fieldtype="Link",
|
||||
options="Supplier",
|
||||
)
|
||||
|
||||
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
|
||||
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")
|
||||
|
||||
@@ -15,14 +15,14 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
width: "80",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -38,14 +38,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -211,7 +211,8 @@ def get_conditions(filters):
|
||||
|
||||
if filters.get("account"):
|
||||
filters.account = get_accounts_with_children(filters.account)
|
||||
conditions.append("account in %(account)s")
|
||||
if filters.account:
|
||||
conditions.append("account in %(account)s")
|
||||
|
||||
if filters.get("cost_center"):
|
||||
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||
@@ -316,7 +317,7 @@ def get_accounts_with_children(accounts):
|
||||
else:
|
||||
frappe.throw(_("Account: {0} does not exist").format(d))
|
||||
|
||||
return list(set(all_accounts))
|
||||
return list(set(all_accounts)) if all_accounts else None
|
||||
|
||||
|
||||
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
|
||||
|
||||
@@ -15,14 +15,14 @@ frappe.query_reports["Gross Profit"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -717,20 +717,22 @@ class GrossProfitGenerator:
|
||||
frappe.qb.from_(purchase_invoice_item)
|
||||
.inner_join(purchase_invoice)
|
||||
.on(purchase_invoice.name == purchase_invoice_item.parent)
|
||||
.select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor)
|
||||
.select(
|
||||
purchase_invoice.name,
|
||||
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
|
||||
)
|
||||
.where(purchase_invoice.docstatus == 1)
|
||||
.where(purchase_invoice.posting_date <= self.filters.to_date)
|
||||
.where(purchase_invoice_item.item_code == item_code)
|
||||
)
|
||||
|
||||
if row.project:
|
||||
query.where(purchase_invoice_item.project == row.project)
|
||||
query = query.where(purchase_invoice_item.project == row.project)
|
||||
|
||||
if row.cost_center:
|
||||
query.where(purchase_invoice_item.cost_center == row.cost_center)
|
||||
query = query.where(purchase_invoice_item.cost_center == row.cost_center)
|
||||
|
||||
query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
|
||||
query.limit(1)
|
||||
query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
|
||||
last_purchase_rate = query.run()
|
||||
|
||||
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
|
||||
|
||||
@@ -15,7 +15,7 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, qb
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
||||
@@ -21,16 +23,12 @@ def execute(filters=None):
|
||||
|
||||
data = []
|
||||
for d in entries:
|
||||
invoice = invoice_details.get(d.against_voucher) or frappe._dict()
|
||||
|
||||
if d.reference_type == "Purchase Invoice":
|
||||
payment_amount = flt(d.debit) or -1 * flt(d.credit)
|
||||
else:
|
||||
payment_amount = flt(d.credit) or -1 * flt(d.debit)
|
||||
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
|
||||
payment_amount = d.amount
|
||||
|
||||
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
|
||||
|
||||
if d.against_voucher:
|
||||
if d.against_voucher_no:
|
||||
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
|
||||
|
||||
row = [
|
||||
@@ -39,11 +37,10 @@ def execute(filters=None):
|
||||
d.party_type,
|
||||
d.party,
|
||||
d.posting_date,
|
||||
d.against_voucher,
|
||||
d.against_voucher_no,
|
||||
invoice.posting_date,
|
||||
invoice.due_date,
|
||||
d.debit,
|
||||
d.credit,
|
||||
d.amount,
|
||||
d.remarks,
|
||||
d.age,
|
||||
d.range1,
|
||||
@@ -111,8 +108,7 @@ def get_columns(filters):
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "due_date", "label": _("Payment Due Date"), "fieldtype": "Date", "width": 100},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "amount", "label": _("Amount"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
|
||||
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
|
||||
{"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140},
|
||||
@@ -129,51 +125,68 @@ def get_columns(filters):
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
conditions = []
|
||||
|
||||
if not filters.party_type:
|
||||
if filters.payment_type == _("Outgoing"):
|
||||
filters.party_type = "Supplier"
|
||||
else:
|
||||
filters.party_type = "Customer"
|
||||
|
||||
if filters.party_type:
|
||||
conditions.append("party_type=%(party_type)s")
|
||||
conditions.append(ple.delinked.eq(0))
|
||||
if filters.payment_type == _("Outgoing"):
|
||||
conditions.append(ple.party_type.eq("Supplier"))
|
||||
conditions.append(ple.against_voucher_type.eq("Purchase Invoice"))
|
||||
else:
|
||||
conditions.append(ple.party_type.eq("Customer"))
|
||||
conditions.append(ple.against_voucher_type.eq("Sales Invoice"))
|
||||
|
||||
if filters.party:
|
||||
conditions.append("party=%(party)s")
|
||||
|
||||
if filters.party_type:
|
||||
conditions.append("against_voucher_type=%(reference_type)s")
|
||||
filters["reference_type"] = (
|
||||
"Sales Invoice" if filters.party_type == "Customer" else "Purchase Invoice"
|
||||
)
|
||||
conditions.append(ple.party.eq(filters.party))
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions.append("posting_date >= %(from_date)s")
|
||||
conditions.append(ple.posting_date.gte(filters.get("from_date")))
|
||||
|
||||
if filters.get("to_date"):
|
||||
conditions.append("posting_date <= %(to_date)s")
|
||||
conditions.append(ple.posting_date.lte(filters.get("to_date")))
|
||||
|
||||
return "and " + " and ".join(conditions) if conditions else ""
|
||||
if filters.get("company"):
|
||||
conditions.append(ple.company.eq(filters.get("company")))
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
def get_entries(filters):
|
||||
return frappe.db.sql(
|
||||
"""select
|
||||
voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {}
|
||||
""".format(get_conditions(filters)),
|
||||
filters,
|
||||
as_dict=1,
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
query = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.posting_date,
|
||||
Abs(ple.amount).as_("amount"),
|
||||
ple.remarks,
|
||||
ple.against_voucher_no,
|
||||
)
|
||||
.where(Criterion.all(conditions))
|
||||
)
|
||||
res = query.run(as_dict=True)
|
||||
return res
|
||||
|
||||
|
||||
def get_invoice_posting_date_map(filters):
|
||||
invoice_details = {}
|
||||
dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
|
||||
for t in frappe.db.sql(f"select name, posting_date, due_date from `tab{dt}`", as_dict=1):
|
||||
dt = (
|
||||
qb.DocType("Sales Invoice")
|
||||
if filters.get("payment_type") == _("Incoming")
|
||||
else qb.DocType("Purchase Invoice")
|
||||
)
|
||||
res = (
|
||||
qb.from_(dt)
|
||||
.select(dt.name, dt.posting_date, dt.due_date)
|
||||
.where((dt.docstatus.eq(1)) & (dt.company.eq(filters.get("company"))))
|
||||
.run(as_dict=1)
|
||||
)
|
||||
for t in res:
|
||||
invoice_details[t.name] = t
|
||||
|
||||
return invoice_details
|
||||
|
||||
@@ -59,13 +59,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
fieldname: "show_zero_values",
|
||||
|
||||
@@ -37,13 +37,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
|
||||
@@ -36,13 +36,13 @@ frappe.query_reports["Trial Balance for Party"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
fieldname: "party_type",
|
||||
|
||||
@@ -55,6 +55,9 @@ GL_REPOSTING_CHUNK = 100
|
||||
def get_fiscal_year(
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||
):
|
||||
if isinstance(boolean, str):
|
||||
boolean = loads(boolean)
|
||||
|
||||
fiscal_years = get_fiscal_years(
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||
)
|
||||
@@ -479,6 +482,11 @@ def reconcile_against_document(
|
||||
# re-submit advance entry
|
||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||
gl_map = doc.build_gl_map()
|
||||
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
||||
|
||||
# Make sure there is no overallocation
|
||||
process_debit_credit_difference(gl_map)
|
||||
|
||||
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
|
||||
|
||||
# Only update outstanding for newly linked vouchers
|
||||
|
||||
@@ -12,6 +12,7 @@ from frappe.utils import (
|
||||
add_months,
|
||||
add_years,
|
||||
cint,
|
||||
cstr,
|
||||
date_diff,
|
||||
flt,
|
||||
get_datetime,
|
||||
@@ -361,9 +362,11 @@ class Asset(AccountsController):
|
||||
final_number_of_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
|
||||
self.number_of_depreciations_booked
|
||||
)
|
||||
|
||||
has_pro_rata = self.check_is_pro_rata(finance_book)
|
||||
if has_pro_rata:
|
||||
depr_already_booked = any(
|
||||
[d.journal_entry for d in self.get("schedules") if d.finance_book == finance_book.finance_book]
|
||||
)
|
||||
if has_pro_rata and not depr_already_booked:
|
||||
final_number_of_depreciations += 1
|
||||
|
||||
has_wdv_or_dd_non_yearly_pro_rata = False
|
||||
@@ -543,7 +546,7 @@ class Asset(AccountsController):
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"depreciation_method": finance_book.depreciation_method,
|
||||
"finance_book": finance_book.finance_book,
|
||||
"finance_book_id": finance_book.idx,
|
||||
"finance_book_id": cstr(finance_book.idx),
|
||||
"shift": shift,
|
||||
},
|
||||
)
|
||||
@@ -749,7 +752,6 @@ class Asset(AccountsController):
|
||||
):
|
||||
straight_line_idx = []
|
||||
finance_books = []
|
||||
|
||||
for i, d in enumerate(self.get("schedules")):
|
||||
if ignore_booked_entry and d.journal_entry:
|
||||
continue
|
||||
@@ -771,7 +773,10 @@ class Asset(AccountsController):
|
||||
finance_books.append(int(d.finance_book_id))
|
||||
|
||||
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
if not d.journal_entry:
|
||||
value_after_depreciation = flt(
|
||||
flt(value_after_depreciation) - depreciation_amount, d.precision("depreciation_amount")
|
||||
)
|
||||
|
||||
# for the last row, if depreciation method = Straight Line
|
||||
if (
|
||||
@@ -783,10 +788,13 @@ class Asset(AccountsController):
|
||||
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
|
||||
|
||||
if not book.shift_based:
|
||||
depreciation_amount += flt(
|
||||
adjustment_amount = flt(
|
||||
value_after_depreciation - flt(book.expected_value_after_useful_life),
|
||||
d.precision("depreciation_amount"),
|
||||
)
|
||||
depreciation_amount = flt(
|
||||
depreciation_amount + adjustment_amount, d.precision("depreciation_amount")
|
||||
)
|
||||
|
||||
d.depreciation_amount = depreciation_amount
|
||||
accumulated_depreciation += d.depreciation_amount
|
||||
@@ -1433,7 +1441,7 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
|
||||
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
|
||||
elif asset.flags.increase_in_asset_value_due_to_repair:
|
||||
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
|
||||
row.total_number_of_depreciations
|
||||
number_of_pending_depreciations
|
||||
)
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
|
||||
@@ -1355,9 +1355,9 @@ class TestDepreciationBasics(AssetSetup):
|
||||
|
||||
for schedule in asset.schedules:
|
||||
if schedule.idx <= 3:
|
||||
self.assertEqual(schedule.finance_book_id, 1)
|
||||
self.assertEqual(schedule.finance_book_id, "1")
|
||||
else:
|
||||
self.assertEqual(schedule.finance_book_id, 2)
|
||||
self.assertEqual(schedule.finance_book_id, "2")
|
||||
|
||||
def test_depreciation_entry_cancellation(self):
|
||||
asset = create_asset(
|
||||
|
||||
@@ -103,12 +103,11 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 8300.0, 73674.71],
|
||||
["2023-09-30", 8300.0, 81974.71],
|
||||
["2023-10-31", 8300.0, 90274.71],
|
||||
["2023-11-30", 8300.0, 98574.71],
|
||||
["2023-12-31", 8300.0, 106874.71],
|
||||
["2024-01-15", 8300.0, 115174.71],
|
||||
["2023-08-31", 9960.0, 75334.71],
|
||||
["2023-09-30", 9960.0, 85294.71],
|
||||
["2023-10-31", 9960.0, 95254.71],
|
||||
["2023-11-30", 9960.0, 105214.71],
|
||||
["2023-12-15", 9960.0, 115174.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
|
||||
@@ -402,7 +402,7 @@
|
||||
{
|
||||
"fieldname": "contact_and_address_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Contact & Address"
|
||||
"label": "Address & Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_tab",
|
||||
@@ -457,7 +457,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-19 16:55:15.148325",
|
||||
"modified": "2024-05-08 18:02:57.342931",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -27,13 +27,13 @@ frappe.query_reports["Procurement Tracker"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -35,14 +35,14 @@ frappe.query_reports["Purchase Analytics"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1008,7 +1008,12 @@ def is_reposting_pending():
|
||||
)
|
||||
|
||||
|
||||
def future_sle_exists(args, sl_entries=None):
|
||||
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
|
||||
if allow_force_reposting and frappe.db.get_single_value(
|
||||
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
|
||||
):
|
||||
return True
|
||||
|
||||
key = (args.voucher_type, args.voucher_no)
|
||||
if not hasattr(frappe.local, "future_sle"):
|
||||
frappe.local.future_sle = {}
|
||||
|
||||
@@ -6,13 +6,13 @@ frappe.query_reports["Campaign Efficiency"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -6,13 +6,13 @@ frappe.query_reports["Lead Owner Efficiency"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -21,7 +21,8 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
||||
exploded_items = frappe.get_all(
|
||||
"BOM Item",
|
||||
filters={"parent": bom},
|
||||
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
|
||||
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom", "idx"],
|
||||
order_by="idx ASC",
|
||||
)
|
||||
|
||||
for item in exploded_items:
|
||||
|
||||
@@ -37,14 +37,14 @@ frappe.query_reports["Job Card Summary"] = {
|
||||
label: __("From Posting Date"),
|
||||
fieldname: "from_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
label: __("To Posting Date"),
|
||||
fieldname: "to_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,14 +16,14 @@ frappe.query_reports["Production Analytics"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -362,4 +362,5 @@ erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2
|
||||
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
|
||||
execute:frappe.db.set_single_value('E Commerce Settings', 'show_actual_qty', 1)
|
||||
erpnext.patches.v14_0.delete_orphaned_asset_movement_item_records
|
||||
erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
|
||||
erpnext.patches.v14_0.remove_cancelled_asset_capitalization_from_asset
|
||||
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
pr_table = frappe.qb.DocType("Pricing Rule")
|
||||
(
|
||||
frappe.qb.update(pr_table)
|
||||
.set(pr_table.has_priority, 1)
|
||||
.where((pr_table.priority.isnotnull()) & (pr_table.priority != ""))
|
||||
).run()
|
||||
@@ -57,6 +57,14 @@ frappe.ui.form.on("Project", {
|
||||
filters: filters,
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -452,7 +452,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 4,
|
||||
"modified": "2023-02-14 04:54:25.819620",
|
||||
"modified": "2024-04-24 10:56:16.001032",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project",
|
||||
@@ -487,6 +487,15 @@
|
||||
"role": "Projects Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
|
||||
@@ -410,11 +410,13 @@ $.extend(erpnext.utils, {
|
||||
method: "erpnext.accounts.utils.get_fiscal_year",
|
||||
args: {
|
||||
date: date,
|
||||
boolean: boolean,
|
||||
},
|
||||
async: false,
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
fiscal_year = r.message[0];
|
||||
if (with_dates) fiscal_year = r.message;
|
||||
else fiscal_year = r.message[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -823,11 +825,14 @@ erpnext.utils.map_current_doc = function (opts) {
|
||||
if (opts.source_doctype) {
|
||||
let data_fields = [];
|
||||
if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) {
|
||||
data_fields.push({
|
||||
fieldname: "merge_taxes",
|
||||
fieldtype: "Check",
|
||||
label: __("Merge taxes from multiple documents"),
|
||||
});
|
||||
let target_meta = frappe.get_meta(cur_frm.doc.doctype);
|
||||
if (target_meta.fields.find((f) => f.fieldname === "taxes")) {
|
||||
data_fields.push({
|
||||
fieldname: "merge_taxes",
|
||||
fieldtype: "Check",
|
||||
label: __("Merge taxes from multiple documents"),
|
||||
});
|
||||
}
|
||||
}
|
||||
const d = new frappe.ui.form.MultiSelectDialog({
|
||||
doctype: opts.source_doctype,
|
||||
|
||||
@@ -446,7 +446,7 @@ body.product-page {
|
||||
width: 30%;
|
||||
|
||||
@media (max-width: 992px) {
|
||||
width: 40%;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,14 +135,51 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-18 08:25:35.302081",
|
||||
"modified": "2024-04-18 15:25:25.808355",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "Lower Deduction Certificate",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
|
||||
@@ -481,7 +481,7 @@
|
||||
{
|
||||
"fieldname": "contact_and_address_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Contact & Address"
|
||||
"label": "Address & Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "defaults_tab",
|
||||
@@ -567,7 +567,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2023-12-28 13:15:36.298369",
|
||||
"modified": "2024-05-08 18:03:20.716169",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.test_runner import make_test_records
|
||||
@@ -322,7 +324,7 @@ class TestCustomer(FrappeTestCase):
|
||||
frappe.ValidationError,
|
||||
update_child_qty_rate,
|
||||
so.doctype,
|
||||
frappe.json.dumps([modified_item]),
|
||||
json.dumps([modified_item]),
|
||||
so.name,
|
||||
)
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ frappe.ui.form.on('Quotation', {
|
||||
frm.trigger("set_label");
|
||||
frm.trigger("toggle_reqd_lead_customer");
|
||||
frm.trigger("set_dynamic_field_label");
|
||||
frm.set_value("party_name", "");
|
||||
frm.set_value("customer_name", "");
|
||||
},
|
||||
|
||||
set_label: function(frm) {
|
||||
@@ -71,7 +73,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
||||
frappe.dynamic_link = {
|
||||
doc: this.frm.doc,
|
||||
fieldname: 'party_name',
|
||||
doctype: doc.quotation_to == 'Customer' ? 'Customer' : 'Lead',
|
||||
doctype: doc.quotation_to,
|
||||
};
|
||||
|
||||
var me = this;
|
||||
@@ -170,6 +172,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
||||
}
|
||||
} else if (this.frm.doc.quotation_to == "Prospect") {
|
||||
this.frm.set_df_property("party_name", "label", "Prospect");
|
||||
this.frm.fields_dict.party_name.get_query = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1138,7 +1138,8 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Inter Company Order Reference",
|
||||
"options": "Purchase Order",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
@@ -1631,7 +1632,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-20 16:04:43.627183",
|
||||
"modified": "2024-05-23 16:35:54.905804",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
@@ -1710,4 +1711,4 @@
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,11 +932,19 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
target.tc_name = ""
|
||||
target.terms = ""
|
||||
target.payment_terms_template = ""
|
||||
target.payment_schedule = []
|
||||
|
||||
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
|
||||
if default_price_list:
|
||||
target.buying_price_list = default_price_list
|
||||
|
||||
default_payment_terms = frappe.get_value("Supplier", supplier, "payment_terms")
|
||||
if default_payment_terms:
|
||||
target.payment_terms_template = default_payment_terms
|
||||
|
||||
if any(item.delivered_by_supplier == 1 for item in source.items):
|
||||
if source.shipping_address_name:
|
||||
target.shipping_address = source.shipping_address_name
|
||||
@@ -988,7 +996,6 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
"contact_person",
|
||||
"taxes_and_charges",
|
||||
"shipping_address",
|
||||
"terms",
|
||||
],
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
},
|
||||
@@ -1056,6 +1063,10 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
target.tc_name = ""
|
||||
target.terms = ""
|
||||
target.payment_terms_template = ""
|
||||
target.payment_schedule = []
|
||||
|
||||
if is_drop_ship_order(target):
|
||||
target.customer = source.customer
|
||||
@@ -1091,7 +1102,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
"contact_person",
|
||||
"taxes_and_charges",
|
||||
"shipping_address",
|
||||
"terms",
|
||||
],
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
},
|
||||
|
||||
@@ -23,14 +23,14 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -43,14 +43,14 @@ frappe.query_reports["Sales Analytics"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ frappe.query_reports["Sales Person Commission Summary"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
|
||||
@@ -20,7 +20,7 @@ frappe.query_reports["Sales Person-wise Transaction Summary"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
|
||||
@@ -53,8 +53,6 @@ class AuthorizationRule(Document):
|
||||
frappe.throw(_("Discount must be less than 100"))
|
||||
elif self.based_on == "Customerwise Discount" and not self.master_name:
|
||||
frappe.throw(_("Customer required for 'Customerwise Discount'"))
|
||||
else:
|
||||
self.based_on = "Not Applicable"
|
||||
|
||||
def validate(self):
|
||||
self.check_duplicate_entry()
|
||||
|
||||
@@ -12,10 +12,11 @@ frappe.ui.form.on("Company", {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.call("check_if_transactions_exist").then((r) => {
|
||||
frm.toggle_enable("default_currency", !r.message);
|
||||
});
|
||||
if (!frm.doc.__islocal) {
|
||||
frm.call("check_if_transactions_exist").then((r) => {
|
||||
frm.toggle_enable("default_currency", !r.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
setup: function (frm) {
|
||||
frm.__rename_queue = "long";
|
||||
|
||||
@@ -18,18 +18,6 @@ erpnext.setup.EmployeeController = class EmployeeController extends frappe.ui.fo
|
||||
refresh() {
|
||||
erpnext.toggle_naming_series();
|
||||
}
|
||||
|
||||
salutation() {
|
||||
if (this.frm.doc.salutation) {
|
||||
this.frm.set_value(
|
||||
"gender",
|
||||
{
|
||||
Mr: "Male",
|
||||
Ms: "Female",
|
||||
}[this.frm.doc.salutation]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.on("Employee", {
|
||||
|
||||
@@ -202,7 +202,7 @@ def update_qty(bin_name, args):
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
# actual qty is not up to date in case of backdated transaction
|
||||
if future_sle_exists(args):
|
||||
if future_sle_exists(args, allow_force_reposting=False):
|
||||
last_sle_qty = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.qty_after_transaction)
|
||||
|
||||
@@ -739,7 +739,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_trip(source_name, target_doc=None):
|
||||
def make_delivery_trip(source_name, target_doc=None, kwargs=None):
|
||||
def update_stop_details(source_doc, target_doc, source_parent):
|
||||
target_doc.customer = source_parent.customer
|
||||
target_doc.address = source_parent.shipping_address_name
|
||||
@@ -772,7 +772,7 @@ def make_delivery_trip(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_installation_note(source_name, target_doc=None):
|
||||
def make_installation_note(source_name, target_doc=None, kwargs=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.installed_qty)
|
||||
target.serial_no = obj.serial_no
|
||||
|
||||
@@ -15,6 +15,9 @@ frappe.ui.form.on("Item", {
|
||||
frm.add_fetch("tax_type", "tax_rate", "tax_rate");
|
||||
|
||||
frm.make_methods = {
|
||||
Quotation: () => {
|
||||
open_form(frm, "Quotation", "Quotation Item", "items");
|
||||
},
|
||||
"Sales Order": () => {
|
||||
open_form(frm, "Sales Order", "Sales Order Item", "items");
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import copy
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
@@ -397,6 +397,13 @@ class Item(Document):
|
||||
def validate_warehouse_for_reorder(self):
|
||||
"""Validate Reorder level table for duplicate and conditional mandatory"""
|
||||
warehouse_material_request_type: list[tuple[str, str]] = []
|
||||
|
||||
_warehouse_before_save = frappe._dict()
|
||||
if not self.is_new() and self._doc_before_save:
|
||||
_warehouse_before_save = {
|
||||
d.name: d.warehouse for d in self._doc_before_save.get("reorder_levels") or []
|
||||
}
|
||||
|
||||
for d in self.get("reorder_levels"):
|
||||
if not d.warehouse_group:
|
||||
d.warehouse_group = d.warehouse
|
||||
@@ -413,6 +420,19 @@ class Item(Document):
|
||||
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
|
||||
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
|
||||
|
||||
if d.warehouse_group and d.warehouse:
|
||||
if _warehouse_before_save.get(d.name) == d.warehouse:
|
||||
continue
|
||||
|
||||
child_warehouses = get_child_warehouses(d.warehouse_group)
|
||||
if d.warehouse not in child_warehouses:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: The warehouse {1} is not a child warehouse of a group warehouse {2}"
|
||||
).format(d.idx, bold(d.warehouse), bold(d.warehouse_group)),
|
||||
title=_("Incorrect Check in (group) Warehouse for Reorder"),
|
||||
)
|
||||
|
||||
def stock_ledger_created(self):
|
||||
if not hasattr(self, "_stock_ledger_created"):
|
||||
self._stock_ledger_created = len(
|
||||
@@ -1318,3 +1338,10 @@ def get_asset_naming_series():
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
|
||||
|
||||
return get_asset_naming_series()
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_child_warehouses(warehouse):
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
|
||||
return get_child_warehouses(warehouse)
|
||||
|
||||
@@ -848,6 +848,27 @@ class TestItem(FrappeTestCase):
|
||||
self.assertEqual(data[0].description, item.description)
|
||||
self.assertTrue("description" in data[0])
|
||||
|
||||
def test_group_warehouse_for_reorder_item(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
item_doc = make_item("_Test Group Warehouse For Reorder Item", {"is_stock_item": 1})
|
||||
warehouse = create_warehouse("_Test Warehouse - _TC")
|
||||
warehouse_doc = frappe.get_doc("Warehouse", warehouse)
|
||||
warehouse_doc.db_set("parent_warehouse", "")
|
||||
|
||||
item_doc.append(
|
||||
"reorder_levels",
|
||||
{
|
||||
"warehouse": warehouse,
|
||||
"warehouse_reorder_level": 10,
|
||||
"warehouse_reorder_qty": 100,
|
||||
"material_request_type": "Purchase",
|
||||
"warehouse_group": "_Test Warehouse Group - _TC",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, item_doc.save)
|
||||
|
||||
|
||||
def set_item_variant_settings(fields):
|
||||
doc = frappe.get_doc("Item Variant Settings")
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"parent_warehouse",
|
||||
"consider_rejected_warehouses",
|
||||
"get_item_locations",
|
||||
"pick_manually",
|
||||
"section_break_6",
|
||||
"scan_barcode",
|
||||
"column_break_13",
|
||||
@@ -192,11 +193,18 @@
|
||||
"fieldname": "consider_rejected_warehouses",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Rejected Warehouses"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled then system won't override the picked qty / batches / serial numbers.",
|
||||
"fieldname": "pick_manually",
|
||||
"fieldtype": "Check",
|
||||
"label": "Pick Manually"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-24 17:05:20.317180",
|
||||
"modified": "2024-03-27 22:49:16.954637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
@@ -268,4 +276,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ class PickList(Document):
|
||||
|
||||
def before_save(self):
|
||||
self.update_status()
|
||||
self.set_item_locations()
|
||||
if not self.pick_manually:
|
||||
self.set_item_locations()
|
||||
|
||||
if self.get("locations"):
|
||||
self.validate_sales_order_percentage()
|
||||
|
||||
@@ -126,7 +126,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_section",
|
||||
@@ -193,7 +194,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-25 11:56:23.361867",
|
||||
"modified": "2024-05-07 15:32:42.905446",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
@@ -204,4 +205,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +126,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.po_required()
|
||||
self.validate_items_quality_inspection()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer()
|
||||
self.validate_cwip_accounts()
|
||||
self.validate_provisional_expense_account()
|
||||
|
||||
@@ -141,6 +140,10 @@ class PurchaseReceipt(BuyingController):
|
||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
|
||||
def validate_uom_is_integer(self):
|
||||
super().validate_uom_is_integer("uom", ["qty", "received_qty"], "Purchase Receipt Item")
|
||||
super().validate_uom_is_integer("stock_uom", "stock_qty", "Purchase Receipt Item")
|
||||
|
||||
def validate_cwip_accounts(self):
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
||||
|
||||
@@ -8,16 +8,7 @@ import frappe
|
||||
from frappe import ValidationError, _
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.query_builder.functions import Coalesce
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
nowdate,
|
||||
safe_json_loads,
|
||||
)
|
||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowdate, safe_json_loads
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.stock.get_item_details import get_reserved_qty_for_so
|
||||
@@ -618,16 +609,14 @@ def auto_make_serial_nos(args):
|
||||
voucher_type = args.get("voucher_type")
|
||||
item_code = args.get("item_code")
|
||||
for serial_no in serial_nos:
|
||||
is_new = False
|
||||
if frappe.db.exists("Serial No", serial_no):
|
||||
sr = frappe.get_cached_doc("Serial No", serial_no)
|
||||
elif args.get("actual_qty", 0) > 0:
|
||||
sr = frappe.new_doc("Serial No")
|
||||
is_new = True
|
||||
sr = update_args_for_serial_no(sr, serial_no, args)
|
||||
elif args.get("actual_qty", 0) > 0 and serial_no:
|
||||
created_numbers.append(serial_no)
|
||||
|
||||
sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new)
|
||||
if is_new:
|
||||
created_numbers.append(sr.name)
|
||||
if created_numbers:
|
||||
make_bulk_serial_nos(args, created_numbers)
|
||||
|
||||
form_links = list(map(lambda d: get_link_to_form("Serial No", d), created_numbers))
|
||||
|
||||
@@ -647,6 +636,72 @@ def auto_make_serial_nos(args):
|
||||
frappe.msgprint(message, multiple_title)
|
||||
|
||||
|
||||
def make_bulk_serial_nos(args, serial_nos):
|
||||
# for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
|
||||
|
||||
if isinstance(args, dict):
|
||||
args = frappe._dict(args)
|
||||
|
||||
serial_nos_details = []
|
||||
item_details = frappe.get_cached_value("Item", args.item_code, ["item_name", "description"], as_dict=1)
|
||||
|
||||
supplier = None
|
||||
if args.voucher_type in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
supplier = frappe.get_cached_value(args.voucher_type, args.voucher_no, "supplier")
|
||||
|
||||
for serial_no in serial_nos:
|
||||
serial_nos_details.append(
|
||||
(
|
||||
serial_no,
|
||||
serial_no,
|
||||
now(),
|
||||
now(),
|
||||
frappe.session.user,
|
||||
frappe.session.user,
|
||||
args.warehouse,
|
||||
args.company,
|
||||
args.item_code,
|
||||
item_details.item_name,
|
||||
item_details.description,
|
||||
"Active",
|
||||
args.batch_no,
|
||||
args.get("work_order"),
|
||||
supplier,
|
||||
args.voucher_type,
|
||||
args.voucher_no,
|
||||
args.posting_date,
|
||||
args.posting_time,
|
||||
flt(args.incoming_rate),
|
||||
)
|
||||
)
|
||||
|
||||
if serial_nos_details:
|
||||
fields = [
|
||||
"name",
|
||||
"serial_no",
|
||||
"creation",
|
||||
"modified",
|
||||
"owner",
|
||||
"modified_by",
|
||||
"warehouse",
|
||||
"company",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"status",
|
||||
"batch_no",
|
||||
"work_order",
|
||||
"supplier",
|
||||
"purchase_document_type",
|
||||
"purchase_document_no",
|
||||
"purchase_date",
|
||||
"purchase_time",
|
||||
"purchase_rate",
|
||||
]
|
||||
|
||||
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||
|
||||
|
||||
def get_items_html(serial_nos, item_code):
|
||||
body = ", ".join(serial_nos)
|
||||
return f"""<details><summary>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"end_time",
|
||||
"limits_dont_apply_on",
|
||||
"item_based_reposting",
|
||||
"do_reposting_for_each_stock_transaction",
|
||||
"errors_notification_section",
|
||||
"notify_reposting_error_to_role"
|
||||
],
|
||||
@@ -65,12 +66,18 @@
|
||||
"fieldname": "errors_notification_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Errors Notification"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "do_reposting_for_each_stock_transaction",
|
||||
"fieldtype": "Check",
|
||||
"label": "Do reposting for each Stock Transaction"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-01 16:14:29.080697",
|
||||
"modified": "2024-04-24 12:19:40.204888",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reposting Settings",
|
||||
@@ -91,4 +98,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ class StockRepostingSettings(Document):
|
||||
def validate(self):
|
||||
self.set_minimum_reposting_time_slot()
|
||||
|
||||
def before_save(self):
|
||||
if self.do_reposting_for_each_stock_transaction:
|
||||
self.item_based_reposting = 1
|
||||
|
||||
def set_minimum_reposting_time_slot(self):
|
||||
"""Ensure that timeslot for reposting is at least 12 hours."""
|
||||
if not self.limit_reposting_timeslot:
|
||||
|
||||
@@ -38,3 +38,51 @@ class TestStockRepostingSettings(unittest.TestCase):
|
||||
|
||||
users = get_recipients()
|
||||
self.assertTrue(user in users)
|
||||
|
||||
def test_do_reposting_for_each_stock_transaction(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1)
|
||||
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
||||
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
||||
|
||||
item = make_item(
|
||||
"_Test item for reposting check for each transaction", properties={"is_stock_item": 1}
|
||||
).name
|
||||
|
||||
stock_entry = make_stock_entry(
|
||||
item_code=item,
|
||||
qty=1,
|
||||
rate=100,
|
||||
stock_entry_type="Material Receipt",
|
||||
target="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
||||
self.assertTrue(riv)
|
||||
|
||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
||||
|
||||
def test_do_not_reposting_for_each_stock_transaction(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
||||
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
||||
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
||||
|
||||
item = make_item(
|
||||
"_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1}
|
||||
).name
|
||||
|
||||
stock_entry = make_stock_entry(
|
||||
item_code=item,
|
||||
qty=1,
|
||||
rate=100,
|
||||
stock_entry_type="Material Receipt",
|
||||
target="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
||||
self.assertFalse(riv)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Available Batch Report"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_default("company"),
|
||||
},
|
||||
{
|
||||
fieldname: "to_date",
|
||||
label: __("On This Date"),
|
||||
fieldtype: "Date",
|
||||
width: "80",
|
||||
reqd: 1,
|
||||
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
},
|
||||
{
|
||||
fieldname: "item_code",
|
||||
label: __("Item"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Item",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
has_batch_no: 1,
|
||||
disabled: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse",
|
||||
label: __("Warehouse"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Warehouse",
|
||||
get_query: () => {
|
||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||
let company = frappe.query_report.get_filter_value("company");
|
||||
|
||||
return {
|
||||
filters: {
|
||||
...(warehouse_type && { warehouse_type }),
|
||||
...(company && { company }),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse_type",
|
||||
label: __("Warehouse Type"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Warehouse Type",
|
||||
},
|
||||
{
|
||||
fieldname: "batch_no",
|
||||
label: __("Batch No"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Batch",
|
||||
get_query: () => {
|
||||
let item = frappe.query_report.get_filter_value("item_code");
|
||||
|
||||
return {
|
||||
filters: {
|
||||
...(item && { item }),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "include_expired_batches",
|
||||
label: __("Include Expired Batches"),
|
||||
fieldtype: "Check",
|
||||
width: "80",
|
||||
},
|
||||
{
|
||||
fieldname: "show_item_name",
|
||||
label: __("Show Item Name"),
|
||||
fieldtype: "Check",
|
||||
width: "80",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2024-04-11 17:03:32.253275",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"letter_head": "",
|
||||
"modified": "2024-04-23 17:09:54.595566",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Available Batch Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Stock Ledger Entry",
|
||||
"report_name": "Available Batch Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, today
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
data = get_data(filters)
|
||||
columns = get_columns(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
"label": _("Item Code"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 200,
|
||||
}
|
||||
]
|
||||
|
||||
if filters.show_item_name:
|
||||
columns.append(
|
||||
{
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 200,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"label": _("Batch No"),
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"width": 150,
|
||||
"options": "Batch",
|
||||
},
|
||||
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
|
||||
]
|
||||
)
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
batchwise_data = get_batchwise_data_from_stock_ledger(filters)
|
||||
|
||||
data = parse_batchwise_data(batchwise_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def parse_batchwise_data(batchwise_data):
|
||||
data = []
|
||||
for key in batchwise_data:
|
||||
d = batchwise_data[key]
|
||||
if d.balance_qty == 0:
|
||||
continue
|
||||
|
||||
data.append(d)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_batchwise_data_from_stock_ledger(filters):
|
||||
batchwise_data = frappe._dict({})
|
||||
|
||||
table = frappe.qb.DocType("Stock Ledger Entry")
|
||||
batch = frappe.qb.DocType("Batch")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.inner_join(batch)
|
||||
.on(table.batch_no == batch.name)
|
||||
.select(
|
||||
table.item_code,
|
||||
table.batch_no,
|
||||
table.warehouse,
|
||||
Sum(table.actual_qty).as_("balance_qty"),
|
||||
)
|
||||
.where(table.is_cancelled == 0)
|
||||
.groupby(table.batch_no, table.item_code, table.warehouse)
|
||||
)
|
||||
|
||||
query = get_query_based_on_filters(query, batch, table, filters)
|
||||
|
||||
for d in query.run(as_dict=True):
|
||||
key = (d.item_code, d.warehouse, d.batch_no)
|
||||
batchwise_data.setdefault(key, d)
|
||||
|
||||
return batchwise_data
|
||||
|
||||
|
||||
def get_query_based_on_filters(query, batch, table, filters):
|
||||
if filters.item_code:
|
||||
query = query.where(table.item_code == filters.item_code)
|
||||
|
||||
if filters.batch_no:
|
||||
query = query.where(batch.name == filters.batch_no)
|
||||
|
||||
if not filters.include_expired_batches:
|
||||
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
|
||||
if filters.to_date == today():
|
||||
query = query.where(batch.batch_qty > 0)
|
||||
|
||||
if filters.warehouse:
|
||||
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
|
||||
warehouses = frappe.get_all(
|
||||
"Warehouse", filters={"lft": (">=", lft), "rgt": ("<=", rgt), "is_group": 0}, pluck="name"
|
||||
)
|
||||
|
||||
query = query.where(table.warehouse.isin(warehouses))
|
||||
|
||||
elif filters.warehouse_type:
|
||||
warehouses = frappe.get_all(
|
||||
"Warehouse", filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, pluck="name"
|
||||
)
|
||||
|
||||
query = query.where(table.warehouse.isin(warehouses))
|
||||
|
||||
if filters.show_item_name:
|
||||
query = query.select(batch.item_name)
|
||||
|
||||
return query
|
||||
@@ -40,16 +40,26 @@ frappe.query_reports["Batch-Wise Balance History"] = {
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse_type",
|
||||
label: __("Warehouse Type"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Warehouse Type",
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse",
|
||||
label: __("Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
get_query: function () {
|
||||
let company = frappe.query_report.get_filter_value("company");
|
||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||
const company = frappe.query_report.get_filter_value("company");
|
||||
|
||||
return {
|
||||
filters: {
|
||||
company: company,
|
||||
...(warehouse_type && { warehouse_type }),
|
||||
...(company && { company }),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -29,8 +29,15 @@ def execute(filters=None):
|
||||
|
||||
sle_count = _estimate_table_row_count("Stock Ledger Entry")
|
||||
|
||||
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
|
||||
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
|
||||
if (
|
||||
sle_count > SLE_COUNT_LIMIT
|
||||
and not filters.get("item_code")
|
||||
and not filters.get("warehouse")
|
||||
and not filters.get("warehouse_type")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Please select either the Item or Warehouse or Warehouse Type filter to generate the report.")
|
||||
)
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
@@ -113,6 +120,16 @@ def get_stock_ledger_entries(filters):
|
||||
)
|
||||
|
||||
query = apply_warehouse_filter(query, sle, filters)
|
||||
if filters.warehouse_type and not filters.warehouse:
|
||||
warehouses = frappe.get_all(
|
||||
"Warehouse",
|
||||
filters={"warehouse_type": filters.warehouse_type, "is_group": 0},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if warehouses:
|
||||
query = query.where(sle.warehouse.isin(warehouses))
|
||||
|
||||
for field in ["item_code", "batch_no", "company"]:
|
||||
if filters.get(field):
|
||||
query = query.where(sle[field] == filters.get(field))
|
||||
@@ -121,6 +138,7 @@ def get_stock_ledger_entries(filters):
|
||||
|
||||
|
||||
def get_item_warehouse_batch_map(filters, float_precision):
|
||||
_system_settings = frappe.get_cached_doc("System Settings")
|
||||
with frappe.db.unbuffered_cursor():
|
||||
sle = get_stock_ledger_entries(filters)
|
||||
sle = sle.run(as_dict=True, as_iterator=True)
|
||||
|
||||
@@ -22,14 +22,14 @@ frappe.query_reports["Incorrect Serial No Valuation"] = {
|
||||
fieldtype: "Date",
|
||||
fieldname: "from_date",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
fieldname: "to_date",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -18,15 +18,25 @@ frappe.query_reports["Stock Ageing"] = {
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse_type",
|
||||
label: __("Warehouse Type"),
|
||||
fieldtype: "Link",
|
||||
width: "80",
|
||||
options: "Warehouse Type",
|
||||
},
|
||||
{
|
||||
fieldname: "warehouse",
|
||||
label: __("Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
get_query: () => {
|
||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||
const company = frappe.query_report.get_filter_value("company");
|
||||
|
||||
return {
|
||||
filters: {
|
||||
...(warehouse_type && { warehouse_type }),
|
||||
...(company && { company }),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -227,25 +227,31 @@ class FIFOSlots:
|
||||
consumed/updated and maintained via FIFO. **
|
||||
}
|
||||
"""
|
||||
if self.sle is None:
|
||||
self.sle = self.__get_stock_ledger_entries()
|
||||
stock_ledger_entries = self.sle
|
||||
|
||||
for d in self.sle:
|
||||
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
||||
_system_settings = frappe.get_cached_doc("System Settings")
|
||||
with frappe.db.unbuffered_cursor():
|
||||
if stock_ledger_entries is None:
|
||||
stock_ledger_entries = self.__get_stock_ledger_entries()
|
||||
|
||||
if d.voucher_type == "Stock Reconciliation":
|
||||
# get difference in qty shift as actual qty
|
||||
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
|
||||
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
|
||||
for d in stock_ledger_entries:
|
||||
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
||||
|
||||
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
|
||||
if d.voucher_type == "Stock Reconciliation":
|
||||
# get difference in qty shift as actual qty
|
||||
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
|
||||
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
|
||||
|
||||
if d.actual_qty > 0:
|
||||
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||
else:
|
||||
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
|
||||
|
||||
self.__update_balances(d, key)
|
||||
if d.actual_qty > 0:
|
||||
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||
else:
|
||||
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||
|
||||
self.__update_balances(d, key)
|
||||
|
||||
del stock_ledger_entries
|
||||
|
||||
if not self.filters.get("show_warehouse_wise_stock"):
|
||||
# (Item 1, WH 1), (Item 1, WH 2) => (Item 1)
|
||||
@@ -412,10 +418,19 @@ class FIFOSlots:
|
||||
|
||||
if self.filters.get("warehouse"):
|
||||
sle_query = self.__get_warehouse_conditions(sle, sle_query)
|
||||
elif self.filters.get("warehouse_type"):
|
||||
warehouses = frappe.get_all(
|
||||
"Warehouse",
|
||||
filters={"warehouse_type": self.filters.get("warehouse_type"), "is_group": 0},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if warehouses:
|
||||
sle_query = sle_query.where(sle.warehouse.isin(warehouses))
|
||||
|
||||
sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
|
||||
|
||||
return sle_query.run(as_dict=True)
|
||||
return sle_query.run(as_dict=True, as_iterator=True)
|
||||
|
||||
def __get_item_query(self) -> str:
|
||||
item_table = frappe.qb.DocType("Item")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user