Merge branch 'develop' of https://github.com/frappe/erpnext into copy-emails-to-customer

This commit is contained in:
Deepesh Garg 2024-01-26 09:39:13 +05:30
commit 722ee53fb1
215 changed files with 64572 additions and 86360 deletions

View File

@ -20,6 +20,18 @@ jobs:
- name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0
semgrep:
name: semgrep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: pip
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules

22
.github/workflows/patch_faux.yml vendored Normal file
View File

@ -0,0 +1,22 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Patch Test
on:
pull_request:
paths:
- "**.js"
- "**.css"
- "**.md"
- "**.html"
- "**.csv"
jobs:
test:
runs-on: ubuntu-latest
name: Patch Test
steps:
- name: Pass skipped tests unconditionally
run: "echo Skipped"

View File

@ -16,7 +16,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18
node-version: 20
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save

View File

@ -0,0 +1,24 @@
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
name: Skipped Tests
on:
pull_request:
paths:
- "**.js"
- "**.css"
- "**.md"
- "**.html"
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
container: [1, 2, 3, 4]
name: Python Unit Tests
steps:
- name: Pass skipped tests unconditionally
run: "echo Skipped"

3
crowdin.yml Normal file
View File

@ -0,0 +1,3 @@
files:
- source: /erpnext/locale/main.pot
translation: /erpnext/locale/%two_letters_code%.po

View File

@ -358,11 +358,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice":
against_type = "Customer"
against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account
else:
against_type = "Supplier"
against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account
@ -415,7 +413,6 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
doc,
credit_account,
debit_account,
against_type,
against,
amount,
base_amount,
@ -497,7 +494,6 @@ def make_gl_entries(
doc,
credit_account,
debit_account,
against_type,
against,
amount,
base_amount,
@ -519,9 +515,7 @@ def make_gl_entries(
doc.get_gl_dict(
{
"account": credit_account,
"against_type": against_type,
"against": against,
"against_link": against,
"credit": base_amount,
"credit_in_account_currency": amount,
"cost_center": cost_center,
@ -540,9 +534,7 @@ def make_gl_entries(
doc.get_gl_dict(
{
"account": debit_account,
"against_type": against_type,
"against": against,
"against_link": against,
"debit": base_amount,
"debit_in_account_currency": amount,
"cost_center": cost_center,

View File

@ -108,6 +108,7 @@
"fieldname": "parent_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_preview": 1,
"label": "Parent Account",
"oldfieldname": "parent_account",
"oldfieldtype": "Link",
@ -192,7 +193,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2023-07-20 18:18:44.405723",
"modified": "2024-01-10 04:57:33.681676",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@ -249,8 +250,9 @@
],
"search_fields": "account_number",
"show_name_in_global_search": 1,
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@ -36,16 +36,16 @@
}
},
"Fixed Assets": {
"Capital Equipments": {
"Capital Equipment": {
"account_type": "Fixed Asset"
},
"Electronic Equipments": {
"Electronic Equipment": {
"account_type": "Fixed Asset"
},
"Furnitures and Fixtures": {
"Furniture and Fixtures": {
"account_type": "Fixed Asset"
},
"Office Equipments": {
"Office Equipment": {
"account_type": "Fixed Asset"
},
"Plants and Machineries": {

View File

@ -23,13 +23,13 @@ def get():
_("Tax Assets"): {"is_group": 1},
},
_("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset"},
_("Electronic Equipments"): {"account_type": "Fixed Asset"},
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"},
_("Office Equipments"): {"account_type": "Fixed Asset"},
_("Capital Equipment"): {"account_type": "Fixed Asset"},
_("Electronic Equipment"): {"account_type": "Fixed Asset"},
_("Furniture and Fixtures"): {"account_type": "Fixed Asset"},
_("Office Equipment"): {"account_type": "Fixed Asset"},
_("Plants and Machineries"): {"account_type": "Fixed Asset"},
_("Buildings"): {"account_type": "Fixed Asset"},
_("Softwares"): {"account_type": "Fixed Asset"},
_("Software"): {"account_type": "Fixed Asset"},
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"},
_("CWIP Account"): {
"account_type": "Capital Work in Progress",

View File

@ -36,13 +36,13 @@ def get():
"account_number": "1100-1600",
},
_("Fixed Assets"): {
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"},
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"},
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"},
_("Capital Equipment"): {"account_type": "Fixed Asset", "account_number": "1710"},
_("Electronic Equipment"): {"account_type": "Fixed Asset", "account_number": "1720"},
_("Furniture and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
_("Office Equipment"): {"account_type": "Fixed Asset", "account_number": "1740"},
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"},
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"},
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"},
_("Software"): {"account_type": "Fixed Asset", "account_number": "1770"},
_("Accumulated Depreciation"): {
"account_type": "Accumulated Depreciation",
"account_number": "1780",

View File

@ -119,7 +119,7 @@ class TestAccount(unittest.TestCase):
InvalidAccountMergeError,
merge_account,
"Capital Stock - _TC",
"Softwares - _TC",
"Software - _TC",
)
# Raise error as currency doesn't match

View File

@ -55,7 +55,7 @@ class BankAccount(Document):
def validate_company(self):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
frappe.throw(_("Company is mandatory for company account"))
def validate_iban(self):
"""

View File

@ -48,11 +48,11 @@ class BankGuarantee(Document):
def on_submit(self):
if not self.bank_guarantee_number:
frappe.throw(_("Enter the Bank Guarantee Number before submittting."))
frappe.throw(_("Enter the Bank Guarantee Number before submitting."))
if not self.name_of_beneficiary:
frappe.throw(_("Enter the name of the Beneficiary before submittting."))
frappe.throw(_("Enter the name of the Beneficiary before submitting."))
if not self.bank:
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
frappe.throw(_("Enter the name of the bank or lending institution before submitting."))
@frappe.whitelist()

View File

@ -76,6 +76,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
"deposit": 100,
"bank_account": self.bank_account,
"reference_number": "123",
"currency": "INR",
}
)
.save()

View File

@ -3,6 +3,7 @@
import frappe
from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt
@ -48,6 +49,24 @@ class BankTransaction(Document):
def validate(self):
self.validate_duplicate_references()
self.validate_currency()
def validate_currency(self):
"""
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
)
)
def set_status(self):
if self.docstatus == 2:
@ -415,3 +434,21 @@ def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):
bt = frappe.get_doc("Bank Transaction", bt_name)
if bt.docstatus == DocStatus.cancelled():
continue
modified = False
for pe in bt.payment_entries:
if pe.payment_document == doctype and pe.payment_entry == docname:
bt.remove(pe)
modified = True
if modified:
bt.save()

View File

@ -2,10 +2,10 @@
# See license.txt
import json
import unittest
import frappe
from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@ -81,6 +81,29 @@ class TestBankTransaction(FrappeTestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertFalse(clearance_date)
def test_cancel_voucher(self):
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
payment.reload()
payment.cancel()
bank_transaction.reload()
self.assertEqual(bank_transaction.docstatus, DocStatus.submitted())
self.assertEqual(bank_transaction.unallocated_amount, 1700)
self.assertEqual(bank_transaction.payment_entries, [])
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc(

View File

@ -80,7 +80,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto"
"label": "Valid Up To"
},
{
"depends_on": "eval: doc.coupon_type == \"Promotional\"",
@ -115,7 +115,7 @@
"read_only": 1
}
],
"modified": "2019-10-19 14:48:14.602481",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Coupon Code",

View File

@ -82,11 +82,11 @@
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
"modified": "2020-11-05 12:16:53.081573",
"modified": "2024-01-17 13:06:01.608953",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
"owner": "Administrator",
"owner": "Administrator",
"permissions": [
{
"create": 1,
@ -118,6 +118,14 @@
{
"read": 1,
"role": "Employee"
},
{
"read": 1,
"role": "Accounts Manager"
},
{
"read": 1,
"role": "Stock Manager"
}
],
"show_name_in_global_search": 1,

View File

@ -39,7 +39,7 @@ def test_record_generator():
]
start = 2012
end = now_datetime().year + 5
end = now_datetime().year + 25
for year in range(start, end):
test_records.append(
{

View File

@ -17,9 +17,7 @@
"account_currency",
"debit_in_account_currency",
"credit_in_account_currency",
"against_type",
"against",
"against_link",
"against_voucher_type",
"against_voucher",
"voucher_type",
@ -131,13 +129,6 @@
"label": "Credit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "against_type",
"fieldtype": "Link",
"in_filter": 1,
"label": "Against Type",
"options": "DocType"
},
{
"fieldname": "against",
"fieldtype": "Text",
@ -146,13 +137,6 @@
"oldfieldname": "against",
"oldfieldtype": "Text"
},
{
"fieldname": "against_link",
"fieldtype": "Dynamic Link",
"in_filter": 1,
"label": "Against",
"options": "against_type"
},
{
"fieldname": "against_voucher_type",
"fieldtype": "Link",
@ -306,7 +290,7 @@
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2023-12-18 15:38:14.006208",
"modified": "2023-09-26 12:03:23.031733",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",

View File

@ -154,7 +154,7 @@ frappe.ui.form.on('Invoice Discounting', {
}
});
},
primary_action_label: __('Get Invocies')
primary_action_label: __('Get Invoices')
});
d.show();
},

View File

@ -153,9 +153,7 @@ class InvoiceDiscounting(AccountsController):
"account": inv.debit_to,
"party_type": "Customer",
"party": d.customer,
"against_type": "Account",
"against": self.accounts_receivable_credit,
"against_link": self.accounts_receivable_credit,
"credit": outstanding_in_company_currency,
"credit_in_account_currency": outstanding_in_company_currency
if inv.party_account_currency == company_currency
@ -175,9 +173,7 @@ class InvoiceDiscounting(AccountsController):
"account": self.accounts_receivable_credit,
"party_type": "Customer",
"party": d.customer,
"against_type": "Account",
"against": inv.debit_to,
"against_link": inv.debit_to,
"debit": outstanding_in_company_currency,
"debit_in_account_currency": outstanding_in_company_currency
if ar_credit_account_currency == company_currency

View File

@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Bank Transaction"];
},
refresh: function(frm) {
@ -220,16 +220,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
return erpnext.journal_entry.account_query(me.frm);
});
me.frm.set_query("against_account_link", "accounts", function(doc, cdt, cdn) {
return erpnext.journal_entry.against_account_query(me.frm);
});
me.frm.set_query("against_type", "accounts", function(){
return {
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_type",
}
})
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
@ -601,21 +591,6 @@ $.extend(erpnext.journal_entry, {
return { filters: filters };
},
against_account_query: function(frm) {
if (frm.doc.against_type != "Account"){
return { filters: {} };
}
else {
let filters = { company: frm.doc.company, is_group: 0 };
if(!frm.doc.multi_currency) {
$.extend(filters, {
account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
});
}
return { filters: filters };
}
},
reverse_journal_entry: function() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",

View File

@ -186,9 +186,12 @@ class JournalEntry(AccountsController):
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in frappe.get_hooks("advance_payment_doctypes"):
if d.reference_type in advance_payment_doctypes:
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
@ -304,7 +307,6 @@ class JournalEntry(AccountsController):
"account": tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
"against_account": parties[0],
"against_account_link": parties[0],
},
)
@ -751,90 +753,27 @@ class JournalEntry(AccountsController):
)
def set_against_account(self):
accounts_debited, accounts_credited = [], []
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
for d in self.get("accounts"):
if d.reference_type == "Sales Invoice":
against_type = "Customer"
field = "customer"
else:
against_type = "Supplier"
field = "supplier"
against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower())
d.against_type = against_type
d.against_account_link = against_account
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
else:
self.get_debited_credited_accounts()
if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1:
self.auto_set_against_accounts()
return
self.get_against_accounts()
for d in self.get("accounts"):
if flt(d.debit) > 0:
accounts_debited.append(d.party or d.account)
if flt(d.credit) > 0:
accounts_credited.append(d.party or d.account)
def auto_set_against_accounts(self):
for i in range(0, len(self.accounts), 2):
acc = self.accounts[i]
against_acc = self.accounts[i + 1]
if acc.debit_in_account_currency > 0:
current_val = acc.debit_in_account_currency * flt(acc.exchange_rate)
against_val = against_acc.credit_in_account_currency * flt(against_acc.exchange_rate)
else:
current_val = acc.credit_in_account_currency * flt(acc.exchange_rate)
against_val = against_acc.debit_in_account_currency * flt(against_acc.exchange_rate)
if current_val == against_val:
acc.against_type = against_acc.party_type or "Account"
against_acc.against_type = acc.party_type or "Account"
acc.against_account_link = against_acc.party or against_acc.account
against_acc.against_account_link = acc.party or acc.account
else:
frappe.msgprint(
_(
"Unable to automatically determine {0} accounts. Set them up in the {1} table if needed."
).format(frappe.bold("against"), frappe.bold("Accounting Entries")),
alert=True,
)
break
def get_against_accounts(self):
self.against_accounts = []
self.split_account = {}
self.get_debited_credited_accounts()
if self.separate_against_account_entries:
no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited)
if no_of_credited_acc <= 1 and no_of_debited_acc <= 1:
self.set_against_accounts_for_single_dr_cr()
self.separate_against_account_entries = 0
elif no_of_credited_acc == 1:
self.against_accounts = self.accounts_debited
self.split_account = self.accounts_credited[0]
elif no_of_debited_acc == 1:
self.against_accounts = self.accounts_credited
self.split_account = self.accounts_debited[0]
def get_debited_credited_accounts(self):
self.accounts_debited, self.accounts_credited = [], []
self.separate_against_account_entries = 1
for d in self.get("accounts"):
if flt(d.debit) > 0:
self.accounts_debited.append(d)
elif flt(d.credit) > 0:
self.accounts_credited.append(d)
if d.against_account_link:
self.separate_against_account_entries = 0
break
def set_against_accounts_for_single_dr_cr(self):
against_account = None
for d in self.accounts:
if flt(d.debit) > 0:
against_account = self.accounts_credited[0]
elif flt(d.credit) > 0:
against_account = self.accounts_debited[0]
if against_account:
d.against_type = against_account.party_type or "Account"
d.against_account = against_account.party or against_account.account
d.against_account_link = against_account.party or against_account.account
for d in self.get("accounts"):
if flt(d.debit) > 0:
d.against_account = ", ".join(list(set(accounts_credited)))
if flt(d.credit) > 0:
d.against_account = ", ".join(list(set(accounts_debited)))
def validate_debit_credit_amount(self):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
@ -1031,108 +970,42 @@ class JournalEntry(AccountsController):
def build_gl_map(self):
gl_map = []
conversion_rate_map = self.get_conversion_rate_map()
transaction_currency_map = self.get_transaction_currency_map()
company_currency = erpnext.get_company_currency(self.company)
self.get_against_accounts()
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark]
r = [x for x in r if x]
remarks = "\n".join(r)
gl_dict = self.get_gl_dict(
{
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
"conversion_rate": conversion_rate_map.get(d.against_account_link, 1)
if d.account_currency == company_currency
else 1,
"currency": transaction_currency_map.get(d.against_account_link, d.account_currency)
if d.account_currency == company_currency
else d.account_currency,
},
item=d,
gl_map.append(
self.get_gl_dict(
{
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
},
item=d,
)
)
if not self.separate_against_account_entries:
gl_dict.update(
{
"against_type": d.against_type,
"against_link": d.against_account_link,
}
)
gl_map.append(gl_dict)
elif d in self.against_accounts:
gl_dict.update(
{
"against_type": self.split_account.get("party_type") or "Account",
"against": self.split_account.get("party") or self.split_account.get("account"),
"against_link": self.split_account.get("party") or self.split_account.get("account"),
}
)
gl_map.append(gl_dict)
else:
for against_account in self.against_accounts:
against_account = against_account.as_dict()
debit = against_account.credit or against_account.credit_in_account_currency
credit = against_account.debit or against_account.debit_in_account_currency
gl_dict = gl_dict.copy()
gl_dict.update(
{
"against_type": against_account.party_type or "Account",
"against": against_account.party or against_account.account,
"against_link": against_account.party or against_account.account,
"debit": flt(debit, d.precision("debit")),
"credit": flt(credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
debit / d.exchange_rate, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
credit / d.exchange_rate, d.precision("credit_in_account_currency")
),
}
)
gl_map.append(gl_dict)
return gl_map
def get_transaction_currency_map(self):
transaction_currency_map = {}
for account in self.get("accounts"):
transaction_currency_map.setdefault(account.party or account.account, account.account_currency)
return transaction_currency_map
def get_conversion_rate_map(self):
conversion_rate_map = {}
for account in self.get("accounts"):
conversion_rate_map.setdefault(account.party or account.account, account.exchange_rate)
return conversion_rate_map
def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries
@ -1755,10 +1628,3 @@ def make_reverse_journal_entry(source_name, target_doc=None):
)
return doclist
@frappe.whitelist()
def get_against_type(doctype, txt, searchfield, start, page_len, filters):
against_types = frappe.db.get_list("Party Type", pluck="name") + ["Account"]
doctype = frappe.qb.DocType("DocType")
return frappe.qb.from_(doctype).select(doctype.name).where(doctype.name.isin(against_types)).run()

