mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 07:29:22 +00:00
Merge pull request #46715 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
30
.github/workflows/label-base-on-title.yml
vendored
Normal file
30
.github/workflows/label-base-on-title.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: "Auto-label PRs based on title"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-label-if-prefix-matches:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check PR title and add label if it matches prefixes
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const title = context.payload.pull_request.title.toLowerCase();
|
||||||
|
const prefixes = ['chore', 'ci', 'style', 'test', 'refactor'];
|
||||||
|
|
||||||
|
// Check if the PR title starts with any of the prefixes
|
||||||
|
if (prefixes.some(prefix => title.startsWith(prefix))) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
labels: ['skip-release-notes']
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -116,6 +116,7 @@ def identify_is_group(child):
|
|||||||
return is_group
|
return is_group
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_chart(chart_template, existing_company=None):
|
def get_chart(chart_template, existing_company=None):
|
||||||
chart = {}
|
chart = {}
|
||||||
if existing_company:
|
if existing_company:
|
||||||
|
|||||||
@@ -159,9 +159,6 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
if bank_account:
|
|
||||||
condition += "and bank_account = %(bank_account)s"
|
|
||||||
|
|
||||||
payment_entries = frappe.db.sql(
|
payment_entries = frappe.db.sql(
|
||||||
f"""
|
f"""
|
||||||
select
|
select
|
||||||
@@ -183,7 +180,6 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
"account": account,
|
"account": account,
|
||||||
"from": from_date,
|
"from": from_date,
|
||||||
"to": to_date,
|
"to": to_date,
|
||||||
"bank_account": bank_account,
|
|
||||||
},
|
},
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -105,7 +105,8 @@
|
|||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"oldfieldname": "cost_center",
|
"oldfieldname": "cost_center",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "debit",
|
"fieldname": "debit",
|
||||||
@@ -358,7 +359,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-21 14:36:49.431166",
|
"modified": "2025-03-21 15:29:11.221890",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GL Entry",
|
"name": "GL Entry",
|
||||||
|
|||||||
@@ -576,8 +576,22 @@ class JournalEntry(AccountsController):
|
|||||||
if customers:
|
if customers:
|
||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
|
|
||||||
|
customer_details = frappe._dict(
|
||||||
|
frappe.db.get_all(
|
||||||
|
"Customer Credit Limit",
|
||||||
|
filters={
|
||||||
|
"parent": ["in", customers],
|
||||||
|
"parenttype": ["=", "Customer"],
|
||||||
|
"company": ["=", self.company],
|
||||||
|
},
|
||||||
|
fields=["parent", "bypass_credit_limit_check"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for customer in customers:
|
for customer in customers:
|
||||||
check_credit_limit(customer, self.company)
|
ignore_outstanding_sales_order = bool(customer_details.get(customer))
|
||||||
|
check_credit_limit(customer, self.company, ignore_outstanding_sales_order)
|
||||||
|
|
||||||
def validate_cheque_info(self):
|
def validate_cheque_info(self):
|
||||||
if self.voucher_type in ["Bank Entry"]:
|
if self.voucher_type in ["Bank Entry"]:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from functools import reduce
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import ValidationError, _, qb, scrub, throw
|
from frappe import ValidationError, _, qb, scrub, throw
|
||||||
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import Tuple
|
from frappe.query_builder import Tuple
|
||||||
from frappe.query_builder.functions import Count
|
from frappe.query_builder.functions import Count
|
||||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||||
@@ -37,7 +38,7 @@ from erpnext.accounts.general_ledger import (
|
|||||||
make_reverse_gl_entries,
|
make_reverse_gl_entries,
|
||||||
process_gl_map,
|
process_gl_map,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details
|
||||||
from erpnext.accounts.utils import (
|
from erpnext.accounts.utils import (
|
||||||
cancel_exchange_gain_loss_journal,
|
cancel_exchange_gain_loss_journal,
|
||||||
get_account_currency,
|
get_account_currency,
|
||||||
@@ -439,6 +440,12 @@ class PaymentEntry(AccountsController):
|
|||||||
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
|
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
|
||||||
|
|
||||||
if self.party:
|
if self.party:
|
||||||
|
if not self.contact_person:
|
||||||
|
set_contact_details(
|
||||||
|
self, party=frappe._dict({"name": self.party}), party_type=self.party_type
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
complete_contact_details(self)
|
||||||
if not self.party_balance:
|
if not self.party_balance:
|
||||||
self.party_balance = get_balance_on(
|
self.party_balance = get_balance_on(
|
||||||
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
|
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
|
||||||
@@ -736,16 +743,39 @@ class PaymentEntry(AccountsController):
|
|||||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
|
outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
|
||||||
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
|
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
|
||||||
|
|
||||||
|
conversion_rate = frappe.db.get_value(key[2], {"name": key[1]}, "conversion_rate")
|
||||||
|
base_paid_amount_precision = get_field_precision(
|
||||||
|
frappe.get_meta("Payment Schedule").get_field("base_paid_amount")
|
||||||
|
)
|
||||||
|
base_outstanding_precision = get_field_precision(
|
||||||
|
frappe.get_meta("Payment Schedule").get_field("base_outstanding")
|
||||||
|
)
|
||||||
|
|
||||||
|
base_paid_amount = flt(
|
||||||
|
(allocated_amount - discounted_amt) * conversion_rate, base_paid_amount_precision
|
||||||
|
)
|
||||||
|
base_outstanding = flt(allocated_amount * conversion_rate, base_outstanding_precision)
|
||||||
|
|
||||||
if cancel:
|
if cancel:
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
UPDATE `tabPayment Schedule`
|
UPDATE `tabPayment Schedule`
|
||||||
SET
|
SET
|
||||||
paid_amount = `paid_amount` - %s,
|
paid_amount = `paid_amount` - %s,
|
||||||
|
base_paid_amount = `base_paid_amount` - %s,
|
||||||
discounted_amount = `discounted_amount` - %s,
|
discounted_amount = `discounted_amount` - %s,
|
||||||
outstanding = `outstanding` + %s
|
outstanding = `outstanding` + %s,
|
||||||
|
base_outstanding = `base_outstanding` - %s
|
||||||
WHERE parent = %s and payment_term = %s""",
|
WHERE parent = %s and payment_term = %s""",
|
||||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
|
(
|
||||||
|
allocated_amount - discounted_amt,
|
||||||
|
base_paid_amount,
|
||||||
|
discounted_amt,
|
||||||
|
allocated_amount,
|
||||||
|
base_outstanding,
|
||||||
|
key[1],
|
||||||
|
key[0],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if allocated_amount > outstanding:
|
if allocated_amount > outstanding:
|
||||||
@@ -761,10 +791,20 @@ class PaymentEntry(AccountsController):
|
|||||||
UPDATE `tabPayment Schedule`
|
UPDATE `tabPayment Schedule`
|
||||||
SET
|
SET
|
||||||
paid_amount = `paid_amount` + %s,
|
paid_amount = `paid_amount` + %s,
|
||||||
|
base_paid_amount = `base_paid_amount` + %s,
|
||||||
discounted_amount = `discounted_amount` + %s,
|
discounted_amount = `discounted_amount` + %s,
|
||||||
outstanding = `outstanding` - %s
|
outstanding = `outstanding` - %s,
|
||||||
|
base_outstanding = `base_outstanding` - %s
|
||||||
WHERE parent = %s and payment_term = %s""",
|
WHERE parent = %s and payment_term = %s""",
|
||||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
|
(
|
||||||
|
allocated_amount - discounted_amt,
|
||||||
|
base_paid_amount,
|
||||||
|
discounted_amt,
|
||||||
|
allocated_amount,
|
||||||
|
base_outstanding,
|
||||||
|
key[1],
|
||||||
|
key[0],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_allocated_amount_in_transaction_currency(
|
def get_allocated_amount_in_transaction_currency(
|
||||||
@@ -2879,7 +2919,7 @@ def get_payment_entry(
|
|||||||
pe.party_type = party_type
|
pe.party_type = party_type
|
||||||
pe.party = doc.get(scrub(party_type))
|
pe.party = doc.get(scrub(party_type))
|
||||||
pe.contact_person = doc.get("contact_person")
|
pe.contact_person = doc.get("contact_person")
|
||||||
pe.contact_email = doc.get("contact_email")
|
complete_contact_details(pe)
|
||||||
pe.ensure_supplier_is_not_blocked()
|
pe.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
pe.paid_from = party_account if payment_type == "Receive" else bank.account
|
pe.paid_from = party_account if payment_type == "Receive" else bank.account
|
||||||
|
|||||||
@@ -24,7 +24,9 @@
|
|||||||
"paid_amount",
|
"paid_amount",
|
||||||
"discounted_amount",
|
"discounted_amount",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"base_payment_amount"
|
"base_payment_amount",
|
||||||
|
"base_outstanding",
|
||||||
|
"base_paid_amount"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -155,19 +157,35 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Payment Amount (Company Currency)",
|
"label": "Payment Amount (Company Currency)",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Outstanding (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "base_paid_amount",
|
||||||
|
"fieldname": "base_paid_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-16 13:57:06.382859",
|
"modified": "2025-03-11 11:06:51.792982",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Schedule",
|
"name": "Payment Schedule",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"row_format": "Dynamic",
|
||||||
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class PaymentSchedule(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
base_outstanding: DF.Currency
|
||||||
|
base_paid_amount: DF.Currency
|
||||||
base_payment_amount: DF.Currency
|
base_payment_amount: DF.Currency
|
||||||
description: DF.SmallText | None
|
description: DF.SmallText | None
|
||||||
discount: DF.Float
|
discount: DF.Float
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from frappe import _, qb
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils.data import comma_and
|
from frappe.utils.data import comma_and
|
||||||
|
|
||||||
|
from erpnext.stock import get_warehouse_account_map
|
||||||
|
|
||||||
|
|
||||||
class RepostAccountingLedger(Document):
|
class RepostAccountingLedger(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@@ -97,6 +99,9 @@ class RepostAccountingLedger(Document):
|
|||||||
doc = frappe.get_doc(x.voucher_type, x.voucher_no)
|
doc = frappe.get_doc(x.voucher_type, x.voucher_no)
|
||||||
if doc.doctype in ["Payment Entry", "Journal Entry"]:
|
if doc.doctype in ["Payment Entry", "Journal Entry"]:
|
||||||
gle_map = doc.build_gl_map()
|
gle_map = doc.build_gl_map()
|
||||||
|
elif doc.doctype == "Purchase Receipt":
|
||||||
|
warehouse_account_map = get_warehouse_account_map(doc.company)
|
||||||
|
gle_map = doc.get_gl_entries(warehouse_account_map)
|
||||||
else:
|
else:
|
||||||
gle_map = doc.get_gl_entries()
|
gle_map = doc.get_gl_entries()
|
||||||
|
|
||||||
@@ -177,6 +182,14 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
doc.force_set_against_expense_account()
|
doc.force_set_against_expense_account()
|
||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
|
|
||||||
|
elif doc.doctype == "Purchase Receipt":
|
||||||
|
if not repost_doc.delete_cancelled_entries:
|
||||||
|
doc.docstatus = 2
|
||||||
|
doc.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
|
doc.docstatus = 1
|
||||||
|
doc.make_gl_entries(from_repost=True)
|
||||||
|
|
||||||
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
|
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
|
||||||
if not repost_doc.delete_cancelled_entries:
|
if not repost_doc.delete_cancelled_entries:
|
||||||
doc.make_gl_entries(1)
|
doc.make_gl_entries(1)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from erpnext.accounts.doctype.payment_request.payment_request import make_paymen
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt
|
||||||
|
|
||||||
|
|
||||||
class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||||
@@ -204,9 +206,81 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||||
|
|
||||||
|
def test_06_repost_purchase_receipt(self):
|
||||||
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
|
|
||||||
|
provisional_account = create_account(
|
||||||
|
account_name="Provision Account",
|
||||||
|
parent_account="Current Liabilities - _TC",
|
||||||
|
company=self.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
another_provisional_account = create_account(
|
||||||
|
account_name="Another Provision Account",
|
||||||
|
parent_account="Current Liabilities - _TC",
|
||||||
|
company=self.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
company = frappe.get_doc("Company", self.company)
|
||||||
|
company.enable_provisional_accounting_for_non_stock_items = 1
|
||||||
|
company.default_provisional_account = provisional_account
|
||||||
|
company.save()
|
||||||
|
|
||||||
|
test_cc = company.cost_center
|
||||||
|
default_expense_account = company.default_expense_account
|
||||||
|
|
||||||
|
item = make_item(properties={"is_stock_item": 0})
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0)
|
||||||
|
pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
|
||||||
|
expected_pr_gles = [
|
||||||
|
{"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
|
||||||
|
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
|
||||||
|
]
|
||||||
|
self.assertEqual(expected_pr_gles, pr_gl_entries)
|
||||||
|
|
||||||
|
# change the provisional account
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Purchase Receipt Item",
|
||||||
|
pr.items[0].name,
|
||||||
|
"provisional_expense_account",
|
||||||
|
another_provisional_account,
|
||||||
|
)
|
||||||
|
|
||||||
|
repost_doc = frappe.new_doc("Repost Accounting Ledger")
|
||||||
|
repost_doc.company = self.company
|
||||||
|
repost_doc.delete_cancelled_entries = True
|
||||||
|
repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name})
|
||||||
|
repost_doc.save().submit()
|
||||||
|
|
||||||
|
pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True)
|
||||||
|
expected_pr_gles_after_repost = [
|
||||||
|
{"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc},
|
||||||
|
{"account": another_provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc},
|
||||||
|
]
|
||||||
|
self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost))
|
||||||
|
self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
repost_doc.cancel()
|
||||||
|
repost_doc.delete()
|
||||||
|
|
||||||
|
pr.reload()
|
||||||
|
pr.cancel()
|
||||||
|
|
||||||
|
company.enable_provisional_accounting_for_non_stock_items = 0
|
||||||
|
company.default_provisional_account = None
|
||||||
|
company.save()
|
||||||
|
|
||||||
|
|
||||||
def update_repost_settings():
|
def update_repost_settings():
|
||||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
allowed_types = [
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
"Purchase Receipt",
|
||||||
|
]
|
||||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||||
for x in allowed_types:
|
for x in allowed_types:
|
||||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||||
|
|||||||
@@ -1819,17 +1819,6 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
for field in expected_gle:
|
for field in expected_gle:
|
||||||
self.assertEqual(expected_gle[field], gle[field])
|
self.assertEqual(expected_gle[field], gle[field])
|
||||||
|
|
||||||
def test_invoice_exchange_rate(self):
|
|
||||||
si = create_sales_invoice(
|
|
||||||
customer="_Test Customer USD",
|
|
||||||
debit_to="_Test Receivable USD - _TC",
|
|
||||||
currency="USD",
|
|
||||||
conversion_rate=1,
|
|
||||||
do_not_save=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, si.save)
|
|
||||||
|
|
||||||
def test_invalid_currency(self):
|
def test_invalid_currency(self):
|
||||||
# Customer currency = USD
|
# Customer currency = USD
|
||||||
|
|
||||||
|
|||||||
@@ -279,9 +279,7 @@ def get_regional_address_details(party_details, doctype, company):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def set_contact_details(party_details, party, party_type):
|
def complete_contact_details(party_details):
|
||||||
party_details.contact_person = get_default_contact(party_type, party.name)
|
|
||||||
|
|
||||||
if not party_details.contact_person:
|
if not party_details.contact_person:
|
||||||
party_details.update(
|
party_details.update(
|
||||||
{
|
{
|
||||||
@@ -310,6 +308,11 @@ def set_contact_details(party_details, party, party_type):
|
|||||||
party_details.update(contact_details)
|
party_details.update(contact_details)
|
||||||
|
|
||||||
|
|
||||||
|
def set_contact_details(party_details, party, party_type):
|
||||||
|
party_details.contact_person = get_default_contact(party_type, party.name)
|
||||||
|
complete_contact_details(party_details)
|
||||||
|
|
||||||
|
|
||||||
def set_other_values(party_details, party, party_type):
|
def set_other_values(party_details, party, party_type):
|
||||||
# copy
|
# copy
|
||||||
if party_type == "Customer":
|
if party_type == "Customer":
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ class ReceivablePayableReport:
|
|||||||
select
|
select
|
||||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||||
si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
|
si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount,
|
||||||
ps.description, ps.paid_amount, ps.discounted_amount
|
ps.description, ps.paid_amount, ps.base_paid_amount, ps.discounted_amount
|
||||||
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
||||||
where
|
where
|
||||||
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
|
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
|
||||||
@@ -540,20 +540,24 @@ class ReceivablePayableReport:
|
|||||||
# Deduct that from paid amount pre allocation
|
# Deduct that from paid amount pre allocation
|
||||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||||
|
|
||||||
|
company_currency = frappe.get_value("Company", self.filters.get("company"), "default_currency")
|
||||||
|
|
||||||
# If single payment terms, no need to split the row
|
# If single payment terms, no need to split the row
|
||||||
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
|
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
|
||||||
self.append_payment_term(row, payment_terms_details[0], original_row)
|
self.append_payment_term(row, payment_terms_details[0], original_row, company_currency)
|
||||||
return
|
return
|
||||||
|
|
||||||
for d in payment_terms_details:
|
for d in payment_terms_details:
|
||||||
term = frappe._dict(original_row)
|
term = frappe._dict(original_row)
|
||||||
self.append_payment_term(row, d, term)
|
self.append_payment_term(row, d, term, company_currency)
|
||||||
|
|
||||||
def append_payment_term(self, row, d, term):
|
def append_payment_term(self, row, d, term, company_currency):
|
||||||
if d.currency == d.party_account_currency:
|
invoiced = d.base_payment_amount
|
||||||
|
paid_amount = d.base_paid_amount
|
||||||
|
|
||||||
|
if company_currency == d.party_account_currency or self.filters.get("in_party_currency"):
|
||||||
invoiced = d.payment_amount
|
invoiced = d.payment_amount
|
||||||
else:
|
paid_amount = d.paid_amount
|
||||||
invoiced = d.base_payment_amount
|
|
||||||
|
|
||||||
row.payment_terms.append(
|
row.payment_terms.append(
|
||||||
term.update(
|
term.update(
|
||||||
@@ -562,15 +566,15 @@ class ReceivablePayableReport:
|
|||||||
"invoiced": invoiced,
|
"invoiced": invoiced,
|
||||||
"invoice_grand_total": row.invoiced,
|
"invoice_grand_total": row.invoiced,
|
||||||
"payment_term": d.description or d.payment_term,
|
"payment_term": d.description or d.payment_term,
|
||||||
"paid": d.paid_amount + d.discounted_amount,
|
"paid": paid_amount + d.discounted_amount,
|
||||||
"credit_note": 0.0,
|
"credit_note": 0.0,
|
||||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount,
|
"outstanding": invoiced - paid_amount - d.discounted_amount,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if d.paid_amount:
|
if paid_amount:
|
||||||
row["paid"] -= d.paid_amount + d.discounted_amount
|
row["paid"] -= paid_amount + d.discounted_amount
|
||||||
|
|
||||||
def allocate_closing_to_term(self, row, term, key):
|
def allocate_closing_to_term(self, row, term, key):
|
||||||
if row[key]:
|
if row[key]:
|
||||||
|
|||||||
@@ -145,6 +145,130 @@ def get_asset_categories_for_grouped_by_category(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_assets_for_grouped_by_category(filters):
|
||||||
|
condition = ""
|
||||||
|
if filters.get("asset_category"):
|
||||||
|
condition = f" and a.asset_category = '{filters.get('asset_category')}'"
|
||||||
|
finance_book_filter = ""
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s"
|
||||||
|
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
|
||||||
|
|
||||||
|
# nosemgrep
|
||||||
|
return frappe.db.sql(
|
||||||
|
f"""
|
||||||
|
SELECT results.asset_category,
|
||||||
|
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
||||||
|
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
|
||||||
|
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
|
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||||
|
from (SELECT a.asset_category,
|
||||||
|
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
|
||||||
|
gle.credit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_via_reversal,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||||
|
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
|
||||||
|
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_amount_during_the_period
|
||||||
|
from `tabGL Entry` gle
|
||||||
|
join `tabAsset` a on
|
||||||
|
gle.against_voucher = a.name
|
||||||
|
join `tabAsset Category Account` aca on
|
||||||
|
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||||
|
join `tabCompany` company on
|
||||||
|
company.name = %(company)s
|
||||||
|
where
|
||||||
|
a.docstatus=1
|
||||||
|
and a.company=%(company)s
|
||||||
|
and a.purchase_date <= %(to_date)s
|
||||||
|
and gle.is_cancelled = 0
|
||||||
|
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||||
|
{condition} {finance_book_filter}
|
||||||
|
group by a.asset_category
|
||||||
|
union
|
||||||
|
SELECT a.asset_category,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
0 as depreciation_eliminated_via_reversal,
|
||||||
|
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
0 as depreciation_amount_during_the_period
|
||||||
|
from `tabAsset` a
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
|
||||||
|
group by a.asset_category) as results
|
||||||
|
group by results.asset_category
|
||||||
|
""",
|
||||||
|
{
|
||||||
|
"to_date": filters.to_date,
|
||||||
|
"from_date": filters.from_date,
|
||||||
|
"company": filters.company,
|
||||||
|
"finance_book": filters.get("finance_book", ""),
|
||||||
|
},
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_by_asset_data(filters):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||||
|
assets = get_assets_for_grouped_by_asset(filters)
|
||||||
|
|
||||||
|
for asset_detail in asset_details:
|
||||||
|
row = frappe._dict()
|
||||||
|
row.update(asset_detail)
|
||||||
|
|
||||||
|
row.value_as_on_to_date = (
|
||||||
|
flt(row.value_as_on_from_date)
|
||||||
|
+ flt(row.value_of_new_purchase)
|
||||||
|
- flt(row.value_of_sold_asset)
|
||||||
|
- flt(row.value_of_scrapped_asset)
|
||||||
|
- flt(row.value_of_capitalized_asset)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||||
|
|
||||||
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
|
+ flt(row.depreciation_amount_during_the_period)
|
||||||
|
- flt(row.depreciation_eliminated_during_the_period)
|
||||||
|
- flt(row.depreciation_eliminated_via_reversal)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_from_date
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_asset_details_for_grouped_by_category(filters):
|
def get_asset_details_for_grouped_by_category(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset"):
|
if filters.get("asset"):
|
||||||
@@ -224,130 +348,6 @@ def get_asset_details_for_grouped_by_category(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_group_by_asset_data(filters):
|
|
||||||
data = []
|
|
||||||
|
|
||||||
asset_details = get_asset_details_for_grouped_by_category(filters)
|
|
||||||
assets = get_assets_for_grouped_by_asset(filters)
|
|
||||||
|
|
||||||
for asset_detail in asset_details:
|
|
||||||
row = frappe._dict()
|
|
||||||
row.update(asset_detail)
|
|
||||||
|
|
||||||
row.value_as_on_to_date = (
|
|
||||||
flt(row.value_as_on_from_date)
|
|
||||||
+ flt(row.value_of_new_purchase)
|
|
||||||
- flt(row.value_of_sold_asset)
|
|
||||||
- flt(row.value_of_scrapped_asset)
|
|
||||||
- flt(row.value_of_capitalized_asset)
|
|
||||||
)
|
|
||||||
|
|
||||||
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
|
||||||
|
|
||||||
row.accumulated_depreciation_as_on_to_date = (
|
|
||||||
flt(row.accumulated_depreciation_as_on_from_date)
|
|
||||||
+ flt(row.depreciation_amount_during_the_period)
|
|
||||||
- flt(row.depreciation_eliminated_during_the_period)
|
|
||||||
- flt(row.depreciation_eliminated_via_reversal)
|
|
||||||
)
|
|
||||||
|
|
||||||
row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt(
|
|
||||||
row.accumulated_depreciation_as_on_from_date
|
|
||||||
)
|
|
||||||
|
|
||||||
row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt(
|
|
||||||
row.accumulated_depreciation_as_on_to_date
|
|
||||||
)
|
|
||||||
|
|
||||||
data.append(row)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def get_assets_for_grouped_by_category(filters):
|
|
||||||
condition = ""
|
|
||||||
if filters.get("asset_category"):
|
|
||||||
condition = f" and a.asset_category = '{filters.get('asset_category')}'"
|
|
||||||
finance_book_filter = ""
|
|
||||||
if filters.get("finance_book"):
|
|
||||||
finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s"
|
|
||||||
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
|
|
||||||
|
|
||||||
# nosemgrep
|
|
||||||
return frappe.db.sql(
|
|
||||||
f"""
|
|
||||||
SELECT results.asset_category,
|
|
||||||
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
|
||||||
sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal,
|
|
||||||
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
|
||||||
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
|
||||||
from (SELECT a.asset_category,
|
|
||||||
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
|
||||||
gle.debit
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as accumulated_depreciation_as_on_from_date,
|
|
||||||
ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then
|
|
||||||
gle.credit
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_eliminated_via_reversal,
|
|
||||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
|
||||||
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
|
|
||||||
gle.debit
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_eliminated_during_the_period,
|
|
||||||
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
|
|
||||||
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
|
|
||||||
gle.debit
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_amount_during_the_period
|
|
||||||
from `tabGL Entry` gle
|
|
||||||
join `tabAsset` a on
|
|
||||||
gle.against_voucher = a.name
|
|
||||||
join `tabAsset Category Account` aca on
|
|
||||||
aca.parent = a.asset_category and aca.company_name = %(company)s
|
|
||||||
join `tabCompany` company on
|
|
||||||
company.name = %(company)s
|
|
||||||
where
|
|
||||||
a.docstatus=1
|
|
||||||
and a.company=%(company)s
|
|
||||||
and a.purchase_date <= %(to_date)s
|
|
||||||
and gle.is_cancelled = 0
|
|
||||||
and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
|
||||||
{condition} {finance_book_filter}
|
|
||||||
group by a.asset_category
|
|
||||||
union
|
|
||||||
SELECT a.asset_category,
|
|
||||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
|
||||||
0
|
|
||||||
else
|
|
||||||
a.opening_accumulated_depreciation
|
|
||||||
end), 0) as accumulated_depreciation_as_on_from_date,
|
|
||||||
0 as depreciation_eliminated_via_reversal,
|
|
||||||
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
|
|
||||||
a.opening_accumulated_depreciation
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_eliminated_during_the_period,
|
|
||||||
0 as depreciation_amount_during_the_period
|
|
||||||
from `tabAsset` a
|
|
||||||
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition}
|
|
||||||
group by a.asset_category) as results
|
|
||||||
group by results.asset_category
|
|
||||||
""",
|
|
||||||
{
|
|
||||||
"to_date": filters.to_date,
|
|
||||||
"from_date": filters.from_date,
|
|
||||||
"company": filters.company,
|
|
||||||
"finance_book": filters.get("finance_book", ""),
|
|
||||||
},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_assets_for_grouped_by_asset(filters):
|
def get_assets_for_grouped_by_asset(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset"):
|
if filters.get("asset"):
|
||||||
@@ -405,7 +405,7 @@ def get_assets_for_grouped_by_asset(filters):
|
|||||||
group by a.name
|
group by a.name
|
||||||
union
|
union
|
||||||
SELECT a.name as name,
|
SELECT a.name as name,
|
||||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then
|
||||||
0
|
0
|
||||||
else
|
else
|
||||||
a.opening_accumulated_depreciation
|
a.opening_accumulated_depreciation
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import frappe
|
|||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe import _, qb, throw
|
from frappe import _, qb, throw
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import AliasedQuery, Criterion, Table
|
from frappe.query_builder import AliasedQuery, Case, Criterion, Table
|
||||||
from frappe.query_builder.functions import Count, Sum
|
from frappe.query_builder.functions import Count, Max, Sum
|
||||||
from frappe.query_builder.utils import DocType
|
from frappe.query_builder.utils import DocType
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
@@ -1974,6 +1974,15 @@ class QueryPaymentLedger:
|
|||||||
.select(
|
.select(
|
||||||
ple.against_voucher_no.as_("voucher_no"),
|
ple.against_voucher_no.as_("voucher_no"),
|
||||||
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
||||||
|
Max(
|
||||||
|
Case().when(
|
||||||
|
(
|
||||||
|
(ple.voucher_no == ple.against_voucher_no)
|
||||||
|
& (ple.voucher_type == ple.against_voucher_type)
|
||||||
|
),
|
||||||
|
(ple.posting_date),
|
||||||
|
)
|
||||||
|
).as_("invoice_date"),
|
||||||
)
|
)
|
||||||
.where(ple.delinked == 0)
|
.where(ple.delinked == 0)
|
||||||
.where(Criterion.all(filter_on_against_voucher_no))
|
.where(Criterion.all(filter_on_against_voucher_no))
|
||||||
@@ -1981,7 +1990,7 @@ class QueryPaymentLedger:
|
|||||||
.where(Criterion.all(self.dimensions_filter))
|
.where(Criterion.all(self.dimensions_filter))
|
||||||
.where(Criterion.all(self.voucher_posting_date))
|
.where(Criterion.all(self.voucher_posting_date))
|
||||||
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
||||||
.orderby(ple.posting_date, ple.voucher_no)
|
.orderby(ple.invoice_date, ple.voucher_no)
|
||||||
.having(qb.Field("amount_in_account_currency") > 0)
|
.having(qb.Field("amount_in_account_currency") > 0)
|
||||||
.limit(self.limit)
|
.limit(self.limit)
|
||||||
.run()
|
.run()
|
||||||
|
|||||||
@@ -2363,6 +2363,9 @@ class AccountsController(TransactionBase):
|
|||||||
base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount")
|
base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount")
|
||||||
)
|
)
|
||||||
d.outstanding = d.payment_amount
|
d.outstanding = d.payment_amount
|
||||||
|
d.base_outstanding = flt(
|
||||||
|
d.payment_amount * self.get("conversion_rate"), d.precision("base_outstanding")
|
||||||
|
)
|
||||||
elif not d.invoice_portion:
|
elif not d.invoice_portion:
|
||||||
d.base_payment_amount = flt(
|
d.base_payment_amount = flt(
|
||||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
||||||
@@ -2689,12 +2692,17 @@ class AccountsController(TransactionBase):
|
|||||||
default_currency = erpnext.get_company_currency(self.company)
|
default_currency = erpnext.get_company_currency(self.company)
|
||||||
if not default_currency:
|
if not default_currency:
|
||||||
throw(_("Please enter default currency in Company Master"))
|
throw(_("Please enter default currency in Company Master"))
|
||||||
if (
|
|
||||||
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
|
if not self.conversion_rate:
|
||||||
or not self.conversion_rate
|
throw(_("Conversion rate cannot be 0"))
|
||||||
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
|
|
||||||
):
|
if self.currency == default_currency and flt(self.conversion_rate) != 1.00:
|
||||||
throw(_("Conversion rate cannot be 0 or 1"))
|
throw(_("Conversion rate must be 1.00 if document currency is same as company currency"))
|
||||||
|
|
||||||
|
if self.currency != default_currency and flt(self.conversion_rate) == 1.00:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Conversion rate is 1.00, but document currency is different from company currency")
|
||||||
|
)
|
||||||
|
|
||||||
def check_finance_books(self, item, asset):
|
def check_finance_books(self, item, asset):
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def get_transaction_list(
|
|||||||
filters=None,
|
filters=None,
|
||||||
limit_start=0,
|
limit_start=0,
|
||||||
limit_page_length=20,
|
limit_page_length=20,
|
||||||
order_by="modified",
|
order_by="creation desc",
|
||||||
custom=False,
|
custom=False,
|
||||||
):
|
):
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
@@ -115,7 +115,7 @@ def get_transaction_list(
|
|||||||
limit_page_length,
|
limit_page_length,
|
||||||
fields="name",
|
fields="name",
|
||||||
ignore_permissions=ignore_permissions,
|
ignore_permissions=ignore_permissions,
|
||||||
order_by="modified desc",
|
order_by=order_by,
|
||||||
)
|
)
|
||||||
|
|
||||||
if custom:
|
if custom:
|
||||||
|
|||||||
@@ -397,7 +397,8 @@ erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
|
|||||||
erpnext.patches.v14_0.update_posting_datetime
|
erpnext.patches.v14_0.update_posting_datetime
|
||||||
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
||||||
erpnext.patches.v15_0.update_query_report
|
erpnext.patches.v15_0.update_query_report
|
||||||
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18
|
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
||||||
erpnext.patches.v15_0.recalculate_amount_difference_field
|
erpnext.patches.v15_0.recalculate_amount_difference_field #2025-03-18
|
||||||
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
||||||
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
||||||
|
erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.query_builder import DocType
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
invoice_types = ["Sales Invoice", "Purchase Invoice"]
|
||||||
|
for invoice_type in invoice_types:
|
||||||
|
invoice = DocType(invoice_type)
|
||||||
|
invoice_details = frappe.qb.from_(invoice).select(invoice.conversion_rate, invoice.name)
|
||||||
|
update_payment_schedule(invoice_details)
|
||||||
|
|
||||||
|
|
||||||
|
def update_payment_schedule(invoice_details):
|
||||||
|
ps = DocType("Payment Schedule")
|
||||||
|
|
||||||
|
frappe.qb.update(ps).join(invoice_details).on(ps.parent == invoice_details.name).set(
|
||||||
|
ps.base_paid_amount, ps.paid_amount * invoice_details.conversion_rate
|
||||||
|
).set(ps.base_outstanding, ps.outstanding * invoice_details.conversion_rate).run()
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"full_name",
|
"full_name",
|
||||||
"welcome_email_sent",
|
"welcome_email_sent",
|
||||||
"view_attachments",
|
"view_attachments",
|
||||||
|
"hide_timesheets",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"project_status"
|
"project_status"
|
||||||
],
|
],
|
||||||
@@ -64,6 +65,13 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "View attachments"
|
"label": "View attachments"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hide_timesheets",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Hide timesheets"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_5",
|
"fieldname": "section_break_5",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
|||||||
@@ -447,22 +447,21 @@ erpnext.sales_common = {
|
|||||||
args: { project: this.frm.doc.project },
|
args: { project: this.frm.doc.project },
|
||||||
callback: function (r, rt) {
|
callback: function (r, rt) {
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
$.each(me.frm.doc["items"] || [], function (i, row) {
|
if (r.message) {
|
||||||
if (r.message) {
|
$.each(me.frm.doc["items"] || [], function (i, row) {
|
||||||
frappe.model.set_value(
|
frappe.model.set_value(
|
||||||
row.doctype,
|
row.doctype,
|
||||||
row.name,
|
row.name,
|
||||||
"cost_center",
|
"cost_center",
|
||||||
r.message
|
r.message
|
||||||
);
|
);
|
||||||
frappe.msgprint(
|
});
|
||||||
__(
|
frappe.msgprint(
|
||||||
"Cost Center For Item with Item Code {0} has been Changed to {1}",
|
__("Cost Center for Item rows has been updated to {0}", [
|
||||||
[row.item_name, r.message]
|
r.message,
|
||||||
)
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,8 +56,8 @@
|
|||||||
{{ empty_state(_("Task")) }}
|
{{ empty_state(_("Task")) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h4 class="my-account-header">{{ _("Timesheets") }}</h4>
|
|
||||||
{% if doc.timesheets %}
|
{% if doc.timesheets %}
|
||||||
|
<h4 class="my-account-header">{{ _("Timesheets") }}</h4>
|
||||||
<div class="website-list">
|
<div class="website-list">
|
||||||
<div class="result">
|
<div class="result">
|
||||||
<div class="web-list-item transaction-list-item">
|
<div class="web-list-item transaction-list-item">
|
||||||
@@ -73,8 +73,6 @@
|
|||||||
{% include "erpnext/templates/includes/projects/project_timesheets.html" %}
|
{% include "erpnext/templates/includes/projects/project_timesheets.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
{{ empty_state(_("Timesheet")) }}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if doc.attachments %}
|
{% if doc.attachments %}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ def get_context(context):
|
|||||||
project_user = frappe.db.get_value(
|
project_user = frappe.db.get_value(
|
||||||
"Project User",
|
"Project User",
|
||||||
{"parent": frappe.form_dict.project, "user": frappe.session.user},
|
{"parent": frappe.form_dict.project, "user": frappe.session.user},
|
||||||
["user", "view_attachments"],
|
["user", "view_attachments", "hide_timesheets"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
if frappe.session.user != "Administrator" and (not project_user or frappe.session.user == "Guest"):
|
if frappe.session.user != "Administrator" and (not project_user or frappe.session.user == "Guest"):
|
||||||
@@ -25,7 +25,8 @@ def get_context(context):
|
|||||||
project.name, start=0, item_status="open", search=frappe.form_dict.get("search")
|
project.name, start=0, item_status="open", search=frappe.form_dict.get("search")
|
||||||
)
|
)
|
||||||
|
|
||||||
project.timesheets = get_timesheets(project.name, start=0, search=frappe.form_dict.get("search"))
|
if project_user and not project_user.hide_timesheets:
|
||||||
|
project.timesheets = get_timesheets(project.name, start=0, search=frappe.form_dict.get("search"))
|
||||||
|
|
||||||
if project_user and project_user.view_attachments:
|
if project_user and project_user.view_attachments:
|
||||||
project.attachments = get_attachments(project.name)
|
project.attachments = get_attachments(project.name)
|
||||||
|
|||||||
Reference in New Issue
Block a user