View File

@ -1,12 +1,18 @@
frappe.listview_settings['Journal Entry'] = {
add_fields: ["voucher_type", "posting_date", "total_debit", "company", "user_remark"],
get_indicator: function(doc) {
if(doc.docstatus==0) {
return [__("Draft", "red", "docstatus,=,0")]
} else if(doc.docstatus==2) {
return [__("Cancelled", "grey", "docstatus,=,2")]
} else {
return [__(doc.voucher_type), "blue", "voucher_type,=," + doc.voucher_type]
frappe.listview_settings["Journal Entry"] = {
add_fields: [
"voucher_type",
"posting_date",
"total_debit",
"company",
"user_remark",
],
get_indicator: function (doc) {
if (doc.docstatus === 1) {
return [
__(doc.voucher_type),
"blue",
`voucher_type,=,${doc.voucher_type}`,
];
}
}
},
};

View File

@ -37,9 +37,7 @@
"col_break3",
"is_advance",
"user_remark",
"against_type",
"against_account",
"against_account_link"
"against_account"
],
"fields": [
{
@ -252,21 +250,14 @@
"print_hide": 1
},
{
"fieldname": "against_account",
"fieldtype": "Text",
"hidden": 1,
"label": "Against Account",
"no_copy": 1,
"oldfieldname": "against_account",
"oldfieldtype": "Text",
"print_hide": 1
},
{
"fieldname": "against_account_link",
"fieldtype": "Dynamic Link",
"fieldname": "against_account",
"fieldtype": "Text",
"hidden": 1,
"label": "Against Account",
"no_copy": 1,
"options": "against_type"
"oldfieldname": "against_account",
"oldfieldtype": "Text",
"print_hide": 1
},
{
"collapsible": 1,
@ -290,18 +281,12 @@
"hidden": 1,
"label": "Reference Detail No",
"no_copy": 1
},
{
"fieldname": "against_type",
"fieldtype": "Link",
"label": "Against Type",
"options": "DocType"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-12-02 23:21:22.205409",
"modified": "2023-12-03 23:21:22.205409",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@ -270,7 +270,7 @@ def start_import(invoices):
errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"
),
indicator="red",
title=_("Error Occured"),
title=_("Error Occurred"),
)
return names

View File

@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries', "Bank Transaction"];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
@ -640,7 +640,7 @@ frappe.ui.form.on('Payment Entry', {
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today();
const fields = [
let fields = [
{fieldtype:"Section Break", label: __("Posting Date")},
{fieldtype:"Date", label: __("From Date"),
fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)},
@ -655,18 +655,29 @@ frappe.ui.form.on('Payment Entry', {
fieldname:"outstanding_amt_greater_than", default: 0},
{fieldtype:"Column Break"},
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
{fieldtype:"Section Break"},
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
"get_query": function() {
return {
"filters": {"company": frm.doc.company}
}
];
if (frm.dimension_filters) {
let column_break_insertion_point = Math.ceil((frm.dimension_filters.length)/2);
fields.push({fieldtype:"Section Break"});
frm.dimension_filters.map((elem, idx)=>{
fields.push({
fieldtype: "Link",
label: elem.document_type == "Cost Center" ? "Cost Center" : elem.label,
options: elem.document_type,
fieldname: elem.fieldname || elem.document_type
});
if(idx+1 == column_break_insertion_point) {
fields.push({fieldtype:"Column Break"});
}
},
{fieldtype:"Column Break"},
});
}
fields = fields.concat([
{fieldtype:"Section Break"},
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
];
]);
let btn_text = "";
@ -933,7 +944,7 @@ frappe.ui.form.on('Payment Entry', {
if(frm.doc.payment_type == "Receive"
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
unallocated_amount = (frm.doc.base_received_amount + total_deductions + flt(frm.doc.base_total_taxes_and_charges)
unallocated_amount = (frm.doc.base_received_amount + total_deductions - flt(frm.doc.base_total_taxes_and_charges)
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
} else if (frm.doc.payment_type == "Pay"
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions

View File

@ -87,12 +87,14 @@
"status",
"custom_remarks",
"remarks",
"base_in_words",
"column_break_16",
"letter_head",
"print_heading",
"bank",
"bank_account_no",
"payment_order",
"in_words",
"subscription_section",
"auto_repeat",
"amended_from",
@ -747,6 +749,20 @@
"hidden": 1,
"label": "Book Advance Payments in Separate Party Account",
"read_only": 1
},
{
"fieldname": "base_in_words",
"fieldtype": "Small Text",
"label": "In Words (Company Currency)",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "in_words",
"fieldtype": "Small Text",
"label": "In Words",
"print_hide": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,

View File

@ -13,6 +13,7 @@ from pypika import Case
from pypika.functions import Coalesce, Sum
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details,
get_party_bank_account,
@ -177,6 +178,7 @@ class PaymentEntry(AccountsController):
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
self.set_status()
self.set_total_in_words()
def on_submit(self):
if self.difference_amount:
@ -189,7 +191,7 @@ class PaymentEntry(AccountsController):
def set_liability_account(self):
# Auto setting liability account should only be done during 'draft' status
if self.docstatus > 0:
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return
if not frappe.db.get_value(
@ -785,6 +787,21 @@ class PaymentEntry(AccountsController):
self.db_set("status", self.status, update_modified=True)
def set_total_in_words(self):
from frappe.utils import money_in_words
if self.payment_type in ("Pay", "Internal Transfer"):
base_amount = abs(self.base_paid_amount)
amount = abs(self.paid_amount)
currency = self.paid_from_account_currency
elif self.payment_type == "Receive":
base_amount = abs(self.base_received_amount)
amount = abs(self.received_amount)
currency = self.paid_to_account_currency
self.base_in_words = money_in_words(base_amount, self.company_currency)
self.in_words = money_in_words(amount, currency)
def set_tax_withholding(self):
if self.party_type != "Supplier":
return
@ -925,7 +942,10 @@ class PaymentEntry(AccountsController):
def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0
if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if d.reference_doctype in advance_payment_doctypes:
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
# This is so there are no Exchange Gain/Loss generated for such doctypes
@ -1144,9 +1164,7 @@ class PaymentEntry(AccountsController):
"account": self.party_account,
"party_type": self.party_type,
"party": self.party,
"against_type": "Account",
"against": against_account,
"against_link": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
},
@ -1311,9 +1329,7 @@ class PaymentEntry(AccountsController):
{
"account": self.paid_from,
"account_currency": self.paid_from_account_currency,
"against_type": self.party_type if self.payment_type == "Pay" else "Account",
"against": self.party if self.payment_type == "Pay" else self.paid_to,
"against_link": self.party if self.payment_type == "Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount,
"cost_center": self.cost_center,
@ -1328,9 +1344,7 @@ class PaymentEntry(AccountsController):
{
"account": self.paid_to,
"account_currency": self.paid_to_account_currency,
"against_type": self.party_type if self.payment_type == "Receive" else "Account",
"against": self.party if self.payment_type == "Receive" else self.paid_from,
"against_link": self.party if self.payment_type == "Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount,
"cost_center": self.cost_center,
@ -1354,7 +1368,6 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to
against_type = self.party_type or "Account"
payment_account = self.get_party_account_for_taxes()
tax_amount = d.tax_amount
base_tax_amount = d.base_tax_amount
@ -1363,9 +1376,7 @@ class PaymentEntry(AccountsController):
self.get_gl_dict(
{
"account": d.account_head,
"against_type": against_type,
"against": against,
"against_link": against,
dr_or_cr: tax_amount,
dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
@ -1390,9 +1401,7 @@ class PaymentEntry(AccountsController):
self.get_gl_dict(
{
"account": payment_account,
"against_type": against_type,
"against": against,
"against_link": against,
rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency
@ -1417,9 +1426,7 @@ class PaymentEntry(AccountsController):
{
"account": d.account,
"account_currency": account_currency,
"against_type": self.party_type or "Account",
"against": self.party or self.paid_from,
"against_link": self.party or self.paid_from,
"debit_in_account_currency": d.amount,
"debit": d.amount,
"cost_center": d.cost_center,
@ -1436,8 +1443,11 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
@ -1684,6 +1694,13 @@ def get_outstanding_reference_documents(args, validate=False):
condition += " and cost_center='%s'" % args.get("cost_center")
accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
# dynamic dimension filters
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if args.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname))
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
date_fields_dict = {
"posting_date": ["from_posting_date", "to_posting_date"],
"due_date": ["from_due_date", "to_due_date"],
@ -1917,6 +1934,12 @@ def get_orders_to_be_billed(
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
# dynamic dimension filters
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
if filters.get(dim.fieldname):
condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname))
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"

View File

@ -95,6 +95,8 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
this.frm.change_custom_button_type(__('Allocate'), null, 'default');
}
this.frm.trigger("set_query_for_dimension_filters");
// check for any running reconciliation jobs
if (this.frm.doc.receivable_payable_account) {
this.frm.call({
@ -125,6 +127,25 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
}
}
set_query_for_dimension_filters() {
frappe.call({
method: "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.get_queries_for_dimension_filters",
args: {
company: this.frm.doc.company,
},
callback: (r) => {
if (!r.exc && r.message) {
r.message.forEach(x => {
this.frm.set_query(x.fieldname, () => {
return {
'filters': x.filters
};
});
});
}
}
});
}
company() {
this.frm.set_value('party', '');

View File

@ -25,7 +25,9 @@
"invoice_limit",
"payment_limit",
"bank_cash_account",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"sec_break1",
"invoice_name",
"invoices",
@ -39,6 +41,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company",
"options": "Company",
"reqd": 1
@ -208,6 +211,18 @@
"fieldname": "payment_name",
"fieldtype": "Data",
"label": "Filter on Payment"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.invoices.length == 0",
"depends_on": "eval:doc.receivable_payable_account",
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions Filter"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
@ -215,7 +230,7 @@
"is_virtual": 1,
"issingle": 1,
"links": [],
"modified": "2023-11-17 17:33:55.701726",
"modified": "2024-01-18 11:56:20.234667",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@ -10,6 +10,7 @@ from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
is_any_doc_running,
)
@ -70,6 +71,7 @@ class PaymentReconciliation(Document):
self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
self.dimensions = get_dimensions()[0]
def load_from_db(self):
# 'modified' attribute is required for `run_doc_method` to work properly.
@ -172,6 +174,14 @@ class PaymentReconciliation(Document):
if self.payment_name:
condition.update({"name": self.payment_name})
# pass dynamic dimension filter values to query builder
dimensions = {}
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
dimensions.update({dimension: self.get(dimension)})
condition.update({"accounting_dimensions": dimensions})
payment_entries = get_advance_payment_entries_for_regional(
self.party_type,
self.party,
@ -185,66 +195,67 @@ class PaymentReconciliation(Document):
return payment_entries
def get_jv_entries(self):
condition = self.get_conditions()
je = qb.DocType("Journal Entry")
jea = qb.DocType("Journal Entry Account")
conditions = self.get_journal_filter_conditions()
# Dimension filters
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
conditions.append(jea[dimension] == self.get(dimension))
if self.payment_name:
condition += f" and t1.name like '%%{self.payment_name}%%'"
conditions.append(je.name.like(f"%%{self.payment_name}%%"))
if self.get("cost_center"):
condition += f" and t2.cost_center = '{self.cost_center}' "
conditions.append(jea.cost_center == self.cost_center)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
conditions.append(jea[dr_or_cr].gt(0))
bank_account_condition = (
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
if self.bank_cash_account:
conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%"))
journal_query = (
qb.from_(je)
.inner_join(jea)
.on(jea.parent == je.name)
.select(
ConstantColumn("Journal Entry").as_("reference_type"),
je.name.as_("reference_name"),
je.posting_date,
je.remark.as_("remarks"),
jea.name.as_("reference_row"),
jea[dr_or_cr].as_("amount"),
jea.is_advance,
jea.exchange_rate,
jea.account_currency.as_("currency"),
jea.cost_center.as_("cost_center"),
)
.where(
(je.docstatus == 1)
& (jea.party_type == self.party_type)
& (jea.party == self.party)
& (jea.account == self.receivable_payable_account)
& (
(jea.reference_type == "")
| (jea.reference_type.isnull())
| (jea.reference_type.isin(("Sales Order", "Purchase Order")))
)
)
.where(Criterion.all(conditions))
.orderby(je.posting_date)
)
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
if self.payment_limit:
journal_query = journal_query.limit(self.payment_limit)
# nosemgrep
journal_entries = frappe.db.sql(
"""
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
{dr_or_cr} as amount, t2.is_advance, t2.exchange_rate,
t2.account_currency as currency, t2.cost_center as cost_center
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
and t2.party_type = %(party_type)s and t2.party = %(party)s
and t2.account = %(account)s and {dr_or_cr} > 0 {condition}
and (t2.reference_type is null or t2.reference_type = '' or
(t2.reference_type in ('Sales Order', 'Purchase Order')
and t2.reference_name is not null and t2.reference_name != ''))
and (CASE
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
THEN 1=1
ELSE {bank_account_condition}
END)
order by t1.posting_date
{limit}
""".format(
**{
"dr_or_cr": dr_or_cr,
"bank_account_condition": bank_account_condition,
"condition": condition,
"limit": limit,
}
),
{
"party_type": self.party_type,
"party": self.party,
"account": self.receivable_payable_account,
"bank_cash_account": "%%%s%%" % self.bank_cash_account,
},
as_dict=1,
)
journal_entries = journal_query.run(as_dict=True)
return list(journal_entries)
@ -298,6 +309,7 @@ class PaymentReconciliation(Document):
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True,
accounting_dimensions=self.accounting_dimension_filter_conditions,
)
for inv in return_outstanding:
@ -447,8 +459,15 @@ class PaymentReconciliation(Document):
row = self.append("allocation", {})
row.update(entry)
def update_dimension_values_in_allocated_entries(self, res):
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
res[dimension] = self.get(dimension)
return res
def get_allocated_entry(self, pay, inv, allocated_amount):
return frappe._dict(
res = frappe._dict(
{
"reference_type": pay.get("reference_type"),
"reference_name": pay.get("reference_name"),
@ -464,6 +483,9 @@ class PaymentReconciliation(Document):
}
)
res = self.update_dimension_values_in_allocated_entries(res)
return res
def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
adjust_allocations_for_taxes(self)
dr_or_cr = (
@ -486,10 +508,10 @@ class PaymentReconciliation(Document):
reconciled_entry.append(payment_details)
if entry_list:
reconcile_against_document(entry_list, skip_ref_details_update_for_pe)
reconcile_against_document(entry_list, skip_ref_details_update_for_pe, self.dimensions)
if dr_or_cr_notes:
reconcile_dr_cr_note(dr_or_cr_notes, self.company)
reconcile_dr_cr_note(dr_or_cr_notes, self.company, self.dimensions)
@frappe.whitelist()
def reconcile(self):
@ -518,7 +540,7 @@ class PaymentReconciliation(Document):
self.get_unreconciled_entries()
def get_payment_details(self, row, dr_or_cr):
return frappe._dict(
payment_details = frappe._dict(
{
"voucher_type": row.get("reference_type"),
"voucher_no": row.get("reference_name"),
@ -541,6 +563,12 @@ class PaymentReconciliation(Document):
}
)
for x in self.dimensions:
if row.get(x.fieldname):
payment_details[x.fieldname] = row.get(x.fieldname)
return payment_details
def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
if not self.get(fieldname):
@ -648,6 +676,13 @@ class PaymentReconciliation(Document):
if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table"))
def build_dimensions_filter_conditions(self):
ple = qb.DocType("Payment Ledger Entry")
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
self.accounting_dimension_filter_conditions.clear()
@ -671,40 +706,30 @@ class PaymentReconciliation(Document):
if self.to_payment_date:
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
def get_conditions(self, get_payments=False):
condition = " and company = '{0}' ".format(self.company)
self.build_dimensions_filter_conditions()
if self.get("cost_center") and get_payments:
condition = " and cost_center = '{0}' ".format(self.cost_center)
def get_journal_filter_conditions(self):
conditions = []
je = qb.DocType("Journal Entry")
jea = qb.DocType("Journal Entry Account")
conditions.append(je.company == self.company)
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date
else ""
)
condition += (
" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
if self.from_payment_date:
conditions.append(je.posting_date.gte(self.from_payment_date))
if self.to_payment_date:
conditions.append(je.posting_date.lte(self.to_payment_date))
if self.minimum_payment_amount:
condition += (
" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
if get_payments
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
)
conditions.append(je.total_debit.gte(self.minimum_payment_amount))
if self.maximum_payment_amount:
condition += (
" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
if get_payments
else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
)
conditions.append(je.total_debit.lte(self.maximum_payment_amount))
return condition
return conditions
def reconcile_dr_cr_note(dr_cr_notes, company):
def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
for inv in dr_cr_notes:
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
@ -754,6 +779,15 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
}
)
# Credit Note(JE) will inherit the same dimension values as payment
dimensions_dict = frappe._dict()
if active_dimensions:
for dim in active_dimensions:
dimensions_dict[dim.fieldname] = inv.get(dim.fieldname)
jv.accounts[0].update(dimensions_dict)
jv.accounts[1].update(dimensions_dict)
jv.flags.ignore_mandatory = True
jv.flags.ignore_exchange_rate = True
jv.remark = None
@ -787,9 +821,27 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
inv.against_voucher,
None,
inv.cost_center,
dimensions_dict,
)
@erpnext.allow_regional
def adjust_allocations_for_taxes(doc):
pass
@frappe.whitelist()
def get_queries_for_dimension_filters(company: str = None):
dimensions_with_filters = []
for d in get_dimensions()[0]:
filters = {}
meta = frappe.get_meta(d.document_type)
if meta.has_field("company") and company:
filters.update({"company": company})
if meta.is_tree:
filters.update({"is_group": 0})
dimensions_with_filters.append({"fieldname": d.fieldname, "filters": filters})
return dimensions_with_filters

View File

@ -24,7 +24,9 @@
"difference_account",
"exchange_rate",
"currency",
"cost_center"
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
@ -157,12 +159,21 @@
"fieldname": "gain_loss_posting_date",
"fieldtype": "Date",
"label": "Difference Posting Date"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"is_virtual": 1,
"istable": 1,
"links": [],
"modified": "2023-11-17 17:33:38.612615",
"modified": "2023-12-14 13:38:26.104150",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",

View File

@ -169,6 +169,13 @@ class PaymentRequest(Document):
elif self.payment_channel == "Phone":
self.request_phone_payment()
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
def request_phone_payment(self):
controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
@ -207,6 +214,14 @@ class PaymentRequest(Document):
self.check_if_payment_entry_exists()
self.set_as_cancelled()
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
def make_invoice(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart":
@ -424,6 +439,15 @@ def make_payment_request(**args):
"""Make payment request"""
args = frappe._dict(args)
if args.dt not in [
"Sales Order",
"Purchase Order",
"Sales Invoice",
"Purchase Invoice",
"POS Invoice",
"Fees",
]:
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
ref_doc = frappe.get_doc(args.dt, args.dn)
gateway_account = get_gateway_details(args) or frappe._dict()

View File

@ -371,7 +371,7 @@ class POSInvoice(SalesInvoice):
if d.get("qty") > 0:
frappe.throw(
_(
"Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return."
"Row #{}: You cannot add positive quantities in a return invoice. Please remove item {} to complete the return."
).format(d.idx, frappe.bold(d.item_code)),
title=_("Invalid Item"),
)
@ -793,7 +793,7 @@ def make_merge_log(invoices):
invoices = json.loads(invoices)
if len(invoices) == 0:
frappe.throw(_("Atleast one invoice has to be selected."))
frappe.throw(_("At least one invoice has to be selected."))
merge_log = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = getdate(nowdate())

View File

@ -132,7 +132,7 @@ class POSProfile(Document):
if len(customer_groups) != len(set(customer_groups)):
frappe.throw(
_("Duplicate customer group found in the cutomer group table"),
_("Duplicate customer group found in the customer group table"),
title=_("Duplicate Customer Group"),
)

View File

@ -339,7 +339,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto"
"label": "Valid Up To"
},
{
"fieldname": "col_break1",
@ -608,7 +608,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2023-02-14 04:53:34.887358",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@ -193,7 +193,7 @@ class PricingRule(Document):
def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected"))
throw(_("At least one of the Selling or Buying must be selected"))
if not self.selling and self.applicable_for in [
"Customer",
@ -579,12 +579,17 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
item_details[field] += pricing_rule.get(field, 0) if pricing_rule else args.get(field, 0)
@frappe.whitelist()
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
get_pricing_rule_items,
)
if isinstance(item_details, str):
item_details = json.loads(item_details)
item_details = frappe._dict(item_details)
for d in get_applied_pricing_rules(pricing_rules):
if not d or not frappe.db.exists("Pricing Rule", d):
continue

View File

@ -232,7 +232,7 @@
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto"
"label": "Valid Up To"
},
{
"fieldname": "column_break_26",
@ -278,7 +278,7 @@
}
],
"links": [],
"modified": "2021-05-06 16:20:22.039078",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme",

View File

@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle"];
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle", "Bank Transaction"];
if(!this.frm.doc.__islocal) {
// show credit_to in print format

View File

@ -827,9 +827,7 @@ class PurchaseInvoice(BuyingController):
"party_type": "Supplier",
"party": self.supplier,
"due_date": self.due_date,
"against_type": "Account",
"against": self.against_expense_account,
"against_link": self.against_expense_account,
"credit": base_grand_total,
"credit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
@ -902,9 +900,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": warehouse_account[item.warehouse]["account"],
"against_type": "Account",
"against": warehouse_account[item.from_warehouse]["account"],
"against_link": warehouse_account[item.from_warehouse]["account"],
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -924,9 +920,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": warehouse_account[item.from_warehouse]["account"],
"against_type": "Account",
"against": warehouse_account[item.warehouse]["account"],
"against_link": warehouse_account[item.warehouse]["account"],
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -943,9 +937,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": item.expense_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
@ -962,9 +954,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": item.expense_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
@ -983,9 +973,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": account,
"against_type": "Account",
"against": item.expense_account,
"against_link": item.expense_account,
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount["base_amount"]),
@ -1005,9 +993,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": supplier_warehouse_account,
"against_type": "Account",
"against": item.expense_account,
"against_link": item.expense_account,
"cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -1062,9 +1048,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": expense_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"debit": amount,
"cost_center": item.cost_center,
"project": item.project or self.project,
@ -1090,9 +1074,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": expense_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"debit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or self.project,
@ -1105,9 +1087,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": self.get_company_default("exchange_gain_loss_account"),
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"credit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center,
"project": item.project or self.project,
@ -1140,10 +1120,8 @@ class PurchaseInvoice(BuyingController):
gl_entries.append(
self.get_gl_dict(
{
"account": stock_rbnb,
"against_type": "Supplier",
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"against_link": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center,
@ -1204,9 +1182,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against_type": "Account",
"against": item.expense_account,
"against_link": item.expense_account,
"debit": stock_adjustment_amt,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
@ -1236,9 +1212,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": tax.account_head,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
dr_or_cr: base_amount,
dr_or_cr + "_in_account_currency": base_amount
if account_currency == self.company_currency
@ -1286,10 +1260,8 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": tax.account_head,
"against_type": "Supplier",
"cost_center": tax.cost_center,
"against": self.supplier,
"against_link": self.supplier,
"credit": applicable_amount,
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
@ -1307,9 +1279,7 @@ class PurchaseInvoice(BuyingController):
{
"account": tax.account_head,
"cost_center": tax.cost_center,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"credit": valuation_tax[tax.name],
"remarks": self.remarks or _("Accounting Entry for Stock"),
},
@ -1324,9 +1294,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": self.unrealized_profit_loss_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center,
@ -1347,9 +1315,7 @@ class PurchaseInvoice(BuyingController):
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"against_type": "Account",
"against": self.cash_bank_account,
"against_link": self.cash_bank_account,
"debit": self.base_paid_amount,
"debit_in_account_currency": self.base_paid_amount
if self.party_account_currency == self.company_currency
@ -1370,9 +1336,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": self.cash_bank_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"credit": self.base_paid_amount,
"credit_in_account_currency": self.base_paid_amount
if bank_account_currency == self.company_currency
@ -1396,9 +1360,7 @@ class PurchaseInvoice(BuyingController):
"account": self.credit_to,
"party_type": "Supplier",
"party": self.supplier,
"against_type": "Account",
"against": self.write_off_account,
"against_link": self.write_off_account,
"debit": self.base_write_off_amount,
"debit_in_account_currency": self.base_write_off_amount
if self.party_account_currency == self.company_currency
@ -1418,9 +1380,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": self.write_off_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"credit": flt(self.base_write_off_amount),
"credit_in_account_currency": self.base_write_off_amount
if write_off_account_currency == self.company_currency
@ -1447,9 +1407,7 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict(
{
"account": round_off_account,
"against_type": "Supplier",
"against": self.supplier,
"against_link": self.supplier,
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
"cost_center": round_off_cost_center

View File

@ -64,6 +64,7 @@
"warehouse",
"from_warehouse",
"quality_inspection",
"add_serial_batch_bundle",
"serial_and_batch_bundle",
"serial_no",
"col_br_wh",
@ -913,12 +914,18 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"depends_on": "eval:parent.update_stock === 1",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-12-25 22:00:28.043555",
"modified": "2024-01-21 19:46:25.537861",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@ -126,7 +126,7 @@
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
@ -230,7 +230,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-08-05 20:04:36.618240",
"modified": "2024-01-14 10:04:36.618240",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
@ -239,4 +239,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@ -37,8 +37,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries",
'Serial and Batch Bundle'
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle", "Bank Transaction",
];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {

View File

@ -7,7 +7,7 @@ from frappe import _, msgprint, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.utils import add_days, cint, flt, formatdate, get_link_to_form, getdate, nowdate
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date
@ -1233,9 +1233,7 @@ class SalesInvoice(SellingController):
"party_type": "Customer",
"party": self.customer,
"due_date": self.due_date,
"against_type": "Account",
"against": self.against_income_account,
"against_link": self.against_income_account,
"debit": base_grand_total,
"debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency
@ -1264,9 +1262,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": tax.account_head,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
"credit_in_account_currency": (
flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
@ -1287,9 +1283,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": self.unrealized_profit_loss_account,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center,
@ -1357,9 +1351,7 @@ class SalesInvoice(SellingController):
add_asset_activity(asset.name, _("Asset sold"))
for gle in fixed_asset_gl_entries:
gle["against_type"] = "Customer"
gle["against"] = self.customer
gle["against_link"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))
self.set_asset_status(asset)
@ -1380,9 +1372,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": income_account,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount"))
@ -1436,9 +1426,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"against_type": "Account",
"against": self.loyalty_redemption_account,
"against_link": self.loyalty_redemption_account,
"against": "Expense account - "
+ cstr(self.loyalty_redemption_account)
+ " for the Loyalty Program",
"credit": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype,
@ -1452,9 +1442,7 @@ class SalesInvoice(SellingController):
{
"account": self.loyalty_redemption_account,
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer",
},
@ -1481,9 +1469,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"against_type": "Account",
"against": payment_mode.account,
"against_link": payment_mode.account,
"credit": payment_mode.base_amount,
"credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency
@ -1504,9 +1490,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": payment_mode.account,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"debit": payment_mode.base_amount,
"debit_in_account_currency": payment_mode.base_amount
if payment_mode_account_currency == self.company_currency
@ -1530,9 +1514,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"against_type": "Account",
"against": self.account_for_change_amount,
"against_link": self.account_for_change_amount,
"debit": flt(self.base_change_amount),
"debit_in_account_currency": flt(self.base_change_amount)
if self.party_account_currency == self.company_currency
@ -1553,9 +1535,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": self.account_for_change_amount,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"credit": self.base_change_amount,
"cost_center": self.cost_center,
},
@ -1581,9 +1561,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
"against_type": "Account",
"against": self.write_off_account,
"against_link": self.write_off_account,
"credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"credit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1603,9 +1581,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": self.write_off_account,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"debit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1633,9 +1609,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict(
{
"account": round_off_account,
"against_type": "Customer",
"against": self.customer,
"against_link": self.customer,
"credit_in_account_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment")
),

View File

@ -108,7 +108,7 @@
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
@ -218,7 +218,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-10-17 13:08:17.776528",
"modified": "2022-10-18 13:08:17.776528",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
@ -227,4 +227,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"states": []
}
}

View File

@ -51,7 +51,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
"options": "\nTrialing\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
"read_only": 1
},
{
@ -267,7 +267,7 @@
"link_fieldname": "subscription"
}
],
"modified": "2023-12-28 17:20:42.687789",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",

View File

@ -16,6 +16,7 @@ from frappe.utils.data import (
date_diff,
flt,
get_last_day,
get_link_to_form,
getdate,
nowdate,
)
@ -77,9 +78,7 @@ class Subscription(Document):
purchase_tax_template: DF.Link | None
sales_tax_template: DF.Link | None
start_date: DF.Date | None
status: DF.Literal[
"", "Trialling", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"
]
status: DF.Literal["", "Trialing", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"]
submit_invoice: DF.Check
trial_period_end: DF.Date | None
trial_period_start: DF.Date | None
@ -232,7 +231,7 @@ class Subscription(Document):
Sets the status of the `Subscription`
"""
if self.is_trialling():
self.status = "Trialling"
self.status = "Trialing"
elif (
self.status == "Active" and self.end_date and getdate(posting_date) > getdate(self.end_date)
):
@ -317,6 +316,37 @@ class Subscription(Document):
if self.is_new():
self.set_subscription_status()
self.validate_party_billing_currency()
def validate_party_billing_currency(self):
"""
Subscription should be of the same currency as the Party's default billing currency or company default.
"""
if self.party:
party_billing_currency = frappe.get_cached_value(
self.party_type, self.party, "default_currency"
) or frappe.get_cached_value("Company", self.company, "default_currency")
plans = [x.plan for x in self.plans]
subscription_plan_currencies = frappe.db.get_all(
"Subscription Plan", filters={"name": ("in", plans)}, fields=["name", "currency"]
)
unsupported_plans = []
for x in subscription_plan_currencies:
if x.currency != party_billing_currency:
unsupported_plans.append("{0}".format(get_link_to_form("Subscription Plan", x.name)))
if unsupported_plans:
unsupported_plans = [
_(
"Below Subscription Plans are of different currency to the party default billing currency/Company currency: {0}"
).format(frappe.bold(party_billing_currency))
] + unsupported_plans
frappe.throw(
unsupported_plans, frappe.ValidationError, "Unsupported Subscription Plans", as_list=True
)
def validate_trial_period(self) -> None:
"""
Runs sanity checks on trial period dates for the `Subscription`
@ -563,6 +593,8 @@ class Subscription(Document):
) and self.can_generate_new_invoice(posting_date):
self.generate_invoice(posting_date=posting_date)
self.update_subscription_period(add_days(self.current_invoice_end, 1))
elif posting_date and getdate(posting_date) > getdate(self.current_invoice_end):
self.update_subscription_period()
if self.cancel_at_period_end and (
getdate(posting_date) >= getdate(self.current_invoice_end)

View File

@ -1,7 +1,7 @@
frappe.listview_settings['Subscription'] = {
get_indicator: function(doc) {
if(doc.status === 'Trialling') {
return [__("Trialling"), "green"];
if(doc.status === 'Trialing') {
return [__("Trialing"), "green"];
} else if(doc.status === 'Active') {
return [__("Active"), "green"];
} else if(doc.status === 'Completed') {

View File

@ -46,7 +46,7 @@ class TestSubscription(FrappeTestCase):
get_date_str(subscription.current_invoice_end),
)
self.assertEqual(subscription.invoices, [])
self.assertEqual(subscription.status, "Trialling")
self.assertEqual(subscription.status, "Trialing")
def test_create_subscription_without_trial_with_correct_period(self):
subscription = create_subscription()
@ -460,11 +460,13 @@ class TestSubscription(FrappeTestCase):
self.assertEqual(len(subscription.invoices), 1)
def test_multi_currency_subscription(self):
party = "_Test Subscription Customer"
frappe.db.set_value("Customer", party, "default_currency", "USD")
subscription = create_subscription(
start_date="2018-01-01",
generate_invoice_at="Beginning of the current subscription period",
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1}],
party="_Test Subscription Customer",
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1, "currency": "USD"}],
party=party,
)
subscription.process()
@ -528,13 +530,21 @@ class TestSubscription(FrappeTestCase):
def make_plans():
create_plan(plan_name="_Test Plan Name", cost=900)
create_plan(plan_name="_Test Plan Name 2", cost=1999)
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
create_plan(plan_name="_Test Plan Name 2", cost=1999, currency="INR")
create_plan(
plan_name="_Test Plan Name 3", cost=1999, billing_interval="Day", billing_interval_count=14
plan_name="_Test Plan Name 3",
cost=1999,
billing_interval="Day",
billing_interval_count=14,
currency="INR",
)
create_plan(
plan_name="_Test Plan Name 4", cost=20000, billing_interval="Month", billing_interval_count=3
plan_name="_Test Plan Name 4",
cost=20000,
billing_interval="Month",
billing_interval_count=3,
currency="INR",
)
create_plan(
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"

View File

@ -41,7 +41,8 @@
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
"options": "Currency",
"reqd": 1
},
{
"fieldname": "column_break_3",
@ -148,10 +149,11 @@
}
],
"links": [],
"modified": "2021-12-10 15:24:15.794477",
"modified": "2024-01-14 17:59:34.687977",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@ -193,5 +195,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@ -24,7 +24,7 @@ class SubscriptionPlan(Document):
billing_interval_count: DF.Int
cost: DF.Currency
cost_center: DF.Link | None
currency: DF.Link | None
currency: DF.Link
item: DF.Link
payment_gateway: DF.Link | None
plan_name: DF.Data

View File

@ -4,7 +4,7 @@
"doctype": "Form Tour",
"idx": 0,
"is_standard": 1,
"modified": "2021-06-29 17:00:26.145996",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@ -82,7 +82,7 @@
"label": "Accounts Frozen Till Date",
"parent_field": "",
"position": "Right",
"title": "Accounts Frozen Upto"
"title": "Accounts Frozen Up To"
},
{
"description": "Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.",

View File

@ -280,7 +280,6 @@ def check_if_in_list(gle, gl_map, dimensions=None):
"project",
"finance_book",
"voucher_no",
"against_link",
]
if dimensions:

View File

@ -39,7 +39,7 @@ frappe.query_reports["Account Balance"] = {
{ "value": "Asset Received But Not Billed", "label": __("Asset Received But Not Billed") },
{ "value": "Bank", "label": __("Bank") },
{ "value": "Cash", "label": __("Cash") },
{ "value": "Chargeble", "label": __("Chargeble") },
{ "value": "Chargeable", "label": __("Chargeable") },
{ "value": "Capital Work in Progress", "label": __("Capital Work in Progress") },
{ "value": "Cost of Goods Sold", "label": __("Cost of Goods Sold") },
{ "value": "Depreciation", "label": __("Depreciation") },

View File

@ -22,7 +22,7 @@ def get_columns(filters):
"fieldtype": "Link",
"fieldname": "account",
"options": "Account",
"width": 100,
"width": 200,
},
{
"label": _("Currency"),
@ -30,7 +30,7 @@ def get_columns(filters):
"fieldname": "currency",
"options": "Currency",
"hidden": 1,
"width": 50,
"width": 100,
},
{
"label": _("Balance"),

View File

@ -10,10 +10,8 @@
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center">
{% if (filters.customer_name) { %}
{%= filters.customer_name %}
{% } else { %}
{%= filters.customer || filters.supplier %}
{% if (filters.party) { %}
{%= __(filters.party) %}
{% } %}
</h4>
<h6 class="text-center">
@ -141,7 +139,7 @@
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
<th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_future_payments) { %}
@ -158,7 +156,7 @@
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %}
{% } else { %}
<th style="width: 40%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
<th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
@ -187,7 +185,7 @@
{% if(!filters.show_future_payments) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{% if(!(filters.party)) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}
@ -260,7 +258,7 @@
{% if(data[i]["party"]|| "&nbsp;") { %}
{% if(!data[i]["is_total_row"]) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{% if(!(filters.party)) { %}
{%= data[i]["party"] %}
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i]["customer_name"] %}

View File

@ -124,11 +124,11 @@ def get_provisional_profit_loss(
key = period if consolidated else period.key
effective_liability = 0.0
if liability:
effective_liability += flt(liability[-2].get(key))
effective_liability += flt(liability[0].get(key))
if equity:
effective_liability += flt(equity[-2].get(key))
effective_liability += flt(equity[0].get(key))
provisional_profit_loss[key] = flt(asset[-2].get(key)) - effective_liability
provisional_profit_loss[key] = flt(asset[0].get(key)) - effective_liability
total_row[key] = effective_liability + provisional_profit_loss[key]
if provisional_profit_loss[key]:
@ -193,11 +193,11 @@ def get_report_summary(
for period in period_list:
key = period if consolidated else period.key
if asset:
net_asset += asset[-2].get(key)
net_asset += asset[0].get(key)
if liability:
net_liability += liability[-2].get(key)
net_liability += liability[0].get(key)
if equity:
net_equity += equity[-2].get(key)
net_equity += equity[0].get(key)
if provisional_profit_loss:
net_provisional_profit_loss += provisional_profit_loss.get(key)

View File

@ -3,49 +3,135 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
from frappe.utils.data import today
from erpnext.accounts.report.balance_sheet.balance_sheet import execute
COMPANY = "_Test Company 6"
COMPANY_SHORT_NAME = "_TC6"
test_dependencies = ["Company"]
class TestBalanceSheet(FrappeTestCase):
def test_balance_sheet(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
create_sales_invoice,
make_sales_invoice,
)
from erpnext.accounts.utils import get_fiscal_year
frappe.db.sql(f"delete from `tabJournal Entry` where company='{COMPANY}'")
frappe.db.sql(f"delete from `tabGL Entry` where company='{COMPANY}'")
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 6'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
create_account("VAT Liabilities", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
create_account("Advance VAT Paid", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
create_account("My Bank", f"Bank Accounts - {COMPANY_SHORT_NAME}", COMPANY)
pi = make_purchase_invoice(
company="_Test Company 6",
warehouse="Finished Goods - _TC6",
expense_account="Cost of Goods Sold - _TC6",
cost_center="Main - _TC6",
qty=10,
rate=100,
# 1000 equity paid to bank account
make_journal_entry(
[
dict(
account_name="My Bank",
debit_in_account_currency=1000,
credit_in_account_currency=0,
),
dict(
account_name="Capital Stock",
debit_in_account_currency=0,
credit_in_account_currency=1000,
),
]
)
si = create_sales_invoice(
company="_Test Company 6",
debit_to="Debtors - _TC6",
income_account="Sales - _TC6",
cost_center="Main - _TC6",
qty=5,
rate=110,
# 110 income paid to bank account (100 revenue + 10 VAT)
make_journal_entry(
[
dict(
account_name="My Bank",
debit_in_account_currency=110,
credit_in_account_currency=0,
),
dict(
account_name="Sales",
debit_in_account_currency=0,
credit_in_account_currency=100,
),
dict(
account_name="VAT Liabilities",
debit_in_account_currency=0,
credit_in_account_currency=10,
),
]
)
# offset VAT Liabilities with intra-year advance payment
make_journal_entry(
[
dict(
account_name="My Bank",
debit_in_account_currency=0,
credit_in_account_currency=10,
),
dict(
account_name="Advance VAT Paid",
debit_in_account_currency=10,
credit_in_account_currency=0,
),
]
)
filters = frappe._dict(
company="_Test Company 6",
company=COMPANY,
period_start_date=today(),
period_end_date=today(),
periodicity="Yearly",
)
result = execute(filters)[1]
for account_dict in result:
if account_dict.get("account") == "Current Liabilities - _TC6":
self.assertEqual(account_dict.total, 1000)
if account_dict.get("account") == "Current Assets - _TC6":
self.assertEqual(account_dict.total, 550)
results = execute(filters)
name_and_total = {
account_dict["account_name"]: account_dict["total"]
for account_dict in results[1]
if "total" in account_dict and "account_name" in account_dict
}
self.assertNotIn("Sales", name_and_total)
self.assertIn("My Bank", name_and_total)
self.assertEqual(name_and_total["My Bank"], 1100)
self.assertIn("VAT Liabilities", name_and_total)
self.assertEqual(name_and_total["VAT Liabilities"], 10)
self.assertIn("Advance VAT Paid", name_and_total)
self.assertEqual(name_and_total["Advance VAT Paid"], -10)
self.assertIn("Duties and Taxes", name_and_total)
self.assertEqual(name_and_total["Duties and Taxes"], 0)
self.assertIn("Application of Funds (Assets)", name_and_total)
self.assertEqual(name_and_total["Application of Funds (Assets)"], 1100)
self.assertIn("Equity", name_and_total)
self.assertEqual(name_and_total["Equity"], 1000)
self.assertIn("'Provisional Profit / Loss (Credit)'", name_and_total)
self.assertEqual(name_and_total["'Provisional Profit / Loss (Credit)'"], 100)
def make_journal_entry(rows):
jv = frappe.new_doc("Journal Entry")
jv.posting_date = today()
jv.company = COMPANY
jv.user_remark = "test"
for row in rows:
row["account"] = row.pop("account_name") + " - " + COMPANY_SHORT_NAME
jv.append("accounts", row)
jv.insert()
jv.submit()
def create_account(account_name: str, parent_account: str, company: str):
if frappe.db.exists("Account", {"account_name": account_name, "company": company}):
return
acc = frappe.new_doc("Account")
acc.account_name = account_name
acc.company = COMPANY
acc.parent_account = parent_account
acc.insert()

View File

@ -84,10 +84,6 @@ function get_filters() {
options: budget_against_options,
default: "Cost Center",
reqd: 1,
get_data: function() {
console.log(this.options);
return ["Emacs", "Rocks"];
},
on_change: function() {
frappe.query_report.set_filter_value("budget_against_filter", []);
frappe.query_report.refresh();

View File

@ -376,6 +376,10 @@ class PartyLedgerSummaryReport(object):
if not income_or_expense_accounts:
# prevent empty 'in' condition
income_or_expense_accounts.append("")
else:
# escape '%' in account name
# ignoring frappe.db.escape as it replaces single quotes with double quotes
income_or_expense_accounts = [x.replace("%", "%%") for x in income_or_expense_accounts]
accounts_query = (
qb.from_(gl)

View File

@ -8,17 +8,7 @@ import re
import frappe
from frappe import _
from frappe.utils import (
add_days,
add_months,
cint,
cstr,
flt,
formatdate,
get_first_day,
getdate,
today,
)
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@ -53,8 +43,6 @@ def get_period_list(
year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date)
year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = []

View File

@ -203,7 +203,7 @@ def get_gl_entries(filters, accounting_dimensions):
voucher_type, voucher_subtype, voucher_no, {dimension_fields}
cost_center, project, {transaction_currency_fields}
against_voucher_type, against_voucher, account_currency,
against_link, against, is_opening, creation {select_fields}
against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{order_by_statement}
@ -398,7 +398,6 @@ def initialize_gle_map(gl_entries, filters):
group_by = group_by_field(filters.get("group_by"))
for gle in gl_entries:
gle.against = gle.get("against_link") or gle.get("against")
gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[]))
return gle_map
@ -449,6 +448,10 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
for gle in gl_entries:
group_by_value = gle.get(group_by)
gle.voucher_type = _(gle.voucher_type)
gle.voucher_subtype = _(gle.voucher_subtype)
gle.against_voucher_type = _(gle.against_voucher_type)
gle.remarks = _(gle.remarks)
gle.party_type = _(gle.party_type)
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
if not group_by_voucher_consolidated:

View File

@ -134,7 +134,7 @@ def get_revenue(data, period_list, include_in_gross=1):
def remove_parent_with_no_child(data):
data_to_be_removed = False
for parent in data:
for parent in list(data):
if "is_group" in parent and parent.get("is_group") == 1:
have_child = False
for child in data:

View File

@ -59,7 +59,21 @@ frappe.query_reports["Item-wise Sales Register"] = {
"fieldname": "group_by",
"fieldtype": "Select",
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"]
}
},
{
"fieldname": "income_account",
"label": __("Income Account"),
"fieldtype": "Link",
"options": "Account",
get_query: () => {
let company = frappe.query_report.get_filter_value('company');
return {
filters: {
'company': company,
}
};
}
},
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);

View File

@ -83,9 +83,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
"company": d.company,
"sales_order": d.sales_order,
"delivery_note": d.delivery_note,
"income_account": d.unrealized_profit_loss_account
if d.is_internal_customer == 1
else d.income_account,
"income_account": get_income_account(d),
"cost_center": d.cost_center,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
@ -150,6 +148,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
return columns, data, None, None, None, skip_total_row
def get_income_account(row):
if row.enable_deferred_revenue:
return row.deferred_revenue_account
elif row.is_internal_customer == 1:
return row.unrealized_profit_loss_account
else:
return row.income_account
def get_columns(additional_table_columns, filters):
columns = []
@ -358,6 +365,13 @@ def get_conditions(filters, additional_conditions=None):
if filters.get("item_group"):
conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
if filters.get("income_account"):
conditions += """
and (ifnull(`tabSales Invoice Item`.income_account, '') = %(income_account)s
or ifnull(`tabSales Invoice Item`.deferred_revenue_account, '') = %(income_account)s
or ifnull(`tabSales Invoice`.unrealized_profit_loss_account, '') = %(income_account)s)
"""
if not filters.get("group_by"):
conditions += (
"ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
@ -399,6 +413,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.enable_deferred_revenue, `tabSales Invoice Item`.deferred_revenue_account,
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,

View File

@ -46,12 +46,10 @@ def get_result(
out = []
for name, details in gle_map.items():
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
bill_no, bill_date = "", ""
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
tax_withholding_category, rate = None, None
bill_no, bill_date = "", ""
party = entry.party or entry.against
posting_date = entry.posting_date
voucher_type = entry.voucher_type
@ -61,12 +59,19 @@ def get_result(
if party_list:
party = party_list[0]
if not tax_withholding_category:
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
if entry.account in tds_accounts:
if entry.account in tds_accounts.keys():
tax_amount += entry.credit - entry.debit
# infer tax withholding category from the account if it's the single account for this category
tax_withholding_category = tds_accounts.get(entry.account)
rate = tax_rate_map.get(tax_withholding_category)
# or else the consolidated value from the voucher document
if not tax_withholding_category:
# or else from the party default
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
if not tax_withholding_category:
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate:
@ -80,41 +85,41 @@ def get_result(
else:
total_amount += entry.credit
if tax_amount:
if party_map.get(party, {}).get("party_type") == "Supplier":
party_name = "supplier_name"
party_type = "supplier_type"
else:
party_name = "customer_name"
party_type = "customer_type"
if tax_amount:
if party_map.get(party, {}).get("party_type") == "Supplier":
party_name = "supplier_name"
party_type = "supplier_type"
else:
party_name = "customer_name"
party_type = "customer_type"
row = {
"pan"
if frappe.db.has_column(filters.party_type, "pan")
else "tax_id": party_map.get(party, {}).get("pan"),
"party": party_map.get(party, {}).get("name"),
}
if filters.naming_series == "Naming Series":
row.update({"party_name": party_map.get(party, {}).get(party_name)})
row.update(
{
"section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
"grand_total": grand_total,
"base_total": base_total,
"tax_amount": tax_amount,
"transaction_date": posting_date,
"transaction_type": voucher_type,
"ref_no": name,
"supplier_invoice_no": bill_no,
"supplier_invoice_date": bill_date,
row = {
"pan"
if frappe.db.has_column(filters.party_type, "pan")
else "tax_id": party_map.get(party, {}).get("pan"),
"party": party_map.get(party, {}).get("name"),
}
)
out.append(row)
if filters.naming_series == "Naming Series":
row.update({"party_name": party_map.get(party, {}).get(party_name)})
row.update(
{
"section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
"grand_total": grand_total,
"base_total": base_total,
"tax_amount": tax_amount,
"transaction_date": posting_date,
"transaction_type": voucher_type,
"ref_no": name,
"supplier_invoice_no": bill_no,
"supplier_invoice_date": bill_date,
}
)
out.append(row)
out.sort(key=lambda x: x["section_code"])
@ -282,11 +287,20 @@ def get_tds_docs(filters):
journal_entry_party_map = frappe._dict()
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
tds_accounts = frappe.get_all(
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
_tds_accounts = frappe.get_all(
"Tax Withholding Account",
{"company": filters.get("company")},
["account", "parent"],
)
tds_accounts = {}
for tds_acc in _tds_accounts:
# if it turns out not to be the only tax withholding category, then don't include in the map
if tds_accounts.get(tds_acc["account"]):
tds_accounts[tds_acc["account"]] = None
else:
tds_accounts[tds_acc["account"]] = tds_acc["parent"]
tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
tds_docs = get_tds_docs_query(filters, bank_accounts, list(tds_accounts.keys())).run(as_dict=True)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":

View File

@ -23,6 +23,10 @@ class TestUtils(unittest.TestCase):
super(TestUtils, cls).setUpClass()
make_test_objects("Address", ADDRESS_RECORDS)
@classmethod
def tearDownClass(cls):
frappe.db.rollback()
def test_get_party_shipping_address(self):
address = get_party_shipping_address("Customer", "_Test Customer 1")
self.assertEqual(address, "_Test Billing Address 2 Title-Billing")
@ -126,6 +130,38 @@ class TestUtils(unittest.TestCase):
self.assertEqual(len(payment_entry.references), 1)
self.assertEqual(payment_entry.difference_amount, 0)
def test_naming_series_variable_parsing(self):
"""
Tests parsing utility used by Naming Series Variable hook for FY
"""
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.utils import nowdate
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# Configure Supplier Naming in Buying Settings
frappe.db.set_default("supp_master_name", "Auto Name")
# Configure Autoname in Supplier DocType
make_property_setter(
"Supplier", None, "naming_rule", "Expression", "Data", for_doctype="Doctype"
)
make_property_setter(
"Supplier", None, "autoname", "SUP-.FY.-.#####", "Data", for_doctype="Doctype"
)
fiscal_year = get_fiscal_year(nowdate())[0]
# Create Supplier
supplier = create_supplier()
# Check Naming Series in generated Supplier ID
doc_name = supplier.name.split("-")
self.assertEqual(len(doc_name), 3)
self.assertSequenceEqual(doc_name[0:2], ("SUP", fiscal_year))
frappe.db.set_default("supp_master_name", "Supplier Name")
ADDRESS_RECORDS = [
{

View File

@ -240,7 +240,6 @@ def get_balance_on(
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account:
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read")
@ -286,18 +285,22 @@ def get_balance_on(
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party) or account_type:
precision = get_currency_precision()
if in_account_currency:
select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)"
select_field = (
"sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))"
)
else:
select_field = "sum(debit) - sum(credit)"
select_field = "sum(round(debit, %s)) - sum(round(credit, %s))"
bal = frappe.db.sql(
"""
SELECT {0}
FROM `tabGL Entry` gle
WHERE {1}""".format(
select_field, " and ".join(cond)
)
),
(precision, precision),
)[0][0]
# if bal is None, return 0
return flt(bal)
@ -453,7 +456,19 @@ def add_cc(args=None):
return cc.name
def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # nosemgrep
def _build_dimensions_dict_for_exc_gain_loss(
entry: dict | object = None, active_dimensions: list = None
):
dimensions_dict = frappe._dict()
if entry and active_dimensions:
for dim in active_dimensions:
dimensions_dict[dim.fieldname] = entry.get(dim.fieldname)
return dimensions_dict
def reconcile_against_document(
args, skip_ref_details_update_for_pe=False, active_dimensions=None
): # nosemgrep
"""
Cancel PE or JV, Update against document, split if required and resubmit
"""
@ -482,6 +497,8 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n
check_if_advance_entry_modified(entry)
validate_allocated_amount(entry)
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
# update ref in advance entry
if voucher_type == "Journal Entry":
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
@ -489,10 +506,14 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n
# amount and account in args
# referenced_row is used to deduplicate gain/loss journal
entry.update({"referenced_row": referenced_row})
doc.make_exchange_gain_loss_journal([entry])
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
else:
referenced_row = update_reference_in_payment_entry(
entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe
entry,
doc,
do_not_save=True,
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
dimensions_dict=dimensions_dict,
)
doc.save(ignore_permissions=True)
@ -600,7 +621,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they might be getting unlinked
if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if jv_detail.get("reference_type") in advance_payment_doctypes:
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
@ -642,7 +666,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
new_row.set("reference_name", d["against_voucher"])
new_row.against_account = cstr(jv_detail.against_account)
new_row.against_account_link = cstr(jv_detail.against_account)
new_row.is_advance = cstr(jv_detail.is_advance)
new_row.docstatus = 1
@ -655,7 +678,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
def update_reference_in_payment_entry(
d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False
d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False, dimensions_dict=None
):
reference_details = {
"reference_doctype": d.against_voucher_type,
@ -668,13 +691,17 @@ def update_reference_in_payment_entry(
else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.difference_amount,
"account": d.account,
"dimensions": d.dimensions,
}
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they are getting unlinked
if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"):
advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if existing_row.get("reference_doctype") in advance_payment_doctypes:
frappe.get_doc(
existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid()
@ -700,8 +727,9 @@ def update_reference_in_payment_entry(
if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details()
payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal(
frappe._dict({"difference_posting_date": d.difference_posting_date})
frappe._dict({"difference_posting_date": d.difference_posting_date}), dimensions_dict
)
if not do_not_save:
@ -1275,12 +1303,12 @@ def get_autoname_with_number(number_value, doc_title, company):
def parse_naming_series_variable(doc, variable):
if variable == "FY":
if doc:
date = doc.get("posting_date") or doc.get("transaction_date")
date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
company = doc.get("company")
else:
date = getdate()
company = None
return get_fiscal_year(date=date, company=company).name
return get_fiscal_year(date=date, company=company)[0]
@frappe.whitelist()
@ -2043,6 +2071,7 @@ def create_gain_loss_journal(
ref2_dn,
ref2_detail_no,
cost_center,
dimensions,
) -> str:
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = "Exchange Gain Or Loss"
@ -2076,7 +2105,8 @@ def create_gain_loss_journal(
dr_or_cr + "_in_account_currency": 0,
}
)
if dimensions:
journal_account.update(dimensions)
journal_entry.append("accounts", journal_account)
journal_account = frappe._dict(
@ -2092,7 +2122,8 @@ def create_gain_loss_journal(
reverse_dr_or_cr: abs(exc_gain_loss),
}
)
if dimensions:
journal_account.update(dimensions)
journal_entry.append("accounts", journal_account)
journal_entry.save()

View File

@ -5,7 +5,7 @@
"label": "Profit and Loss"
}
],
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"nDhfcJYbKH\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"9k1rDm2C0l\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Tax Masters\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [],
"docstatus": 0,
@ -14,562 +14,10 @@
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Accounting",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Masters",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Company",
"link_count": 0,
"link_to": "Company",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chart of Accounts",
"link_count": 0,
"link_to": "Account",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Settings",
"link_count": 0,
"link_to": "Accounts Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Fiscal Year",
"link_count": 0,
"link_to": "Fiscal Year",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Dimension",
"link_count": 0,
"link_to": "Accounting Dimension",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Finance Book",
"link_count": 0,
"link_to": "Finance Book",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Period",
"link_count": 0,
"link_to": "Accounting Period",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Term",
"link_count": 0,
"link_to": "Payment Term",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "General Ledger",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry",
"link_count": 0,
"link_to": "Journal Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry Template",
"link_count": 0,
"link_to": "Journal Entry Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "General Ledger",
"link_count": 0,
"link_to": "General Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Ledger Summary",
"link_count": 0,
"link_to": "Customer Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Receivable",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Sales Invoice",
"link_count": 0,
"link_to": "Sales Invoice",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Request",
"link_count": 0,
"link_to": "Payment Request",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable",
"link_count": 0,
"link_to": "Accounts Receivable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable Summary",
"link_count": 0,
"link_to": "Accounts Receivable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Register",
"link_count": 0,
"link_to": "Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Sales Register",
"link_count": 0,
"link_to": "Item-wise Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Order Analysis",
"link_count": 0,
"link_to": "Sales Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Delivered Items To Be Billed",
"link_count": 0,
"link_to": "Delivered Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Payable",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Invoice",
"link_count": 0,
"link_to": "Purchase Invoice",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier",
"link_count": 0,
"link_to": "Supplier",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Payable",
"link_count": 0,
"link_to": "Accounts Payable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Payable Summary",
"link_count": 0,
"link_to": "Accounts Payable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Register",
"link_count": 0,
"link_to": "Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Purchase Register",
"link_count": 0,
"link_to": "Item-wise Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Order",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Received Items To Be Billed",
"link_count": 0,
"link_to": "Received Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance for Party",
"link_count": 0,
"link_to": "Trial Balance for Party",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Journal Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Payment Period Based On Invoice Date",
"link_count": 0,
"link_to": "Payment Period Based On Invoice Date",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Partners Commission",
"link_count": 0,
"link_to": "Sales Partners Commission",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Credit Balance",
"link_count": 0,
"link_to": "Customer Credit Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Payment Summary",
"link_count": 0,
"link_to": "Sales Payment Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Address",
"hidden": 0,
"is_query_report": 1,
"label": "Address And Contacts",
"link_count": 0,
"link_to": "Address And Contacts",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "UAE VAT 201",
"link_count": 0,
"link_to": "UAE VAT 201",
"link_type": "Report",
"onboard": 0,
"only_for": "United Arab Emirates",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Financial Statements",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance",
"link_count": 0,
"link_to": "Trial Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profit and Loss Statement",
"link_count": 0,
"link_to": "Profit and Loss Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Balance Sheet",
"link_count": 0,
"link_to": "Balance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Cash Flow",
"link_count": 0,
"link_to": "Cash Flow",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Consolidated Financial Statement",
"link_count": 0,
"link_to": "Consolidated Financial Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -611,102 +59,6 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Gateway Account",
"link_count": 0,
"link_to": "Payment Gateway Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Terms and Conditions Template",
"link_count": 0,
"link_to": "Terms and Conditions",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Mode of Payment",
"link_count": 0,
"link_to": "Mode of Payment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank",
"link_count": 0,
"link_to": "Bank",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Account",
"link_count": 0,
"link_to": "Bank Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Clearance",
"link_count": 0,
"link_to": "Bank Clearance",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Reconciliation Tool",
"link_count": 0,
"link_to": "Bank Reconciliation Tool",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Bank Reconciliation Statement",
"link_count": 0,
"link_to": "Bank Reconciliation Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -918,8 +270,83 @@
{
"hidden": 0,
"is_query_report": 0,
"label": "Taxes",
"label": "Banking",
"link_count": 6,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank",
"link_count": 0,
"link_to": "Bank",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Account",
"link_count": 0,
"link_to": "Bank Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Clearance",
"link_count": 0,
"link_to": "Bank Clearance",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Reconciliation Tool",
"link_count": 0,
"link_to": "Bank Reconciliation Tool",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Bank Reconciliation Statement",
"link_count": 0,
"link_to": "Bank Reconciliation Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Plaid Settings",
"link_count": 0,
"link_to": "Plaid Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Tax Masters",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
@ -1003,60 +430,8 @@
{
"hidden": 0,
"is_query_report": 0,
"label": "Profitability",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Gross Profit",
"link_count": 0,
"link_to": "Gross Profit",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profitability Analysis",
"link_count": 0,
"link_to": "Profitability Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Invoice Trends",
"link_count": 0,
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Invoice Trends",
"link_count": 0,
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Banking",
"link_count": 6,
"label": "Accounting Masters",
"link_count": 8,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
@ -1065,9 +440,31 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank",
"label": "Company",
"link_count": 0,
"link_to": "Bank",
"link_to": "Company",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chart of Accounts",
"link_count": 0,
"link_to": "Account",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Settings",
"link_count": 0,
"link_to": "Accounts Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@ -1076,9 +473,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Account",
"label": "Fiscal Year",
"link_count": 0,
"link_to": "Bank Account",
"link_to": "Fiscal Year",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@ -1087,9 +484,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Clearance",
"label": "Accounting Dimension",
"link_count": 0,
"link_to": "Bank Clearance",
"link_to": "Accounting Dimension",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@ -1098,36 +495,98 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Reconciliation Tool",
"label": "Finance Book",
"link_count": 0,
"link_to": "Bank Reconciliation Tool",
"link_to": "Finance Book",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Bank Reconciliation Statement",
"is_query_report": 0,
"label": "Accounting Period",
"link_count": 0,
"link_to": "Bank Reconciliation Statement",
"link_type": "Report",
"link_to": "Accounting Period",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Term",
"link_count": 0,
"link_to": "Payment Term",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Plaid Settings",
"label": "Payments",
"link_count": 5,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Plaid Settings",
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry",
"link_count": 0,
"link_to": "Journal Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry Template",
"link_count": 0,
"link_to": "Journal Entry Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Terms and Conditions",
"link_count": 0,
"link_to": "Terms and Conditions",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Mode of Payment",
"link_count": 0,
"link_to": "Mode of Payment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2024-01-02 15:21:09.895531",
"modified": "2024-01-18 22:15:40.941711",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",

View File

@ -0,0 +1,277 @@
{
"charts": [],
"content": "[{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Ledgers\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2024-01-05 16:09:16.766939",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "file",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Financial Reports",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Profitability",
"link_count": 0,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Gross Profit",
"link_count": 0,
"link_to": "Gross Profit",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profitability Analysis",
"link_count": 0,
"link_to": "Profitability Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Invoice Trends",
"link_count": 0,
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Invoice Trends",
"link_count": 0,
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Financial Statements",
"link_count": 5,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance",
"link_count": 0,
"link_to": "Trial Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profit and Loss Statement",
"link_count": 0,
"link_to": "Profit and Loss Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Balance Sheet",
"link_count": 0,
"link_to": "Balance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Cash Flow",
"link_count": 0,
"link_to": "Cash Flow",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Consolidated Financial Statement",
"link_count": 0,
"link_to": "Consolidated Financial Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Ledgers",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "General Ledger",
"link_count": 0,
"link_to": "General Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Ledger Summary",
"link_count": 0,
"link_to": "Customer Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Other Reports",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance for Party",
"link_count": 0,
"link_to": "Trial Balance for Party",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Journal Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Payment Period Based On Invoice Date",
"link_count": 0,
"link_to": "Payment Period Based On Invoice Date",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Partners Commission",
"link_count": 0,
"link_to": "Sales Partners Commission",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Credit Balance",
"link_count": 0,
"link_to": "Customer Credit Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Payment Summary",
"link_count": 0,
"link_to": "Sales Payment Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Address",
"hidden": 0,
"is_query_report": 1,
"label": "Address And Contacts",
"link_count": 0,
"link_to": "Address And Contacts",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "UAE VAT 201",
"link_count": 0,
"link_to": "UAE VAT 201",
"link_type": "Report",
"onboard": 0,
"only_for": "United Arab Emirates",
"type": "Link"
}
],
"modified": "2024-01-18 22:13:07.596844",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Financial Reports",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 5.0,
"shortcuts": [],
"title": "Financial Reports"
}

View File

@ -0,0 +1,204 @@
{
"charts": [],
"content": "[{\"id\":\"rMMsfn2eB4\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Shortcuts</b></span>\",\"col\":12}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Payable\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jAcOH-cC-Q\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"7dj93PEUjW\",\"type\":\"card\",\"data\":{\"card_name\":\"Invoicing\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"9yseIkdG50\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2024-01-05 15:29:11.144373",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "arrow-left",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Payables",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Invoicing",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Invoice",
"link_count": 0,
"link_to": "Purchase Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Supplier",
"link_count": 0,
"link_to": "Supplier",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry",
"link_count": 0,
"link_to": "Journal Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Payable",
"link_count": 0,
"link_to": "Accounts Payable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Payable Summary",
"link_count": 0,
"link_to": "Accounts Payable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Register",
"link_count": 0,
"link_to": "Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Item-wise Purchase Register",
"link_count": 0,
"link_to": "Item-wise Purchase Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Received Items To Be Billed",
"link_count": 0,
"link_to": "Received Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2024-01-18 22:09:46.221549",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payables",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 3.0,
"shortcuts": [
{
"doc_view": "",
"label": "Accounts Payable",
"link_to": "Accounts Payable",
"type": "Report"
},
{
"doc_view": "",
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"type": "DocType"
},
{
"doc_view": "",
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Payment Entry",
"link_to": "Payment Entry",
"type": "DocType"
}
],
"title": "Payables"
}

View File

@ -0,0 +1,254 @@
{
"charts": [],
"content": "[{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Shortcuts</b></span>\",\"col\":12}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"5yHldR0JNk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"POS Invoice\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"ILlIxJuexy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Cost Center\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"jLgv00c6ek\",\"type\":\"card\",\"data\":{\"card_name\":\"Invoicing\",\"col\":4}},{\"id\":\"npwfXlz0u1\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"am70C27Jrb\",\"type\":\"card\",\"data\":{\"card_name\":\"Dunning\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2024-01-05 15:29:21.084241",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "arrow-right",
"idx": 0,
"indicator_color": "",
"is_hidden": 0,
"label": "Receivables",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Invoicing",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Invoice",
"link_count": 0,
"link_to": "Sales Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 4,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Request",
"link_count": 0,
"link_to": "Payment Request",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Reconciliation",
"link_count": 0,
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Gateway Account",
"link_count": 0,
"link_to": "Payment Gateway Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning",
"link_count": 2,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning",
"link_count": 0,
"link_to": "Dunning",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dunning Type",
"link_count": 0,
"link_to": "Dunning Type",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 6,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable",
"link_count": 0,
"link_to": "Accounts Receivable",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Accounts Receivable Summary",
"link_count": 0,
"link_to": "Accounts Receivable Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Register",
"link_count": 0,
"link_to": "Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Sales Register",
"link_count": 0,
"link_to": "Item-wise Sales Register",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Order Analysis",
"link_count": 0,
"link_to": "Sales Order Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Delivered Items To Be Billed",
"link_count": 0,
"link_to": "Delivered Items To Be Billed",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2024-01-18 22:11:51.474477",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Receivables",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 4.0,
"shortcuts": [
{
"color": "Grey",
"doc_view": "List",
"label": "POS Invoice",
"link_to": "POS Invoice",
"stats_filter": "[]",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "List",
"label": "Cost Center",
"link_to": "Cost Center",
"type": "DocType"
},
{
"doc_view": "",
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"stats_filter": "[]",
"type": "DocType"
},
{
"doc_view": "",
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Payment Entry",
"link_to": "Payment Entry",
"type": "DocType"
},
{
"doc_view": "",
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"type": "Report"
}
],
"title": "Receivables"
}

View File

@ -571,16 +571,16 @@ frappe.ui.form.on('Asset', {
indicator: 'red'
});
}
var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset');
var asset_quantity = is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
frm.set_value('gross_purchase_amount', purchase_amount);
frm.set_value('purchase_receipt_amount', purchase_amount);
frm.set_value('asset_quantity', asset_quantity);
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
if(item.asset_location) { frm.set_value('location', item.asset_location); }
frappe.db.get_value('Item', item.item_code, 'is_grouped_asset', (r) => {
var asset_quantity = r.is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
frm.set_value('gross_purchase_amount', purchase_amount);
frm.set_value('purchase_receipt_amount', purchase_amount);
frm.set_value('asset_quantity', asset_quantity);
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
if(item.asset_location) { frm.set_value('location', item.asset_location); }
});
},
set_depreciation_rate: function(frm, row) {

View File

@ -202,8 +202,7 @@
"fieldname": "purchase_date",
"fieldtype": "Date",
"label": "Purchase Date",
"mandatory_depends_on": "eval:!doc.is_existing_asset",
"read_only": 1,
"mandatory_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
},
{
@ -590,7 +589,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-01-05 17:36:53.131512",
"modified": "2024-01-15 17:35:49.226603",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@ -162,6 +162,7 @@ class Asset(AccountsController):
def on_cancel(self):
self.validate_cancellation()
self.cancel_movement_entries()
self.cancel_capitalization()
self.delete_depreciation_entries()
cancel_asset_depr_schedules(self)
self.set_status()
@ -517,6 +518,13 @@ class Asset(AccountsController):
movement = frappe.get_doc("Asset Movement", movement.get("name"))
movement.cancel()
def cancel_capitalization(self):
if self.capitalized_in:
self.db_set("capitalized_in", None)
asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in)
if asset_capitalization.docstatus == 1:
asset_capitalization.cancel()
def delete_depreciation_entries(self):
if self.calculate_depreciation:
for row in self.get("finance_books"):
@ -697,9 +705,7 @@ class Asset(AccountsController):
self.get_gl_dict(
{
"account": cwip_account,
"against_type": "Account",
"against": fixed_asset_account,
"against_link": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
@ -714,9 +720,7 @@ class Asset(AccountsController):
self.get_gl_dict(
{
"account": fixed_asset_account,
"against_type": "Account",
"against": cwip_account,
"against_link": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount,
@ -1004,7 +1008,7 @@ def make_asset_movement(assets, purpose=None):
assets = json.loads(assets)
if len(assets) == 0:
frappe.throw(_("Atleast one asset has to be selected."))
frappe.throw(_("At least one asset has to be selected."))
asset_movement = frappe.new_doc("Asset Movement")
asset_movement.quantity = len(assets)
@ -1031,6 +1035,8 @@ def is_cwip_accounting_enabled(asset_category):
@frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name)
if not asset.calculate_depreciation:
return flt(asset.value_after_depreciation)
return asset.get_value_after_depreciation(finance_book)

View File

@ -561,6 +561,8 @@ def modify_depreciation_schedule_for_asset_repairs(asset, notes):
def reverse_depreciation_entry_made_after_disposal(asset, date):
for row in asset.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
if not asset_depr_schedule_doc:
continue
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if schedule.schedule_date == date:

View File

@ -251,16 +251,7 @@ class TestAsset(AssetSetup):
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
(
"_Test Fixed Asset - _TC",
0.0,
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
),
(
"_Test Fixed Asset - _TC",
0.0,
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
(
"_Test Gain/Loss on Asset Disposal - _TC",
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
@ -900,7 +891,7 @@ class TestDepreciationMethods(AssetSetup):
["2030-12-31", 28630.14, 28630.14],
["2031-12-31", 35684.93, 64315.07],
["2032-12-31", 17842.46, 82157.53],
["2033-06-06", 5342.47, 87500.0],
["2033-06-06", 5342.46, 87499.99],
]
schedules = [
@ -1012,7 +1003,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
depreciation_amount = get_depreciation_amount(
asset_depr_schedule_doc, asset, 100000, asset.finance_books[0]
asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0]
)
self.assertEqual(depreciation_amount, 30000)

View File

@ -21,10 +21,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
this.show_stock_ledger();
}
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
this.get_target_asset_details();
}
// if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
// this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
// this.get_target_asset_details();
// }
}
setup_queries() {
@ -143,13 +143,20 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
},
callback: function (r) {
if (!r.exc && r.message) {
me.frm.clear_table("stock_items");
for (let item of r.message) {
me.frm.add_child("stock_items", item);
if(r.message[0] && r.message[0].length) {
me.frm.clear_table("stock_items");
for (let item of r.message[0]) {
me.frm.add_child("stock_items", item);
}
refresh_field("stock_items");
}
if (r.message[1] && r.message[1].length) {
me.frm.clear_table("asset_items");
for (let item of r.message[1]) {
me.frm.add_child("asset_items", item);
}
me.frm.refresh_field("asset_items");
}
refresh_field("stock_items");
me.calculate_totals();
}

View File

@ -136,11 +136,20 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Serial and Batch Bundle",
"Asset",
)
self.cancel_target_asset()
self.update_stock_ledger()
self.make_gl_entries()
self.restore_consumed_asset_items()
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
if asset_doc.docstatus == 1:
asset_doc.cancel()
def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
@ -485,9 +494,7 @@ class AssetCapitalization(StockController):
self.get_gl_dict(
{
"account": account,
"against_type": "Account",
"against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -528,9 +535,7 @@ class AssetCapitalization(StockController):
self.set_consumed_asset_status(asset)
for gle in fixed_asset_gl_entries:
gle["against_type"] = "Account"
gle["against"] = target_account
gle["against_link"] = target_account
gl_entries.append(self.get_gl_dict(gle, item=item))
target_against.add(gle["account"])
@ -546,9 +551,7 @@ class AssetCapitalization(StockController):
self.get_gl_dict(
{
"account": item_row.expense_account,
"against_type": "Account",
"against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -559,46 +562,41 @@ class AssetCapitalization(StockController):
)
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
for target_account in target_against:
if self.target_is_fixed_asset:
# Capitalization
if self.target_is_fixed_asset:
# Capitalization
gl_entries.append(
self.get_gl_dict(
{
"account": self.target_fixed_asset_account,
"against": ", ".join(target_against),
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": flt(self.total_value, precision),
"cost_center": self.get("cost_center"),
},
item=self,
)
)
else:
# Target Stock Item
sle_list = self.sle_map.get(self.name)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
account = self.warehouse_account[sle.warehouse]["account"]
gl_entries.append(
self.get_gl_dict(
{
"account": self.target_fixed_asset_account,
"against_type": "Account",
"against": target_account,
"against_link": target_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": flt(self.total_value, precision) / len(target_against),
"cost_center": self.get("cost_center"),
"account": account,
"against": ", ".join(target_against),
"cost_center": self.cost_center,
"project": self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference,
},
self.warehouse_account[sle.warehouse]["account_currency"],
item=self,
)
)
else:
# Target Stock Item
sle_list = self.sle_map.get(self.name)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
account = self.warehouse_account[sle.warehouse]["account"]
gl_entries.append(
self.get_gl_dict(
{
"account": account,
"against_type": "Account",
"against": target_account,
"against_link": target_account,
"cost_center": self.cost_center,
"project": self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference / len(target_against),
},
self.warehouse_account[sle.warehouse]["account_currency"],
item=self,
)
)
def create_target_asset(self):
if (
@ -892,7 +890,6 @@ def get_consumed_asset_details(args):
out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
)
return out
@ -940,10 +937,27 @@ def get_items_tagged_to_wip_composite_asset(asset):
"qty",
"valuation_rate",
"amount",
"is_fixed_asset",
"parent",
]
pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
"Purchase Receipt Item", filters={"wip_composite_asset": asset, "docstatus": 1}, fields=fields
)
return pr_items
stock_items = []
asset_items = []
for d in pr_items:
if not d.is_fixed_asset:
stock_items.append(frappe._dict(d))
else:
asset_details = frappe.db.get_value(
"Asset",
{"item_code": d.item_code, "purchase_receipt": d.parent},
["name as asset", "asset_name"],
as_dict=1,
)
d.update(asset_details)
asset_items.append(frappe._dict(d))
return stock_items, asset_items

View File

@ -98,12 +98,12 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries
expected_gle = {
"_Test Fixed Asset - TCP1": 2999.99,
"_Test Fixed Asset - TCP1": 3000,
"Expenses Included In Asset Valuation - TCP1": -1000,
"_Test Warehouse - TCP1": -2000,
"Round Off - TCP1": 0.01,
}
actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle)
# Test Stock Ledger Entries
@ -189,10 +189,9 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
expected_gle = {
"_Test Fixed Asset - _TC": 2999.99,
"_Test Fixed Asset - _TC": 3000,
"Expenses Included In Asset Valuation - _TC": -1000,
default_expense_account: -2000,
"Round Off - _TC": 0.01,
}
actual_gle = get_actual_gle_dict(asset_capitalization.name)
@ -377,10 +376,9 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries
expected_gle = {
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
"_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
"_Test Fixed Asset - TCP1": -consumed_asset_purchase_value,
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal - 0.01,
"Round Off - TCP1": 0.01,
}
actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle)

View File

@ -7,6 +7,7 @@ from frappe.model.document import Document
from frappe.utils import (
add_days,
add_months,
add_years,
cint,
date_diff,
flt,
@ -18,6 +19,7 @@ from frappe.utils import (
)
import erpnext
from erpnext.accounts.utils import get_fiscal_year
class AssetDepreciationSchedule(Document):
@ -283,12 +285,20 @@ class AssetDepreciationSchedule(Document):
depreciation_amount = 0
number_of_pending_depreciations = final_number_of_depreciations - start
yearly_opening_wdv = value_after_depreciation
current_fiscal_year_end_date = None
for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
if not current_fiscal_year_end_date:
current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
current_fiscal_year_end_date = add_years(current_fiscal_year_end_date, 1)
yearly_opening_wdv = value_after_depreciation
if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
else:
@ -298,6 +308,7 @@ class AssetDepreciationSchedule(Document):
self,
asset_doc,
value_after_depreciation,
yearly_opening_wdv,
row,
n,
prev_depreciation_amount,
@ -401,8 +412,9 @@ class AssetDepreciationSchedule(Document):
if not depreciation_amount:
continue
value_after_depreciation -= flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount")
value_after_depreciation = flt(
value_after_depreciation - flt(depreciation_amount),
asset_doc.precision("gross_purchase_amount"),
)
# Adjust depreciation amount in the last period based on the expected value after useful life
@ -582,6 +594,7 @@ def get_depreciation_amount(
asset_depr_schedule,
asset,
depreciable_value,
yearly_opening_wdv,
fb_row,
schedule_idx=0,
prev_depreciation_amount=0,
@ -597,6 +610,7 @@ def get_depreciation_amount(
asset,
fb_row,
depreciable_value,
yearly_opening_wdv,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
@ -744,6 +758,7 @@ def get_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
yearly_opening_wdv,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,

View File

@ -40,7 +40,7 @@ class AssetMaintenance(Document):
if getdate(task.next_due_date) < getdate(nowdate()):
task.maintenance_status = "Overdue"
if not task.assign_to and self.docstatus == 0:
throw(_("Row #{}: Please asign task to a member.").format(task.idx))
throw(_("Row #{}: Please assign task to a member.").format(task.idx))
def on_update(self):
for task in self.get("asset_maintenance_tasks"):

View File

@ -285,9 +285,7 @@ class AssetRepair(AccountsController):
"account": fixed_asset_account,
"debit": self.repair_cost,
"debit_in_account_currency": self.repair_cost,
"against_type": "Account",
"against": pi_expense_account,
"against_link": pi_expense_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
@ -306,9 +304,7 @@ class AssetRepair(AccountsController):
"account": pi_expense_account,
"credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost,
"against_type": "Account",
"against": fixed_asset_account,
"against_link": fixed_asset_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
@ -342,9 +338,7 @@ class AssetRepair(AccountsController):
"account": item.expense_account or default_expense_account,
"credit": item.amount,
"credit_in_account_currency": item.amount,
"against_type": "Account",
"against": fixed_asset_account,
"against_link": fixed_asset_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
@ -361,9 +355,7 @@ class AssetRepair(AccountsController):
"account": fixed_asset_account,
"debit": item.amount,
"debit_in_account_currency": item.amount,
"against_type": "Account",
"against": item.expense_account or default_expense_account,
"against_link": item.expense_account or default_expense_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,

View File

@ -2,14 +2,14 @@
"action": "Show Form Tour",
"action_label": "Let's review existing Asset Category",
"creation": "2021-08-13 14:26:18.656303",
"description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipments\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
"description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipment\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2021-11-23 10:02:03.242127",
"modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator",
"name": "Asset Category",
"owner": "Administrator",

View File

@ -202,7 +202,7 @@ def prepare_chart_data(data, filters):
"values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()],
},
{
"name": _("Depreciatied Amount"),
"name": _("Depreciated Amount"),
"values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
},
],

View File

@ -196,18 +196,18 @@
"type": "Link"
}
],
"modified": "2023-05-24 14:47:20.243146",
"modified": "2024-01-05 17:40:34.570041",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
"number_cards": [],
"owner": "Administrator",
"parent_page": "Accounting",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 4.0,
"sequence_id": 7.0,
"shortcuts": [
{
"label": "Asset",

View File

@ -214,7 +214,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-05 15:26:02.320942",
"modified": "2024-01-12 16:42:01.894346",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@ -240,39 +240,24 @@
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts User",
"share": 1
"role": "Accounts User"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1
"role": "Accounts Manager"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Stock Manager",
"share": 1
"role": "Stock Manager"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Stock User",
"share": 1
"role": "Stock User"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Purchase User",
"share": 1
"role": "Purchase User"
}
],
"sort_field": "modified",

View File

@ -134,6 +134,7 @@
"more_info_tab",
"tracking_section",
"status",
"advance_payment_status",
"column_break_75",
"per_billed",
"per_received",
@ -1269,13 +1270,25 @@
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
},
{
"fieldname": "advance_payment_status",
"fieldtype": "Select",
"hidden": 1,
"in_standard_filter": 1,
"label": "Advance Payment Status",
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "Not Initiated\nInitiated\nPartially Paid\nFully Paid",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-10-01 20:58:07.851037",
"modified": "2023-10-10 13:37:40.158761",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@ -1330,4 +1343,4 @@
"timeline_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1
}
}

View File

@ -215,6 +215,10 @@ class PurchaseOrder(BuyingController):
self.validate_fg_item_for_subcontracting()
self.set_received_qty_for_drop_ship_items()
if not self.advance_payment_status:
self.advance_payment_status = "Not Initiated"
validate_inter_company_party(
self.doctype, self.supplier, self.company, self.inter_company_order_reference
)

View File

@ -1,6 +1,6 @@
frappe.listview_settings['Purchase Order'] = {
add_fields: ["base_grand_total", "company", "currency", "supplier",
"supplier_name", "per_received", "per_billed", "status"],
"supplier_name", "per_received", "per_billed", "status", "advance_payment_status"],
get_indicator: function (doc) {
if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
@ -8,6 +8,8 @@ frappe.listview_settings['Purchase Order'] = {
return [__("On Hold"), "orange", "status,=,On Hold"];
} else if (doc.status === "Delivered") {
return [__("Delivered"), "green", "status,=,Closed"];
} else if (doc.advance_payment_status == "Initiated") {
return [__("To Pay"), "gray", "advance_payment_status,=,Initiated"];
} else if (flt(doc.per_received, 2) < 100 && doc.status !== "Closed") {
if (flt(doc.per_billed, 2) < 100) {
return [__("To Receive and Bill"), "orange",

View File

@ -1021,6 +1021,33 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertTrue(frappe.db.get_value("Subcontracting Order", {"purchase_order": po.name}))
def test_purchase_order_advance_payment_status(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
po = create_purchase_order()
self.assertEqual(
frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
)
pr = make_payment_request(dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True)
self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated")
pe = get_payment_entry(po.doctype, po.name).save().submit()
self.assertEqual(
frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Fully Paid"
)
pe.reload()
pe.cancel()
self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated")
pr.reload()
pr.cancel()
self.assertEqual(
frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated"
)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@ -54,7 +54,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
"fieldtype": "MultiSelectList",
"width": "80",
get_data: function(txt) {
let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]
let status = ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"]
let options = []
for (let option of status){
options.push({

View File

@ -7,6 +7,7 @@ import json
import frappe
from frappe import _, bold, qb, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder import Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
@ -21,12 +22,14 @@ from frappe.utils import (
get_link_to_form,
getdate,
nowdate,
parse_json,
today,
)
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimensions,
)
from erpnext.accounts.doctype.pricing_rule.utils import (
apply_pricing_rule_for_free_items,
@ -831,6 +834,37 @@ class AccountsController(TransactionBase):
self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
def append_taxes_from_item_tax_template(self):
if not frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
return
for row in self.items:
item_tax_rate = row.get("item_tax_rate")
if not item_tax_rate:
continue
if isinstance(item_tax_rate, str):
item_tax_rate = parse_json(item_tax_rate)
for account_head, rate in item_tax_rate.items():
row = self.get_tax_row(account_head)
if not row:
self.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": account_head,
"rate": 0,
"description": account_head,
},
)
def get_tax_row(self, account_head):
for row in self.taxes:
if row.account_head == account_head:
return row
def set_other_charges(self):
self.set("taxes", [])
self.set_taxes()
@ -930,7 +964,7 @@ class AccountsController(TransactionBase):
# Update details in transaction currency
gl_dict.update(
{
"transaction_currency": args.get("currency") or self.get("currency") or self.company_currency,
"transaction_currency": self.get("currency") or self.company_currency,
"transaction_exchange_rate": self.get("conversion_rate", 1),
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, args, "debit"
@ -969,10 +1003,10 @@ class AccountsController(TransactionBase):
return self.doctype
def get_value_in_transaction_currency(self, account_currency, args, field):
if account_currency == args.get("currency") or self.get("currency"):
if account_currency == self.get("currency"):
return args.get(field + "_in_account_currency")
else:
return flt(args.get(field, 0) / (args.get("conversion_rate") or self.get("conversion_rate", 1)))
return flt(args.get(field, 0) / self.get("conversion_rate", 1))
def validate_qty_is_not_zero(self):
for item in self.items:
@ -1161,7 +1195,6 @@ class AccountsController(TransactionBase):
)
credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit"
against_type = "Supplier" if self.doctype == "Purchase Invoice" else "Customer"
against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
if precision_loss:
@ -1169,9 +1202,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict(
{
"account": round_off_account,
"against_type": against_type,
"against": against,
"against_link": against,
credit_or_debit: precision_loss,
"cost_center": round_off_cost_center
if self.use_company_roundoff_cost_center
@ -1219,7 +1250,9 @@ class AccountsController(TransactionBase):
return True
return False
def make_exchange_gain_loss_journal(self, args: dict = None) -> None:
def make_exchange_gain_loss_journal(
self, args: dict = None, dimensions_dict: dict = None
) -> None:
"""
Make Exchange Gain/Loss journal for Invoices and Payments
"""
@ -1274,6 +1307,7 @@ class AccountsController(TransactionBase):
self.name,
arg.get("referenced_row"),
arg.get("cost_center"),
dimensions_dict,
)
frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format(
@ -1354,6 +1388,7 @@ class AccountsController(TransactionBase):
self.name,
d.idx,
self.cost_center,
dimensions_dict,
)
frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format(
@ -1418,14 +1453,25 @@ class AccountsController(TransactionBase):
if lst:
from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst)
# pass dimension values to utility method
active_dimensions = get_dimensions()[0]
for x in lst:
for dim in active_dimensions:
if self.get(dim.fieldname):
x.update({dim.fieldname: self.get(dim.fieldname)})
reconcile_against_document(lst, active_dimensions=active_dimensions)
def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
remove_from_bank_transaction,
)
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
unlink_ref_doc_from_payment_entries,
)
remove_from_bank_transaction(self.doctype, self.name)
if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
# Cancel Exchange Gain/Loss Journal before unlinking
cancel_exchange_gain_loss_journal(self)
@ -1520,13 +1566,11 @@ class AccountsController(TransactionBase):
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit"
rev_dr_cr = "debit"
against_type = "Supplier"
supplier_or_customer = self.supplier
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
against_type = "Customer"
supplier_or_customer = self.customer
if enable_discount_accounting:
@ -1551,9 +1595,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict(
{
"account": item.discount_account,
"against_type": against_type,
"against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
),
@ -1571,9 +1613,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict(
{
"account": income_or_expense_account,
"against_type": against_type,
"against": supplier_or_customer,
"against_link": supplier_or_customer,
rev_dr_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
),
@ -1596,9 +1636,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict(
{
"account": self.additional_discount_account,
"against_type": against_type,
"against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: self.base_discount_amount,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
},
@ -1755,7 +1793,10 @@ class AccountsController(TransactionBase):
def set_total_advance_paid(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
party = self.customer if self.doctype == "Sales Order" else self.supplier
if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
party = self.customer
if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
party = self.supplier
advance = (
frappe.qb.from_(ple)
.select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount"))
@ -1769,6 +1810,8 @@ class AccountsController(TransactionBase):
.run(as_dict=True)
)
advance_paid, order_total = None, None
if advance:
advance = advance[0]
@ -1797,7 +1840,38 @@ class AccountsController(TransactionBase):
).format(formatted_advance_paid, self.name, formatted_order_total)
)
frappe.db.set_value(self.doctype, self.name, "advance_paid", advance_paid)
self.db_set("advance_paid", advance_paid)
self.set_advance_payment_status(advance_paid, order_total)
def set_advance_payment_status(
self, advance_paid: float | None = None, order_total: float | None = None
):
new_status = None
# if money is paid set the paid states
if advance_paid:
new_status = "Partially Paid" if advance_paid < order_total else "Fully Paid"
if not new_status:
prs = frappe.db.count(
"Payment Request",
{
"reference_doctype": self.doctype,
"reference_name": self.name,
"docstatus": 1,
},
)
if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
new_status = "Requested" if prs else "Not Requested"
if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
new_status = "Initiated" if prs else "Not Initiated"
if new_status == self.advance_payment_status:
return
self.db_set("advance_payment_status", new_status)
self.set_status(update=True)
self.notify_update()
@property
def company_abbr(self):
@ -2690,47 +2764,37 @@ def get_common_query(
q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
if condition:
if condition.get("name", None):
q = q.where(payment_entry.name.like(f"%{condition.get('name')}%"))
# conditions should be built as an array and passed as Criterion
common_filter_conditions = []
common_filter_conditions.append(payment_entry.company == condition["company"])
if condition.get("name", None):
common_filter_conditions.append(payment_entry.name.like(f"%{condition.get('name')}%"))
if condition.get("from_payment_date"):
common_filter_conditions.append(payment_entry.posting_date.gte(condition["from_payment_date"]))
if condition.get("to_payment_date"):
common_filter_conditions.append(payment_entry.posting_date.lte(condition["to_payment_date"]))
q = q.where(payment_entry.company == condition["company"])
q = (
q.where(payment_entry.posting_date >= condition["from_payment_date"])
if condition.get("from_payment_date")
else q
)
q = (
q.where(payment_entry.posting_date <= condition["to_payment_date"])
if condition.get("to_payment_date")
else q
)
if condition.get("get_payments") == True:
q = (
q.where(payment_entry.cost_center == condition["cost_center"])
if condition.get("cost_center")
else q
)
q = (
q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"])
if condition.get("minimum_payment_amount")
else q
)
q = (
q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"])
if condition.get("maximum_payment_amount")
else q
)
else:
q = (
q.where(payment_entry.total_debit >= condition["minimum_payment_amount"])
if condition.get("minimum_payment_amount")
else q
)
q = (
q.where(payment_entry.total_debit <= condition["maximum_payment_amount"])
if condition.get("maximum_payment_amount")
else q
)
if condition.get("cost_center"):
common_filter_conditions.append(payment_entry.cost_center == condition["cost_center"])
if condition.get("accounting_dimensions"):
for field, val in condition.get("accounting_dimensions").items():
common_filter_conditions.append(payment_entry[field] == val)
if condition.get("minimum_payment_amount"):
common_filter_conditions.append(
payment_entry.unallocated_amount.gte(condition["minimum_payment_amount"])
)
if condition.get("maximum_payment_amount"):
common_filter_conditions.append(
payment_entry.unallocated_amount.lte(condition["maximum_payment_amount"])
)
q = q.where(Criterion.all(common_filter_conditions))
q = q.orderby(payment_entry.posting_date)
q = q.limit(limit) if limit else q

View File

@ -56,7 +56,11 @@ def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part
copy_attributes_to_variant(template, variant)
variant.item_code = append_number_if_name_exists("Item", template.name)
variant_name = f"{template.name} - {manufacturer}"
if manufacturer_part_no:
variant_name += f" - {manufacturer_part_no}"
variant.item_code = append_number_if_name_exists("Item", variant_name)
variant.flags.ignore_mandatory = True
variant.save()

View File

@ -6,10 +6,12 @@ import json
from collections import OrderedDict, defaultdict
import frappe
from frappe import scrub
from frappe import qb, scrub
from frappe.desk.reportview import get_filters_cond, get_match_cond
from frappe.query_builder.functions import Concat, Sum
from frappe.query_builder import Criterion, CustomFunction
from frappe.query_builder.functions import Concat, Locate, Sum
from frappe.utils import nowdate, today, unique
from pypika import Order
import erpnext
from erpnext.stock.get_item_details import _get_item_tax_template
@ -344,37 +346,46 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
doctype = "Project"
cond = ""
proj = qb.DocType("Project")
qb_filter_and_conditions = []
qb_filter_or_conditions = []
ifelse = CustomFunction("IF", ["condition", "then", "else"])
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" % (
frappe.db.escape(filters.get("customer"))
)
qb_filter_and_conditions.append(proj.customer == filters.get("customer"))
qb_filter_and_conditions.append(proj.status.notin(["Completed", "Cancelled"]))
q = qb.from_(proj)
fields = get_fields(doctype, ["name", "project_name"])
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
for x in fields:
q = q.select(proj[x])
return frappe.db.sql(
"""select {fields} from `tabProject`
where
`tabProject`.status not in ('Completed', 'Cancelled')
and {cond} {scond} {match_cond}
order by
(case when locate(%(_txt)s, `tabProject`.name) > 0 then locate(%(_txt)s, `tabProject`.name) else 99999 end),
`tabProject`.idx desc,
`tabProject`.name asc
limit {page_len} offset {start}""".format(
fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
cond=cond,
scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len,
),
{"txt": "%{0}%".format(txt), "_txt": txt.replace("%", "")},
)
# don't consider 'customer' and 'status' fields for pattern search, as they must be exactly matched
searchfields = [
x for x in frappe.get_meta(doctype).get_search_fields() if x not in ["customer", "status"]
]
# pattern search
if txt:
for x in searchfields:
qb_filter_or_conditions.append(proj[x].like(f"%{txt}%"))
q = q.where(Criterion.all(qb_filter_and_conditions)).where(Criterion.any(qb_filter_or_conditions))
# ordering
if txt:
# project_name containing search string 'txt' will be given higher precedence
q = q.orderby(ifelse(Locate(txt, proj.project_name) > 0, Locate(txt, proj.project_name), 99999))
q = q.orderby(proj.idx, order=Order.desc).orderby(proj.name)
if page_len:
q = q.limit(page_len)
if start:
q = q.offset(start)
return q.run()
@frappe.whitelist()
@ -421,23 +432,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
query = get_batches_from_stock_ledger_entries(searchfields, txt, filters)
bundle_query = get_batches_from_serial_and_batch_bundle(searchfields, txt, filters)
data = (
frappe.qb.from_((query) + (bundle_query))
.select("batch_no", "qty", "manufacturing_date", "expiry_date")
.offset(start)
.limit(page_len)
batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len)
batches.extend(
get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)
)
for field in searchfields:
data = data.select(field)
filtered_batches = get_filterd_batches(batches)
data = data.run()
data = get_filterd_batches(data)
return data
return filtered_batches
def get_filterd_batches(data):
@ -457,7 +459,7 @@ def get_filterd_batches(data):
return filterd_batch
def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, page_len=100):
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch")
@ -479,6 +481,8 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
& (stock_ledger_entry.batch_no.isnotnull())
)
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
.offset(start)
.limit(page_len)
)
query = query.select(
@ -493,16 +497,16 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
query = query.select(batch_table[field])
if txt:
txt_condition = batch_table.name.like(txt)
txt_condition = batch_table.name.like("%{0}%".format(txt))
for field in searchfields + ["name"]:
txt_condition |= batch_table[field].like(txt)
txt_condition |= batch_table[field].like("%{0}%".format(txt))
query = query.where(txt_condition)
return query
return query.run(as_list=1) or []
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0, page_len=100):
bundle = frappe.qb.DocType("Serial and Batch Entry")
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch")
@ -527,6 +531,8 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
)
.groupby(bundle.batch_no, bundle.warehouse)
.offset(start)
.limit(page_len)
)
bundle_query = bundle_query.select(
@ -541,13 +547,13 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
bundle_query = bundle_query.select(batch_table[field])
if txt:
txt_condition = batch_table.name.like(txt)
txt_condition = batch_table.name.like("%{0}%".format(txt))
for field in searchfields + ["name"]:
txt_condition |= batch_table[field].like(txt)
txt_condition |= batch_table[field].like("%{0}%".format(txt))
bundle_query = bundle_query.where(txt_condition)
return bundle_query
return bundle_query.run(as_list=1)
@frappe.whitelist()

View File

@ -141,7 +141,7 @@ def validate_returned_items(doc):
items_returned = True
if not items_returned:
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
frappe.throw(_("At least one item should be entered with negative quantity in return document"))
def validate_quantity(doc, args, ref, valid_items, already_returned_items):

View File

@ -53,6 +53,10 @@ status_map = {
"To Deliver",
"eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note",
],
[
"To Pay",
"eval:self.advance_payment_status == 'Requested' and self.docstatus == 1",
],
[
"Completed",
"eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1",
@ -63,15 +67,19 @@ status_map = {
],
"Purchase Order": [
["Draft", None],
[
"To Receive and Bill",
"eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
],
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
[
"To Receive",
"eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1",
],
[
"To Receive and Bill",
"eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
],
[
"To Pay",
"eval:self.advance_payment_status == 'Initiated' and self.docstatus == 1",
],
[
"Completed",
"eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1",

View File

@ -162,9 +162,7 @@ class StockController(AccountsController):
self.get_gl_dict(
{
"account": warehouse_account[sle.warehouse]["account"],
"against_type": "Account",
"against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -180,9 +178,7 @@ class StockController(AccountsController):
self.get_gl_dict(
{
"account": expense_account,
"against_type": "Account",
"against": warehouse_account[sle.warehouse]["account"],
"against_link": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(sle.stock_value_difference, precision),
@ -214,9 +210,7 @@ class StockController(AccountsController):
self.get_gl_dict(
{
"account": expense_account,
"against_type": "Account",
"against": warehouse_asset_account,
"against_link": warehouse_asset_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"),
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
@ -232,9 +226,7 @@ class StockController(AccountsController):
self.get_gl_dict(
{
"account": warehouse_asset_account,
"against_type": "Account",
"against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center,
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"credit": sle_rounding_diff,
@ -395,11 +387,7 @@ class StockController(AccountsController):
}
for row in self.get(table_name):
for field in [
"serial_and_batch_bundle",
"current_serial_and_batch_bundle",
"rejected_serial_and_batch_bundle",
]:
for field in QTY_FIELD.keys():
if row.get(field):
frappe.get_doc("Serial and Batch Bundle", row.get(field)).set_serial_and_batch_values(
self, row, qty_field=QTY_FIELD[field]
@ -840,7 +828,6 @@ class StockController(AccountsController):
credit,
remarks,
against_account,
against_type="Account",
debit_in_account_currency=None,
credit_in_account_currency=None,
account_currency=None,
@ -855,9 +842,7 @@ class StockController(AccountsController):
"cost_center": cost_center,
"debit": debit,
"credit": credit,
"against_type": against_type,
"against": against_account,
"against_link": against_account,
"remarks": remarks,
}

Some files were not shown because too many files have changed in this diff Show More