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

This commit is contained in:
Deepesh Garg 2024-01-26 09:35:38 +05:30
commit 6173b34b10
215 changed files with 64572 additions and 86360 deletions

View File

@ -20,6 +20,18 @@ jobs:
- name: Install and Run Pre-commit - name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0 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 - name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-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 - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 20
- name: Setup dependencies - name: Setup dependencies
run: | run: |
npm install @semantic-release/git @semantic-release/exec --no-save 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) account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
against_type = "Customer"
against, project = doc.customer, doc.project against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account credit_account, debit_account = item.income_account, item.deferred_revenue_account
else: else:
against_type = "Supplier"
against, project = doc.supplier, item.project against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account 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, doc,
credit_account, credit_account,
debit_account, debit_account,
against_type,
against, against,
amount, amount,
base_amount, base_amount,
@ -497,7 +494,6 @@ def make_gl_entries(
doc, doc,
credit_account, credit_account,
debit_account, debit_account,
against_type,
against, against,
amount, amount,
base_amount, base_amount,
@ -519,9 +515,7 @@ def make_gl_entries(
doc.get_gl_dict( doc.get_gl_dict(
{ {
"account": credit_account, "account": credit_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
"credit": base_amount, "credit": base_amount,
"credit_in_account_currency": amount, "credit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
@ -540,9 +534,7 @@ def make_gl_entries(
doc.get_gl_dict( doc.get_gl_dict(
{ {
"account": debit_account, "account": debit_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
"debit": base_amount, "debit": base_amount,
"debit_in_account_currency": amount, "debit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,7 +55,7 @@ class BankAccount(Document):
def validate_company(self): def validate_company(self):
if self.is_company_account and not self.company: 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): def validate_iban(self):
""" """

View File

@ -48,11 +48,11 @@ class BankGuarantee(Document):
def on_submit(self): def on_submit(self):
if not self.bank_guarantee_number: 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: 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: 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() @frappe.whitelist()

View File

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

View File

@ -3,6 +3,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import flt from frappe.utils import flt
@ -48,6 +49,24 @@ class BankTransaction(Document):
def validate(self): def validate(self):
self.validate_duplicate_references() 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): def set_status(self):
if self.docstatus == 2: if self.docstatus == 2:
@ -415,3 +434,21 @@ def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name) bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt) set_voucher_clearance(doctype, docname, None, bt)
return docname 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 # See license.txt
import json import json
import unittest
import frappe import frappe
from frappe import utils from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( 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") clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertFalse(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 # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self): def test_debit_credit_output(self):
bank_transaction = frappe.get_doc( bank_transaction = frappe.get_doc(

View File

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

View File

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

View File

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

View File

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

View File

@ -153,9 +153,7 @@ class InvoiceDiscounting(AccountsController):
"account": inv.debit_to, "account": inv.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": d.customer, "party": d.customer,
"against_type": "Account",
"against": self.accounts_receivable_credit, "against": self.accounts_receivable_credit,
"against_link": self.accounts_receivable_credit,
"credit": outstanding_in_company_currency, "credit": outstanding_in_company_currency,
"credit_in_account_currency": outstanding_in_company_currency "credit_in_account_currency": outstanding_in_company_currency
if inv.party_account_currency == company_currency if inv.party_account_currency == company_currency
@ -175,9 +173,7 @@ class InvoiceDiscounting(AccountsController):
"account": self.accounts_receivable_credit, "account": self.accounts_receivable_credit,
"party_type": "Customer", "party_type": "Customer",
"party": d.customer, "party": d.customer,
"against_type": "Account",
"against": inv.debit_to, "against": inv.debit_to,
"against_link": inv.debit_to,
"debit": outstanding_in_company_currency, "debit": outstanding_in_company_currency,
"debit_in_account_currency": outstanding_in_company_currency "debit_in_account_currency": outstanding_in_company_currency
if ar_credit_account_currency == 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", { frappe.ui.form.on("Journal Entry", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("bank_account", "account", "account"); 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) { 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); 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) { me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
@ -601,21 +591,6 @@ $.extend(erpnext.journal_entry, {
return { filters: filters }; 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() { reverse_journal_entry: function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry", 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): def update_advance_paid(self):
advance_paid = frappe._dict() 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"): for d in self.get("accounts"):
if d.is_advance: 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) advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items(): for voucher_type, order_list in advance_paid.items():
@ -304,7 +307,6 @@ class JournalEntry(AccountsController):
"account": tax_withholding_details.get("account_head"), "account": tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get("tax_amount"), rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
"against_account": parties[0], "against_account": parties[0],
"against_account_link": parties[0],
}, },
) )
@ -751,90 +753,27 @@ class JournalEntry(AccountsController):
) )
def set_against_account(self): def set_against_account(self):
accounts_debited, accounts_credited = [], []
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.reference_type == "Sales Invoice": if d.reference_type == "Sales Invoice":
against_type = "Customer" field = "customer"
else: else:
against_type = "Supplier" field = "supplier"
against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower()) d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
d.against_type = against_type
d.against_account_link = against_account
else: 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()
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"): for d in self.get("accounts"):
if flt(d.debit) > 0: if flt(d.debit) > 0:
self.accounts_debited.append(d) accounts_debited.append(d.party or d.account)
elif flt(d.credit) > 0: if flt(d.credit) > 0:
self.accounts_credited.append(d) accounts_credited.append(d.party or d.account)
if d.against_account_link: for d in self.get("accounts"):
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: if flt(d.debit) > 0:
against_account = self.accounts_credited[0] d.against_account = ", ".join(list(set(accounts_credited)))
elif flt(d.credit) > 0: if flt(d.credit) > 0:
against_account = self.accounts_debited[0] d.against_account = ", ".join(list(set(accounts_debited)))
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
def validate_debit_credit_amount(self): def validate_debit_credit_amount(self):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
@ -1031,23 +970,20 @@ class JournalEntry(AccountsController):
def build_gl_map(self): def build_gl_map(self):
gl_map = [] 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"): for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark] r = [d.user_remark, self.remark]
r = [x for x in r if x] r = [x for x in r if x]
remarks = "\n".join(r) remarks = "\n".join(r)
gl_dict = self.get_gl_dict( gl_map.append(
self.get_gl_dict(
{ {
"account": d.account, "account": d.account,
"party_type": d.party_type, "party_type": d.party_type,
"due_date": self.due_date, "due_date": self.due_date,
"party": d.party, "party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")), "debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")), "credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency, "account_currency": d.account_currency,
@ -1064,75 +1000,12 @@ class JournalEntry(AccountsController):
"cost_center": d.cost_center, "cost_center": d.cost_center,
"project": d.project, "project": d.project,
"finance_book": self.finance_book, "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, 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 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): def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries 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 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'] = { frappe.listview_settings["Journal Entry"] = {
add_fields: ["voucher_type", "posting_date", "total_debit", "company", "user_remark"], add_fields: [
get_indicator: function(doc) { "voucher_type",
if(doc.docstatus==0) { "posting_date",
return [__("Draft", "red", "docstatus,=,0")] "total_debit",
} else if(doc.docstatus==2) { "company",
return [__("Cancelled", "grey", "docstatus,=,2")] "user_remark",
} else { ],
return [__(doc.voucher_type), "blue", "voucher_type,=," + doc.voucher_type] 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", "col_break3",
"is_advance", "is_advance",
"user_remark", "user_remark",
"against_type", "against_account"
"against_account",
"against_account_link"
], ],
"fields": [ "fields": [
{ {
@ -261,13 +259,6 @@
"oldfieldtype": "Text", "oldfieldtype": "Text",
"print_hide": 1 "print_hide": 1
}, },
{
"fieldname": "against_account_link",
"fieldtype": "Dynamic Link",
"label": "Against Account",
"no_copy": 1,
"options": "against_type"
},
{ {
"collapsible": 1, "collapsible": 1,
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
@ -290,18 +281,12 @@
"hidden": 1, "hidden": 1,
"label": "Reference Detail No", "label": "Reference Detail No",
"no_copy": 1 "no_copy": 1
},
{
"fieldname": "against_type",
"fieldtype": "Link",
"label": "Against Type",
"options": "DocType"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-12-02 23:21:22.205409", "modified": "2023-12-03 23:21:22.205409",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "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>" errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"
), ),
indicator="red", indicator="red",
title=_("Error Occured"), title=_("Error Occurred"),
) )
return names return names

View File

@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
frappe.ui.form.on('Payment Entry', { frappe.ui.form.on('Payment Entry', {
onload: function(frm) { 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.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null); 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) { get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today(); const today = frappe.datetime.get_today();
const fields = [ let fields = [
{fieldtype:"Section Break", label: __("Posting Date")}, {fieldtype:"Section Break", label: __("Posting Date")},
{fieldtype:"Date", label: __("From Date"), {fieldtype:"Date", label: __("From Date"),
fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)}, 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}, fieldname:"outstanding_amt_greater_than", default: 0},
{fieldtype:"Column Break"}, {fieldtype:"Column Break"},
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {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() { if (frm.dimension_filters) {
return { let column_break_insertion_point = Math.ceil((frm.dimension_filters.length)/2);
"filters": {"company": frm.doc.company}
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:"Section Break"},
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
]; ]);
let btn_text = ""; let btn_text = "";
@ -933,7 +944,7 @@ frappe.ui.form.on('Payment Entry', {
if(frm.doc.payment_type == "Receive" if(frm.doc.payment_type == "Receive"
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions && 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)) { && 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; - frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
} else if (frm.doc.payment_type == "Pay" } else if (frm.doc.payment_type == "Pay"
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions && frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions

View File

@ -87,12 +87,14 @@
"status", "status",
"custom_remarks", "custom_remarks",
"remarks", "remarks",
"base_in_words",
"column_break_16", "column_break_16",
"letter_head", "letter_head",
"print_heading", "print_heading",
"bank", "bank",
"bank_account_no", "bank_account_no",
"payment_order", "payment_order",
"in_words",
"subscription_section", "subscription_section",
"auto_repeat", "auto_repeat",
"amended_from", "amended_from",
@ -747,6 +749,20 @@
"hidden": 1, "hidden": 1,
"label": "Book Advance Payments in Separate Party Account", "label": "Book Advance Payments in Separate Party Account",
"read_only": 1 "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, "index_web_pages_for_search": 1,

View File

@ -13,6 +13,7 @@ from pypika import Case
from pypika.functions import Coalesce, Sum from pypika.functions import Coalesce, Sum
import erpnext import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.bank_account.bank_account import ( from erpnext.accounts.doctype.bank_account.bank_account import (
get_bank_account_details, get_bank_account_details,
get_party_bank_account, get_party_bank_account,
@ -177,6 +178,7 @@ class PaymentEntry(AccountsController):
self.validate_paid_invoices() self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
self.set_status() self.set_status()
self.set_total_in_words()
def on_submit(self): def on_submit(self):
if self.difference_amount: if self.difference_amount:
@ -189,7 +191,7 @@ class PaymentEntry(AccountsController):
def set_liability_account(self): def set_liability_account(self):
# Auto setting liability account should only be done during 'draft' status # 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 return
if not frappe.db.get_value( if not frappe.db.get_value(
@ -785,6 +787,21 @@ class PaymentEntry(AccountsController):
self.db_set("status", self.status, update_modified=True) 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): def set_tax_withholding(self):
if self.party_type != "Supplier": if self.party_type != "Supplier":
return return
@ -925,7 +942,10 @@ class PaymentEntry(AccountsController):
def calculate_base_allocated_amount_for_reference(self, d) -> float: def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0 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. # 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 # This is so there are no Exchange Gain/Loss generated for such doctypes
@ -1144,9 +1164,7 @@ class PaymentEntry(AccountsController):
"account": self.party_account, "account": self.party_account,
"party_type": self.party_type, "party_type": self.party_type,
"party": self.party, "party": self.party,
"against_type": "Account",
"against": against_account, "against": against_account,
"against_link": against_account,
"account_currency": self.party_account_currency, "account_currency": self.party_account_currency,
"cost_center": self.cost_center, "cost_center": self.cost_center,
}, },
@ -1311,9 +1329,7 @@ class PaymentEntry(AccountsController):
{ {
"account": self.paid_from, "account": self.paid_from,
"account_currency": self.paid_from_account_currency, "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": 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_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1328,9 +1344,7 @@ class PaymentEntry(AccountsController):
{ {
"account": self.paid_to, "account": self.paid_to,
"account_currency": self.paid_to_account_currency, "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": 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_in_account_currency": self.received_amount,
"debit": self.base_received_amount, "debit": self.base_received_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1354,7 +1368,6 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to against = self.party or self.paid_to
against_type = self.party_type or "Account"
payment_account = self.get_party_account_for_taxes() payment_account = self.get_party_account_for_taxes()
tax_amount = d.tax_amount tax_amount = d.tax_amount
base_tax_amount = d.base_tax_amount base_tax_amount = d.base_tax_amount
@ -1363,9 +1376,7 @@ class PaymentEntry(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": d.account_head, "account": d.account_head,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
dr_or_cr: tax_amount, dr_or_cr: tax_amount,
dr_or_cr + "_in_account_currency": base_tax_amount dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency if account_currency == self.company_currency
@ -1390,9 +1401,7 @@ class PaymentEntry(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": payment_account, "account": payment_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
rev_dr_or_cr: tax_amount, rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency if account_currency == self.company_currency
@ -1417,9 +1426,7 @@ class PaymentEntry(AccountsController):
{ {
"account": d.account, "account": d.account,
"account_currency": account_currency, "account_currency": account_currency,
"against_type": self.party_type or "Account",
"against": self.party or self.paid_from, "against": self.party or self.paid_from,
"against_link": self.party or self.paid_from,
"debit_in_account_currency": d.amount, "debit_in_account_currency": d.amount,
"debit": d.amount, "debit": d.amount,
"cost_center": d.cost_center, "cost_center": d.cost_center,
@ -1436,8 +1443,11 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self): def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party: 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"): 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( frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid() ).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") condition += " and cost_center='%s'" % args.get("cost_center")
accounting_dimensions_filter.append(ple.cost_center == 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 = { date_fields_dict = {
"posting_date": ["from_posting_date", "to_posting_date"], "posting_date": ["from_posting_date", "to_posting_date"],
"due_date": ["from_due_date", "to_due_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: if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % 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: if party_account_currency == company_currency:
grand_total_field = "base_grand_total" grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_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.change_custom_button_type(__('Allocate'), null, 'default');
} }
this.frm.trigger("set_query_for_dimension_filters");
// check for any running reconciliation jobs // check for any running reconciliation jobs
if (this.frm.doc.receivable_payable_account) { if (this.frm.doc.receivable_payable_account) {
this.frm.call({ 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() { company() {
this.frm.set_value('party', ''); this.frm.set_value('party', '');

View File

@ -25,7 +25,9 @@
"invoice_limit", "invoice_limit",
"payment_limit", "payment_limit",
"bank_cash_account", "bank_cash_account",
"accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break",
"sec_break1", "sec_break1",
"invoice_name", "invoice_name",
"invoices", "invoices",
@ -39,6 +41,7 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1
@ -208,6 +211,18 @@
"fieldname": "payment_name", "fieldname": "payment_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Filter on Payment" "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, "hide_toolbar": 1,
@ -215,7 +230,7 @@
"is_virtual": 1, "is_virtual": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-11-17 17:33:55.701726", "modified": "2024-01-18 11:56:20.234667",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation", "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 from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
import erpnext import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import ( from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
is_any_doc_running, is_any_doc_running,
) )
@ -70,6 +71,7 @@ class PaymentReconciliation(Document):
self.common_filter_conditions = [] self.common_filter_conditions = []
self.accounting_dimension_filter_conditions = [] self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = [] self.ple_posting_date_filter = []
self.dimensions = get_dimensions()[0]
def load_from_db(self): def load_from_db(self):
# 'modified' attribute is required for `run_doc_method` to work properly. # 'modified' attribute is required for `run_doc_method` to work properly.
@ -172,6 +174,14 @@ class PaymentReconciliation(Document):
if self.payment_name: if self.payment_name:
condition.update({"name": 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( payment_entries = get_advance_payment_entries_for_regional(
self.party_type, self.party_type,
self.party, self.party,
@ -185,66 +195,67 @@ class PaymentReconciliation(Document):
return payment_entries return payment_entries
def get_jv_entries(self): 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: 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"): 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 = ( dr_or_cr = (
"credit_in_account_currency" "credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable" if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency" else "debit_in_account_currency"
) )
conditions.append(jea[dr_or_cr].gt(0))
bank_account_condition = ( if self.bank_cash_account:
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1" 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 = journal_query.run(as_dict=True)
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,
)
return list(journal_entries) return list(journal_entries)
@ -298,6 +309,7 @@ class PaymentReconciliation(Document):
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None, 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, max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True, get_payments=True,
accounting_dimensions=self.accounting_dimension_filter_conditions,
) )
for inv in return_outstanding: for inv in return_outstanding:
@ -447,8 +459,15 @@ class PaymentReconciliation(Document):
row = self.append("allocation", {}) row = self.append("allocation", {})
row.update(entry) 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): def get_allocated_entry(self, pay, inv, allocated_amount):
return frappe._dict( res = frappe._dict(
{ {
"reference_type": pay.get("reference_type"), "reference_type": pay.get("reference_type"),
"reference_name": pay.get("reference_name"), "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): def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
adjust_allocations_for_taxes(self) adjust_allocations_for_taxes(self)
dr_or_cr = ( dr_or_cr = (
@ -486,10 +508,10 @@ class PaymentReconciliation(Document):
reconciled_entry.append(payment_details) reconciled_entry.append(payment_details)
if entry_list: 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: 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() @frappe.whitelist()
def reconcile(self): def reconcile(self):
@ -518,7 +540,7 @@ class PaymentReconciliation(Document):
self.get_unreconciled_entries() self.get_unreconciled_entries()
def get_payment_details(self, row, dr_or_cr): def get_payment_details(self, row, dr_or_cr):
return frappe._dict( payment_details = frappe._dict(
{ {
"voucher_type": row.get("reference_type"), "voucher_type": row.get("reference_type"),
"voucher_no": row.get("reference_name"), "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): def check_mandatory_to_fetch(self):
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]: for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
if not self.get(fieldname): if not self.get(fieldname):
@ -648,6 +676,13 @@ class PaymentReconciliation(Document):
if not invoices_to_reconcile: if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table")) 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): def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear() self.common_filter_conditions.clear()
self.accounting_dimension_filter_conditions.clear() self.accounting_dimension_filter_conditions.clear()
@ -671,40 +706,30 @@ class PaymentReconciliation(Document):
if self.to_payment_date: if self.to_payment_date:
self.ple_posting_date_filter.append(ple.posting_date.lte(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): self.build_dimensions_filter_conditions()
condition = " and company = '{0}' ".format(self.company)
if self.get("cost_center") and get_payments: def get_journal_filter_conditions(self):
condition = " and cost_center = '{0}' ".format(self.cost_center) conditions = []
je = qb.DocType("Journal Entry")
jea = qb.DocType("Journal Entry Account")
conditions.append(je.company == self.company)
condition += ( if self.from_payment_date:
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) conditions.append(je.posting_date.gte(self.from_payment_date))
if self.from_payment_date
else "" if self.to_payment_date:
) conditions.append(je.posting_date.lte(self.to_payment_date))
condition += (
" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
if self.minimum_payment_amount: if self.minimum_payment_amount:
condition += ( conditions.append(je.total_debit.gte(self.minimum_payment_amount))
" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
if get_payments
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
)
if self.maximum_payment_amount: if self.maximum_payment_amount:
condition += ( conditions.append(je.total_debit.lte(self.maximum_payment_amount))
" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
if get_payments
else " and total_debit <= {0}".format(flt(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: for inv in dr_cr_notes:
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note" 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_mandatory = True
jv.flags.ignore_exchange_rate = True jv.flags.ignore_exchange_rate = True
jv.remark = None jv.remark = None
@ -787,9 +821,27 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
inv.against_voucher, inv.against_voucher,
None, None,
inv.cost_center, inv.cost_center,
dimensions_dict,
) )
@erpnext.allow_regional @erpnext.allow_regional
def adjust_allocations_for_taxes(doc): def adjust_allocations_for_taxes(doc):
pass 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", "difference_account",
"exchange_rate", "exchange_rate",
"currency", "currency",
"cost_center" "accounting_dimensions_section",
"cost_center",
"dimension_col_break"
], ],
"fields": [ "fields": [
{ {
@ -157,12 +159,21 @@
"fieldname": "gain_loss_posting_date", "fieldname": "gain_loss_posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Difference Posting 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, "is_virtual": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-11-17 17:33:38.612615", "modified": "2023-12-14 13:38:26.104150",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Allocation", "name": "Payment Reconciliation Allocation",

View File

@ -169,6 +169,13 @@ class PaymentRequest(Document):
elif self.payment_channel == "Phone": elif self.payment_channel == "Phone":
self.request_phone_payment() 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): def request_phone_payment(self):
controller = _get_payment_gateway_controller(self.payment_gateway) controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount() request_amount = self.get_request_amount()
@ -207,6 +214,14 @@ class PaymentRequest(Document):
self.check_if_payment_entry_exists() self.check_if_payment_entry_exists()
self.set_as_cancelled() 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): def make_invoice(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) 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": 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""" """Make payment request"""
args = frappe._dict(args) 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) ref_doc = frappe.get_doc(args.dt, args.dn)
gateway_account = get_gateway_details(args) or frappe._dict() gateway_account = get_gateway_details(args) or frappe._dict()

View File

@ -371,7 +371,7 @@ class POSInvoice(SalesInvoice):
if d.get("qty") > 0: if d.get("qty") > 0:
frappe.throw( 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)), ).format(d.idx, frappe.bold(d.item_code)),
title=_("Invalid Item"), title=_("Invalid Item"),
) )
@ -793,7 +793,7 @@ def make_merge_log(invoices):
invoices = json.loads(invoices) invoices = json.loads(invoices)
if len(invoices) == 0: 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 = frappe.new_doc("POS Invoice Merge Log")
merge_log.posting_date = getdate(nowdate()) merge_log.posting_date = getdate(nowdate())

View File

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

View File

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

View File

@ -193,7 +193,7 @@ class PricingRule(Document):
def validate_applicable_for_selling_or_buying(self): def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying: 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 [ if not self.selling and self.applicable_for in [
"Customer", "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) 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): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
from erpnext.accounts.doctype.pricing_rule.utils import ( from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules, get_applied_pricing_rules,
get_pricing_rule_items, 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): for d in get_applied_pricing_rules(pricing_rules):
if not d or not frappe.db.exists("Pricing Rule", d): if not d or not frappe.db.exists("Pricing Rule", d):
continue continue

View File

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

View File

@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload(); super.onload();
// Ignore linked advances // 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) { if(!this.frm.doc.__islocal) {
// show credit_to in print format // show credit_to in print format

View File

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

View File

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

View File

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

View File

@ -37,8 +37,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.onload(); super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 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", 'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle", "Bank Transaction",
'Serial and Batch Bundle'
]; ];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { 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.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values 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 import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.deferred_revenue import validate_service_stop_date
@ -1233,9 +1233,7 @@ class SalesInvoice(SellingController):
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"due_date": self.due_date, "due_date": self.due_date,
"against_type": "Account",
"against": self.against_income_account, "against": self.against_income_account,
"against_link": self.against_income_account,
"debit": base_grand_total, "debit": base_grand_total,
"debit_in_account_currency": base_grand_total "debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1264,9 +1262,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": tax.account_head, "account": tax.account_head,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")), "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(base_amount, tax.precision("base_tax_amount_after_discount_amount")) flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
@ -1287,9 +1283,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.unrealized_profit_loss_account, "account": self.unrealized_profit_loss_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": flt(self.total_taxes_and_charges), "debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges), "debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1357,9 +1351,7 @@ class SalesInvoice(SellingController):
add_asset_activity(asset.name, _("Asset sold")) add_asset_activity(asset.name, _("Asset sold"))
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against_type"] = "Customer"
gle["against"] = self.customer gle["against"] = self.customer
gle["against_link"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))
self.set_asset_status(asset) self.set_asset_status(asset)
@ -1380,9 +1372,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": income_account, "account": income_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")), "credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount")) flt(base_amount, item.precision("base_net_amount"))
@ -1436,9 +1426,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account", "against": "Expense account - "
"against": self.loyalty_redemption_account, + cstr(self.loyalty_redemption_account)
"against_link": self.loyalty_redemption_account, + " for the Loyalty Program",
"credit": self.loyalty_amount, "credit": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
@ -1452,9 +1442,7 @@ class SalesInvoice(SellingController):
{ {
"account": self.loyalty_redemption_account, "account": self.loyalty_redemption_account,
"cost_center": self.cost_center or self.loyalty_redemption_cost_center, "cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": self.loyalty_amount, "debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer", "remark": "Loyalty Points redeemed by the customer",
}, },
@ -1481,9 +1469,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": payment_mode.account, "against": payment_mode.account,
"against_link": payment_mode.account,
"credit": payment_mode.base_amount, "credit": payment_mode.base_amount,
"credit_in_account_currency": payment_mode.base_amount "credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1504,9 +1490,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": payment_mode.account, "account": payment_mode.account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": payment_mode.base_amount, "debit": payment_mode.base_amount,
"debit_in_account_currency": payment_mode.base_amount "debit_in_account_currency": payment_mode.base_amount
if payment_mode_account_currency == self.company_currency if payment_mode_account_currency == self.company_currency
@ -1530,9 +1514,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": self.account_for_change_amount, "against": self.account_for_change_amount,
"against_link": self.account_for_change_amount,
"debit": flt(self.base_change_amount), "debit": flt(self.base_change_amount),
"debit_in_account_currency": flt(self.base_change_amount) "debit_in_account_currency": flt(self.base_change_amount)
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1553,9 +1535,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.account_for_change_amount, "account": self.account_for_change_amount,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": self.base_change_amount, "credit": self.base_change_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
}, },
@ -1581,9 +1561,7 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": self.write_off_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": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount")) flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1603,9 +1581,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.write_off_account, "account": self.write_off_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"debit_in_account_currency": ( "debit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount")) flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1633,9 +1609,7 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": round_off_account, "account": round_off_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit_in_account_currency": flt( "credit_in_account_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment") self.rounding_adjustment, self.precision("rounding_adjustment")
), ),

View File

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

View File

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

View File

@ -16,6 +16,7 @@ from frappe.utils.data import (
date_diff, date_diff,
flt, flt,
get_last_day, get_last_day,
get_link_to_form,
getdate, getdate,
nowdate, nowdate,
) )
@ -77,9 +78,7 @@ class Subscription(Document):
purchase_tax_template: DF.Link | None purchase_tax_template: DF.Link | None
sales_tax_template: DF.Link | None sales_tax_template: DF.Link | None
start_date: DF.Date | None start_date: DF.Date | None
status: DF.Literal[ status: DF.Literal["", "Trialing", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"]
"", "Trialling", "Active", "Past Due Date", "Cancelled", "Unpaid", "Completed"
]
submit_invoice: DF.Check submit_invoice: DF.Check
trial_period_end: DF.Date | None trial_period_end: DF.Date | None
trial_period_start: DF.Date | None trial_period_start: DF.Date | None
@ -232,7 +231,7 @@ class Subscription(Document):
Sets the status of the `Subscription` Sets the status of the `Subscription`
""" """
if self.is_trialling(): if self.is_trialling():
self.status = "Trialling" self.status = "Trialing"
elif ( elif (
self.status == "Active" and self.end_date and getdate(posting_date) > getdate(self.end_date) 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(): if self.is_new():
self.set_subscription_status() 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: def validate_trial_period(self) -> None:
""" """
Runs sanity checks on trial period dates for the `Subscription` 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): ) and self.can_generate_new_invoice(posting_date):
self.generate_invoice(posting_date=posting_date) self.generate_invoice(posting_date=posting_date)
self.update_subscription_period(add_days(self.current_invoice_end, 1)) 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 ( if self.cancel_at_period_end and (
getdate(posting_date) >= getdate(self.current_invoice_end) getdate(posting_date) >= getdate(self.current_invoice_end)

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
"doctype": "Form Tour", "doctype": "Form Tour",
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"modified": "2021-06-29 17:00:26.145996", "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",
@ -82,7 +82,7 @@
"label": "Accounts Frozen Till Date", "label": "Accounts Frozen Till Date",
"parent_field": "", "parent_field": "",
"position": "Right", "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.", "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", "project",
"finance_book", "finance_book",
"voucher_no", "voucher_no",
"against_link",
] ]
if dimensions: 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": "Asset Received But Not Billed", "label": __("Asset Received But Not Billed") },
{ "value": "Bank", "label": __("Bank") }, { "value": "Bank", "label": __("Bank") },
{ "value": "Cash", "label": __("Cash") }, { "value": "Cash", "label": __("Cash") },
{ "value": "Chargeble", "label": __("Chargeble") }, { "value": "Chargeable", "label": __("Chargeable") },
{ "value": "Capital Work in Progress", "label": __("Capital Work in Progress") }, { "value": "Capital Work in Progress", "label": __("Capital Work in Progress") },
{ "value": "Cost of Goods Sold", "label": __("Cost of Goods Sold") }, { "value": "Cost of Goods Sold", "label": __("Cost of Goods Sold") },
{ "value": "Depreciation", "label": __("Depreciation") }, { "value": "Depreciation", "label": __("Depreciation") },

View File

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

View File

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

View File

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

View File

@ -3,49 +3,135 @@
import frappe import frappe
from frappe.tests.utils import FrappeTestCase 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 from erpnext.accounts.report.balance_sheet.balance_sheet import execute
COMPANY = "_Test Company 6"
COMPANY_SHORT_NAME = "_TC6"
test_dependencies = ["Company"]
class TestBalanceSheet(FrappeTestCase): class TestBalanceSheet(FrappeTestCase):
def test_balance_sheet(self): def test_balance_sheet(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice frappe.db.sql(f"delete from `tabJournal Entry` where company='{COMPANY}'")
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import ( frappe.db.sql(f"delete from `tabGL Entry` where company='{COMPANY}'")
create_sales_invoice,
make_sales_invoice,
)
from erpnext.accounts.utils import get_fiscal_year
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'") create_account("VAT Liabilities", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 6'") create_account("Advance VAT Paid", f"Duties and Taxes - {COMPANY_SHORT_NAME}", COMPANY)
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'") create_account("My Bank", f"Bank Accounts - {COMPANY_SHORT_NAME}", COMPANY)
pi = make_purchase_invoice( # 1000 equity paid to bank account
company="_Test Company 6", make_journal_entry(
warehouse="Finished Goods - _TC6", [
expense_account="Cost of Goods Sold - _TC6", dict(
cost_center="Main - _TC6", account_name="My Bank",
qty=10, debit_in_account_currency=1000,
rate=100, 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", # 110 income paid to bank account (100 revenue + 10 VAT)
debit_to="Debtors - _TC6", make_journal_entry(
income_account="Sales - _TC6", [
cost_center="Main - _TC6", dict(
qty=5, account_name="My Bank",
rate=110, 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( filters = frappe._dict(
company="_Test Company 6", company=COMPANY,
period_start_date=today(), period_start_date=today(),
period_end_date=today(), period_end_date=today(),
periodicity="Yearly", periodicity="Yearly",
) )
result = execute(filters)[1] results = execute(filters)
for account_dict in result: name_and_total = {
if account_dict.get("account") == "Current Liabilities - _TC6": account_dict["account_name"]: account_dict["total"]
self.assertEqual(account_dict.total, 1000) for account_dict in results[1]
if account_dict.get("account") == "Current Assets - _TC6": if "total" in account_dict and "account_name" in account_dict
self.assertEqual(account_dict.total, 550) }
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, options: budget_against_options,
default: "Cost Center", default: "Cost Center",
reqd: 1, reqd: 1,
get_data: function() {
console.log(this.options);
return ["Emacs", "Rocks"];
},
on_change: function() { on_change: function() {
frappe.query_report.set_filter_value("budget_against_filter", []); frappe.query_report.set_filter_value("budget_against_filter", []);
frappe.query_report.refresh(); frappe.query_report.refresh();

View File

@ -376,6 +376,10 @@ class PartyLedgerSummaryReport(object):
if not income_or_expense_accounts: if not income_or_expense_accounts:
# prevent empty 'in' condition # prevent empty 'in' condition
income_or_expense_accounts.append("") 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 = ( accounts_query = (
qb.from_(gl) qb.from_(gl)

View File

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

View File

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

View File

@ -59,7 +59,21 @@ frappe.query_reports["Item-wise Sales Register"] = {
"fieldname": "group_by", "fieldname": "group_by",
"fieldtype": "Select", "fieldtype": "Select",
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"] "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) { "formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data); 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, "company": d.company,
"sales_order": d.sales_order, "sales_order": d.sales_order,
"delivery_note": d.delivery_note, "delivery_note": d.delivery_note,
"income_account": d.unrealized_profit_loss_account "income_account": get_income_account(d),
if d.is_internal_customer == 1
else d.income_account,
"cost_center": d.cost_center, "cost_center": d.cost_center,
"stock_qty": d.stock_qty, "stock_qty": d.stock_qty,
"stock_uom": d.stock_uom, "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 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): def get_columns(additional_table_columns, filters):
columns = [] columns = []
@ -358,6 +365,13 @@ def get_conditions(filters, additional_conditions=None):
if filters.get("item_group"): if filters.get("item_group"):
conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s""" 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"): if not filters.get("group_by"):
conditions += ( conditions += (
"ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc" "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, `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`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, `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`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, `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, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,

View File

@ -46,12 +46,10 @@ def get_result(
out = [] out = []
for name, details in gle_map.items(): 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: 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 party = entry.party or entry.against
posting_date = entry.posting_date posting_date = entry.posting_date
voucher_type = entry.voucher_type voucher_type = entry.voucher_type
@ -61,13 +59,20 @@ def get_result(
if party_list: if party_list:
party = party_list[0] party = party_list[0]
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: if not tax_withholding_category:
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category") tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category) rate = tax_rate_map.get(tax_withholding_category)
if entry.account in tds_accounts:
tax_amount += entry.credit - entry.debit
if net_total_map.get(name): if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate: if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount # back calcalute total amount from rate and tax_amount
@ -282,11 +287,20 @@ def get_tds_docs(filters):
journal_entry_party_map = frappe._dict() journal_entry_party_map = frappe._dict()
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name") bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
tds_accounts = frappe.get_all( _tds_accounts = frappe.get_all(
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account" "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: for d in tds_docs:
if d.voucher_type == "Purchase Invoice": if d.voucher_type == "Purchase Invoice":

View File

@ -23,6 +23,10 @@ class TestUtils(unittest.TestCase):
super(TestUtils, cls).setUpClass() super(TestUtils, cls).setUpClass()
make_test_objects("Address", ADDRESS_RECORDS) make_test_objects("Address", ADDRESS_RECORDS)
@classmethod
def tearDownClass(cls):
frappe.db.rollback()
def test_get_party_shipping_address(self): def test_get_party_shipping_address(self):
address = get_party_shipping_address("Customer", "_Test Customer 1") address = get_party_shipping_address("Customer", "_Test Customer 1")
self.assertEqual(address, "_Test Billing Address 2 Title-Billing") 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(len(payment_entry.references), 1)
self.assertEqual(payment_entry.difference_amount, 0) 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 = [ 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),)) cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account: if account:
if not (frappe.flags.ignore_account_permission or ignore_account_permission): if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read") acc.check_permission("read")
@ -286,18 +285,22 @@ def get_balance_on(
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False))) cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party) or account_type: if account or (party_type and party) or account_type:
precision = get_currency_precision()
if in_account_currency: 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: else:
select_field = "sum(debit) - sum(credit)" select_field = "sum(round(debit, %s)) - sum(round(credit, %s))"
bal = frappe.db.sql( bal = frappe.db.sql(
""" """
SELECT {0} SELECT {0}
FROM `tabGL Entry` gle FROM `tabGL Entry` gle
WHERE {1}""".format( WHERE {1}""".format(
select_field, " and ".join(cond) select_field, " and ".join(cond)
) ),
(precision, precision),
)[0][0] )[0][0]
# if bal is None, return 0 # if bal is None, return 0
return flt(bal) return flt(bal)
@ -453,7 +456,19 @@ def add_cc(args=None):
return cc.name 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 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) check_if_advance_entry_modified(entry)
validate_allocated_amount(entry) validate_allocated_amount(entry)
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
# update ref in advance entry # update ref in advance entry
if voucher_type == "Journal Entry": if voucher_type == "Journal Entry":
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False) 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 # amount and account in args
# referenced_row is used to deduplicate gain/loss journal # referenced_row is used to deduplicate gain/loss journal
entry.update({"referenced_row": referenced_row}) entry.update({"referenced_row": referenced_row})
doc.make_exchange_gain_loss_journal([entry]) doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
else: else:
referenced_row = update_reference_in_payment_entry( 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) 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] jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they might be getting unlinked # 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() 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: 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.set("reference_name", d["against_voucher"])
new_row.against_account = cstr(jv_detail.against_account) 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.is_advance = cstr(jv_detail.is_advance)
new_row.docstatus = 1 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( 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_details = {
"reference_doctype": d.against_voucher_type, "reference_doctype": d.against_voucher_type,
@ -668,13 +691,17 @@ def update_reference_in_payment_entry(
else payment_entry.get_exchange_rate(), else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.difference_amount, "exchange_gain_loss": d.difference_amount,
"account": d.account, "account": d.account,
"dimensions": d.dimensions,
} }
if d.voucher_detail_no: if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0] existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they are getting unlinked # 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( frappe.get_doc(
existing_row.reference_doctype, existing_row.reference_name existing_row.reference_doctype, existing_row.reference_name
).set_total_advance_paid() ).set_total_advance_paid()
@ -700,8 +727,9 @@ def update_reference_in_payment_entry(
if not skip_ref_details_update_for_pe: if not skip_ref_details_update_for_pe:
payment_entry.set_missing_ref_details() payment_entry.set_missing_ref_details()
payment_entry.set_amounts() payment_entry.set_amounts()
payment_entry.make_exchange_gain_loss_journal( 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: 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): def parse_naming_series_variable(doc, variable):
if variable == "FY": if variable == "FY":
if doc: 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") company = doc.get("company")
else: else:
date = getdate() date = getdate()
company = None company = None
return get_fiscal_year(date=date, company=company).name return get_fiscal_year(date=date, company=company)[0]
@frappe.whitelist() @frappe.whitelist()
@ -2043,6 +2071,7 @@ def create_gain_loss_journal(
ref2_dn, ref2_dn,
ref2_detail_no, ref2_detail_no,
cost_center, cost_center,
dimensions,
) -> str: ) -> str:
journal_entry = frappe.new_doc("Journal Entry") journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.voucher_type = "Exchange Gain Or Loss"
@ -2076,7 +2105,8 @@ def create_gain_loss_journal(
dr_or_cr + "_in_account_currency": 0, dr_or_cr + "_in_account_currency": 0,
} }
) )
if dimensions:
journal_account.update(dimensions)
journal_entry.append("accounts", journal_account) journal_entry.append("accounts", journal_account)
journal_account = frappe._dict( journal_account = frappe._dict(
@ -2092,7 +2122,8 @@ def create_gain_loss_journal(
reverse_dr_or_cr: abs(exc_gain_loss), reverse_dr_or_cr: abs(exc_gain_loss),
} }
) )
if dimensions:
journal_account.update(dimensions)
journal_entry.append("accounts", journal_account) journal_entry.append("accounts", journal_account)
journal_entry.save() journal_entry.save()

View File

@ -5,7 +5,7 @@
"label": "Profit and Loss" "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", "creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -14,562 +14,10 @@
"hide_custom": 0, "hide_custom": 0,
"icon": "accounting", "icon": "accounting",
"idx": 0, "idx": 0,
"indicator_color": "",
"is_hidden": 0, "is_hidden": 0,
"label": "Accounting", "label": "Accounting",
"links": [ "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, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -611,102 +59,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "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, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@ -918,8 +270,83 @@
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 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_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, "onboard": 0,
"type": "Card Break" "type": "Card Break"
}, },
@ -1003,60 +430,8 @@
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Profitability", "label": "Accounting Masters",
"link_count": 0, "link_count": 8,
"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,
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Card Break" "type": "Card Break"
@ -1065,9 +440,31 @@
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Bank", "label": "Company",
"link_count": 0, "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", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -1076,9 +473,9 @@
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Bank Account", "label": "Fiscal Year",
"link_count": 0, "link_count": 0,
"link_to": "Bank Account", "link_to": "Fiscal Year",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -1087,9 +484,9 @@
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Bank Clearance", "label": "Accounting Dimension",
"link_count": 0, "link_count": 0,
"link_to": "Bank Clearance", "link_to": "Accounting Dimension",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
@ -1098,36 +495,98 @@
"dependencies": "", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
"label": "Bank Reconciliation Tool", "label": "Finance Book",
"link_count": 0, "link_count": 0,
"link_to": "Bank Reconciliation Tool", "link_to": "Finance Book",
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"dependencies": "GL Entry", "dependencies": "",
"hidden": 0, "hidden": 0,
"is_query_report": 1, "is_query_report": 0,
"label": "Bank Reconciliation Statement", "label": "Accounting Period",
"link_count": 0, "link_count": 0,
"link_to": "Bank Reconciliation Statement", "link_to": "Accounting Period",
"link_type": "Report", "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, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 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_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", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
} }
], ],
"modified": "2024-01-02 15:21:09.895531", "modified": "2024-01-18 22:15:40.941711",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "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,8 +571,8 @@ frappe.ui.form.on('Asset', {
indicator: 'red' indicator: 'red'
}); });
} }
var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset'); frappe.db.get_value('Item', item.item_code, 'is_grouped_asset', (r) => {
var asset_quantity = is_grouped_asset ? item.qty : 1; var asset_quantity = r.is_grouped_asset ? item.qty : 1;
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount')); var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
frm.set_value('gross_purchase_amount', purchase_amount); frm.set_value('gross_purchase_amount', purchase_amount);
@ -580,7 +580,7 @@ frappe.ui.form.on('Asset', {
frm.set_value('asset_quantity', asset_quantity); frm.set_value('asset_quantity', asset_quantity);
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center); frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
if(item.asset_location) { frm.set_value('location', item.asset_location); } if(item.asset_location) { frm.set_value('location', item.asset_location); }
});
}, },
set_depreciation_rate: function(frm, row) { set_depreciation_rate: function(frm, row) {

View File

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

View File

@ -162,6 +162,7 @@ class Asset(AccountsController):
def on_cancel(self): def on_cancel(self):
self.validate_cancellation() self.validate_cancellation()
self.cancel_movement_entries() self.cancel_movement_entries()
self.cancel_capitalization()
self.delete_depreciation_entries() self.delete_depreciation_entries()
cancel_asset_depr_schedules(self) cancel_asset_depr_schedules(self)
self.set_status() self.set_status()
@ -517,6 +518,13 @@ class Asset(AccountsController):
movement = frappe.get_doc("Asset Movement", movement.get("name")) movement = frappe.get_doc("Asset Movement", movement.get("name"))
movement.cancel() 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): def delete_depreciation_entries(self):
if self.calculate_depreciation: if self.calculate_depreciation:
for row in self.get("finance_books"): for row in self.get("finance_books"):
@ -697,9 +705,7 @@ class Asset(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": cwip_account, "account": cwip_account,
"against_type": "Account",
"against": fixed_asset_account, "against": fixed_asset_account,
"against_link": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount, "credit": self.purchase_receipt_amount,
@ -714,9 +720,7 @@ class Asset(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": fixed_asset_account, "account": fixed_asset_account,
"against_type": "Account",
"against": cwip_account, "against": cwip_account,
"against_link": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount, "debit": self.purchase_receipt_amount,
@ -1004,7 +1008,7 @@ def make_asset_movement(assets, purpose=None):
assets = json.loads(assets) assets = json.loads(assets)
if len(assets) == 0: 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 = frappe.new_doc("Asset Movement")
asset_movement.quantity = len(assets) asset_movement.quantity = len(assets)
@ -1031,6 +1035,8 @@ def is_cwip_accounting_enabled(asset_category):
@frappe.whitelist() @frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None): def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name) 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) 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): def reverse_depreciation_entry_made_after_disposal(asset, date):
for row in asset.get("finance_books"): for row in asset.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book) 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")): for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if schedule.schedule_date == date: 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")), flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0, 0.0,
), ),
( ("_Test Fixed Asset - _TC", 0.0, 100000.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 Gain/Loss on Asset Disposal - _TC", "_Test Gain/Loss on Asset Disposal - _TC",
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), 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], ["2030-12-31", 28630.14, 28630.14],
["2031-12-31", 35684.93, 64315.07], ["2031-12-31", 35684.93, 64315.07],
["2032-12-31", 17842.46, 82157.53], ["2032-12-31", 17842.46, 82157.53],
["2033-06-06", 5342.47, 87500.0], ["2033-06-06", 5342.46, 87499.99],
] ]
schedules = [ schedules = [
@ -1012,7 +1003,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
depreciation_amount = get_depreciation_amount( 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) self.assertEqual(depreciation_amount, 30000)

View File

@ -21,10 +21,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
this.show_stock_ledger(); 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") { // 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.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
this.get_target_asset_details(); // this.get_target_asset_details();
} // }
} }
setup_queries() { setup_queries() {
@ -143,13 +143,20 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
}, },
callback: function (r) { callback: function (r) {
if (!r.exc && r.message) { if (!r.exc && r.message) {
if(r.message[0] && r.message[0].length) {
me.frm.clear_table("stock_items"); me.frm.clear_table("stock_items");
for (let item of r.message[0]) {
for (let item of r.message) {
me.frm.add_child("stock_items", item); me.frm.add_child("stock_items", item);
} }
refresh_field("stock_items"); 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");
}
me.calculate_totals(); me.calculate_totals();
} }

View File

@ -136,11 +136,20 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry", "Stock Ledger Entry",
"Repost Item Valuation", "Repost Item Valuation",
"Serial and Batch Bundle", "Serial and Batch Bundle",
"Asset",
) )
self.cancel_target_asset()
self.update_stock_ledger() self.update_stock_ledger()
self.make_gl_entries() self.make_gl_entries()
self.restore_consumed_asset_items() 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): def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code 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( self.get_gl_dict(
{ {
"account": account, "account": account,
"against_type": "Account",
"against": target_account, "against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -528,9 +535,7 @@ class AssetCapitalization(StockController):
self.set_consumed_asset_status(asset) self.set_consumed_asset_status(asset)
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against_type"] = "Account"
gle["against"] = target_account gle["against"] = target_account
gle["against_link"] = target_account
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))
target_against.add(gle["account"]) target_against.add(gle["account"])
@ -546,9 +551,7 @@ class AssetCapitalization(StockController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item_row.expense_account, "account": item_row.expense_account,
"against_type": "Account",
"against": target_account, "against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -559,18 +562,15 @@ class AssetCapitalization(StockController):
) )
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision): 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: if self.target_is_fixed_asset:
# Capitalization # Capitalization
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.target_fixed_asset_account, "account": self.target_fixed_asset_account,
"against_type": "Account", "against": ", ".join(target_against),
"against": target_account,
"against_link": target_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": flt(self.total_value, precision) / len(target_against), "debit": flt(self.total_value, precision),
"cost_center": self.get("cost_center"), "cost_center": self.get("cost_center"),
}, },
item=self, item=self,
@ -587,13 +587,11 @@ class AssetCapitalization(StockController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": account, "account": account,
"against_type": "Account", "against": ", ".join(target_against),
"against": target_account,
"against_link": target_account,
"cost_center": self.cost_center, "cost_center": self.cost_center,
"project": self.get("project"), "project": self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference / len(target_against), "debit": stock_value_difference,
}, },
self.warehouse_account[sle.warehouse]["account_currency"], self.warehouse_account[sle.warehouse]["account_currency"],
item=self, item=self,
@ -892,7 +890,6 @@ def get_consumed_asset_details(args):
out.cost_center = get_default_cost_center( out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults args, item_defaults, item_group_defaults, brand_defaults
) )
return out return out
@ -940,10 +937,27 @@ def get_items_tagged_to_wip_composite_asset(asset):
"qty", "qty",
"valuation_rate", "valuation_rate",
"amount", "amount",
"is_fixed_asset",
"parent",
] ]
pr_items = frappe.get_all( 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 # Test General Ledger Entries
expected_gle = { expected_gle = {
"_Test Fixed Asset - TCP1": 2999.99, "_Test Fixed Asset - TCP1": 3000,
"Expenses Included In Asset Valuation - TCP1": -1000, "Expenses Included In Asset Valuation - TCP1": -1000,
"_Test Warehouse - TCP1": -2000, "_Test Warehouse - TCP1": -2000,
"Round Off - TCP1": 0.01,
} }
actual_gle = get_actual_gle_dict(asset_capitalization.name) actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle) self.assertEqual(actual_gle, expected_gle)
# Test Stock Ledger Entries # Test Stock Ledger Entries
@ -189,10 +189,9 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries # Test General Ledger Entries
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account") default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
expected_gle = { expected_gle = {
"_Test Fixed Asset - _TC": 2999.99, "_Test Fixed Asset - _TC": 3000,
"Expenses Included In Asset Valuation - _TC": -1000, "Expenses Included In Asset Valuation - _TC": -1000,
default_expense_account: -2000, default_expense_account: -2000,
"Round Off - _TC": 0.01,
} }
actual_gle = get_actual_gle_dict(asset_capitalization.name) actual_gle = get_actual_gle_dict(asset_capitalization.name)
@ -377,10 +376,9 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries # Test General Ledger Entries
expected_gle = { expected_gle = {
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
"_Test Accumulated Depreciations - TCP1": accumulated_depreciation, "_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
"_Test Fixed Asset - TCP1": -consumed_asset_purchase_value, "_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) actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle) self.assertEqual(actual_gle, expected_gle)

View File

@ -7,6 +7,7 @@ from frappe.model.document import Document
from frappe.utils import ( from frappe.utils import (
add_days, add_days,
add_months, add_months,
add_years,
cint, cint,
date_diff, date_diff,
flt, flt,
@ -18,6 +19,7 @@ from frappe.utils import (
) )
import erpnext import erpnext
from erpnext.accounts.utils import get_fiscal_year
class AssetDepreciationSchedule(Document): class AssetDepreciationSchedule(Document):
@ -283,12 +285,20 @@ class AssetDepreciationSchedule(Document):
depreciation_amount = 0 depreciation_amount = 0
number_of_pending_depreciations = final_number_of_depreciations - start 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): for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance) # If depreciation is already completed (for double declining balance)
if skip_row: if skip_row:
continue 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: if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
else: else:
@ -298,6 +308,7 @@ class AssetDepreciationSchedule(Document):
self, self,
asset_doc, asset_doc,
value_after_depreciation, value_after_depreciation,
yearly_opening_wdv,
row, row,
n, n,
prev_depreciation_amount, prev_depreciation_amount,
@ -401,8 +412,9 @@ class AssetDepreciationSchedule(Document):
if not depreciation_amount: if not depreciation_amount:
continue continue
value_after_depreciation -= flt( value_after_depreciation = flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount") 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 # 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_depr_schedule,
asset, asset,
depreciable_value, depreciable_value,
yearly_opening_wdv,
fb_row, fb_row,
schedule_idx=0, schedule_idx=0,
prev_depreciation_amount=0, prev_depreciation_amount=0,
@ -597,6 +610,7 @@ def get_depreciation_amount(
asset, asset,
fb_row, fb_row,
depreciable_value, depreciable_value,
yearly_opening_wdv,
schedule_idx, schedule_idx,
prev_depreciation_amount, prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata, has_wdv_or_dd_non_yearly_pro_rata,
@ -744,6 +758,7 @@ def get_wdv_or_dd_depr_amount(
asset, asset,
fb_row, fb_row,
depreciable_value, depreciable_value,
yearly_opening_wdv,
schedule_idx, schedule_idx,
prev_depreciation_amount, prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata, 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()): if getdate(task.next_due_date) < getdate(nowdate()):
task.maintenance_status = "Overdue" task.maintenance_status = "Overdue"
if not task.assign_to and self.docstatus == 0: 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): def on_update(self):
for task in self.get("asset_maintenance_tasks"): for task in self.get("asset_maintenance_tasks"):

View File

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

View File

@ -2,14 +2,14 @@
"action": "Show Form Tour", "action": "Show Form Tour",
"action_label": "Let's review existing Asset Category", "action_label": "Let's review existing Asset Category",
"creation": "2021-08-13 14:26:18.656303", "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, "docstatus": 0,
"doctype": "Onboarding Step", "doctype": "Onboarding Step",
"idx": 0, "idx": 0,
"is_complete": 0, "is_complete": 0,
"is_single": 0, "is_single": 0,
"is_skipped": 0, "is_skipped": 0,
"modified": "2021-11-23 10:02:03.242127", "modified": "2024-01-24 02:20:26.145996",
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Asset Category", "name": "Asset Category",
"owner": "Administrator", "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()], "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()], "values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
}, },
], ],

View File

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

View File

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

View File

@ -134,6 +134,7 @@
"more_info_tab", "more_info_tab",
"tracking_section", "tracking_section",
"status", "status",
"advance_payment_status",
"column_break_75", "column_break_75",
"per_billed", "per_billed",
"per_received", "per_received",
@ -1269,13 +1270,25 @@
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
"label": "Connections", "label": "Connections",
"show_dashboard": 1 "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", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-10-01 20:58:07.851037", "modified": "2023-10-10 13:37:40.158761",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

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

View File

@ -1,6 +1,6 @@
frappe.listview_settings['Purchase Order'] = { frappe.listview_settings['Purchase Order'] = {
add_fields: ["base_grand_total", "company", "currency", "supplier", 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) { get_indicator: function (doc) {
if (doc.status === "Closed") { if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"]; return [__("Closed"), "green", "status,=,Closed"];
@ -8,6 +8,8 @@ frappe.listview_settings['Purchase Order'] = {
return [__("On Hold"), "orange", "status,=,On Hold"]; return [__("On Hold"), "orange", "status,=,On Hold"];
} else if (doc.status === "Delivered") { } else if (doc.status === "Delivered") {
return [__("Delivered"), "green", "status,=,Closed"]; 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") { } else if (flt(doc.per_received, 2) < 100 && doc.status !== "Closed") {
if (flt(doc.per_billed, 2) < 100) { if (flt(doc.per_billed, 2) < 100) {
return [__("To Receive and Bill"), "orange", 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})) 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(): def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@ -54,7 +54,7 @@ frappe.query_reports["Purchase Order Analysis"] = {
"fieldtype": "MultiSelectList", "fieldtype": "MultiSelectList",
"width": "80", "width": "80",
get_data: function(txt) { 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 = [] let options = []
for (let option of status){ for (let option of status){
options.push({ options.push({

View File

@ -7,6 +7,7 @@ import json
import frappe import frappe
from frappe import _, bold, qb, throw from frappe import _, bold, qb, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied 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.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum from frappe.query_builder.functions import Abs, Sum
from frappe.utils import ( from frappe.utils import (
@ -21,12 +22,14 @@ from frappe.utils import (
get_link_to_form, get_link_to_form,
getdate, getdate,
nowdate, nowdate,
parse_json,
today, today,
) )
import erpnext import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
get_dimensions,
) )
from erpnext.accounts.doctype.pricing_rule.utils import ( from erpnext.accounts.doctype.pricing_rule.utils import (
apply_pricing_rule_for_free_items, 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"))) 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): def set_other_charges(self):
self.set("taxes", []) self.set("taxes", [])
self.set_taxes() self.set_taxes()
@ -930,7 +964,7 @@ class AccountsController(TransactionBase):
# Update details in transaction currency # Update details in transaction currency
gl_dict.update( 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), "transaction_exchange_rate": self.get("conversion_rate", 1),
"debit_in_transaction_currency": self.get_value_in_transaction_currency( "debit_in_transaction_currency": self.get_value_in_transaction_currency(
account_currency, args, "debit" account_currency, args, "debit"
@ -969,10 +1003,10 @@ class AccountsController(TransactionBase):
return self.doctype return self.doctype
def get_value_in_transaction_currency(self, account_currency, args, field): 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") return args.get(field + "_in_account_currency")
else: 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): def validate_qty_is_not_zero(self):
for item in self.items: for item in self.items:
@ -1161,7 +1195,6 @@ class AccountsController(TransactionBase):
) )
credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit" 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 against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
if precision_loss: if precision_loss:
@ -1169,9 +1202,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": round_off_account, "account": round_off_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
credit_or_debit: precision_loss, credit_or_debit: precision_loss,
"cost_center": round_off_cost_center "cost_center": round_off_cost_center
if self.use_company_roundoff_cost_center if self.use_company_roundoff_cost_center
@ -1219,7 +1250,9 @@ class AccountsController(TransactionBase):
return True return True
return False 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 Make Exchange Gain/Loss journal for Invoices and Payments
""" """
@ -1274,6 +1307,7 @@ class AccountsController(TransactionBase):
self.name, self.name,
arg.get("referenced_row"), arg.get("referenced_row"),
arg.get("cost_center"), arg.get("cost_center"),
dimensions_dict,
) )
frappe.msgprint( frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format( _("Exchange Gain/Loss amount has been booked through {0}").format(
@ -1354,6 +1388,7 @@ class AccountsController(TransactionBase):
self.name, self.name,
d.idx, d.idx,
self.cost_center, self.cost_center,
dimensions_dict,
) )
frappe.msgprint( frappe.msgprint(
_("Exchange Gain/Loss amount has been booked through {0}").format( _("Exchange Gain/Loss amount has been booked through {0}").format(
@ -1418,14 +1453,25 @@ class AccountsController(TransactionBase):
if lst: if lst:
from erpnext.accounts.utils import reconcile_against_document 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): def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
remove_from_bank_transaction,
)
from erpnext.accounts.utils import ( from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal, cancel_exchange_gain_loss_journal,
unlink_ref_doc_from_payment_entries, 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"]: if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
# Cancel Exchange Gain/Loss Journal before unlinking # Cancel Exchange Gain/Loss Journal before unlinking
cancel_exchange_gain_loss_journal(self) cancel_exchange_gain_loss_journal(self)
@ -1520,13 +1566,11 @@ class AccountsController(TransactionBase):
if self.doctype == "Purchase Invoice": if self.doctype == "Purchase Invoice":
dr_or_cr = "credit" dr_or_cr = "credit"
rev_dr_cr = "debit" rev_dr_cr = "debit"
against_type = "Supplier"
supplier_or_customer = self.supplier supplier_or_customer = self.supplier
else: else:
dr_or_cr = "debit" dr_or_cr = "debit"
rev_dr_cr = "credit" rev_dr_cr = "credit"
against_type = "Customer"
supplier_or_customer = self.customer supplier_or_customer = self.customer
if enable_discount_accounting: if enable_discount_accounting:
@ -1551,9 +1595,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item.discount_account, "account": item.discount_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: flt( dr_or_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount") discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
), ),
@ -1571,9 +1613,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": income_or_expense_account, "account": income_or_expense_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
rev_dr_cr: flt( rev_dr_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount") discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
), ),
@ -1596,9 +1636,7 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.additional_discount_account, "account": self.additional_discount_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: self.base_discount_amount, dr_or_cr: self.base_discount_amount,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), "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): def set_total_advance_paid(self):
ple = frappe.qb.DocType("Payment Ledger Entry") 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 = ( advance = (
frappe.qb.from_(ple) frappe.qb.from_(ple)
.select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount")) .select(ple.account_currency, Abs(Sum(ple.amount_in_account_currency)).as_("amount"))
@ -1769,6 +1810,8 @@ class AccountsController(TransactionBase):
.run(as_dict=True) .run(as_dict=True)
) )
advance_paid, order_total = None, None
if advance: if advance:
advance = advance[0] advance = advance[0]
@ -1797,7 +1840,38 @@ class AccountsController(TransactionBase):
).format(formatted_advance_paid, self.name, formatted_order_total) ).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 @property
def company_abbr(self): def company_abbr(self):
@ -2690,47 +2764,37 @@ def get_common_query(
q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate")) q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
if condition: if condition:
if condition.get("name", None): # conditions should be built as an array and passed as Criterion
q = q.where(payment_entry.name.like(f"%{condition.get('name')}%")) 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: if condition.get("get_payments") == True:
q = ( if condition.get("cost_center"):
q.where(payment_entry.cost_center == condition["cost_center"]) common_filter_conditions.append(payment_entry.cost_center == condition["cost_center"])
if condition.get("cost_center")
else q 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"])
) )
q = (
q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) if condition.get("maximum_payment_amount"):
if condition.get("minimum_payment_amount") common_filter_conditions.append(
else q payment_entry.unallocated_amount.lte(condition["maximum_payment_amount"])
)
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
) )
q = q.where(Criterion.all(common_filter_conditions))
q = q.orderby(payment_entry.posting_date) q = q.orderby(payment_entry.posting_date)
q = q.limit(limit) if limit else q 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) 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.flags.ignore_mandatory = True
variant.save() variant.save()

View File

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

View File

@ -141,7 +141,7 @@ def validate_returned_items(doc):
items_returned = True items_returned = True
if not items_returned: 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): def validate_quantity(doc, args, ref, valid_items, already_returned_items):

View File

@ -53,6 +53,10 @@ status_map = {
"To Deliver", "To Deliver",
"eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note", "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", "Completed",
"eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1", "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": [ "Purchase Order": [
["Draft", None], ["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 Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
[ [
"To Receive", "To Receive",
"eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1", "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", "Completed",
"eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1", "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( self.get_gl_dict(
{ {
"account": warehouse_account[sle.warehouse]["account"], "account": warehouse_account[sle.warehouse]["account"],
"against_type": "Account",
"against": expense_account, "against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"), "project": item_row.project or self.get("project"),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -180,9 +178,7 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Account",
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"against_link": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(sle.stock_value_difference, precision), "debit": -1 * flt(sle.stock_value_difference, precision),
@ -214,9 +210,7 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Account",
"against": warehouse_asset_account, "against": warehouse_asset_account,
"against_link": warehouse_asset_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"), "project": item_row.project or self.get("project"),
"remarks": _("Rounding gain/loss Entry for Stock Transfer"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
@ -232,9 +226,7 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": warehouse_asset_account, "account": warehouse_asset_account,
"against_type": "Account",
"against": expense_account, "against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": _("Rounding gain/loss Entry for Stock Transfer"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"credit": sle_rounding_diff, "credit": sle_rounding_diff,
@ -395,11 +387,7 @@ class StockController(AccountsController):
} }
for row in self.get(table_name): for row in self.get(table_name):
for field in [ for field in QTY_FIELD.keys():
"serial_and_batch_bundle",
"current_serial_and_batch_bundle",
"rejected_serial_and_batch_bundle",
]:
if row.get(field): if row.get(field):
frappe.get_doc("Serial and Batch Bundle", row.get(field)).set_serial_and_batch_values( frappe.get_doc("Serial and Batch Bundle", row.get(field)).set_serial_and_batch_values(
self, row, qty_field=QTY_FIELD[field] self, row, qty_field=QTY_FIELD[field]
@ -840,7 +828,6 @@ class StockController(AccountsController):
credit, credit,
remarks, remarks,
against_account, against_account,
against_type="Account",
debit_in_account_currency=None, debit_in_account_currency=None,
credit_in_account_currency=None, credit_in_account_currency=None,
account_currency=None, account_currency=None,
@ -855,9 +842,7 @@ class StockController(AccountsController):
"cost_center": cost_center, "cost_center": cost_center,
"debit": debit, "debit": debit,
"credit": credit, "credit": credit,
"against_type": against_type,
"against": against_account, "against": against_account,
"against_link": against_account,
"remarks": remarks, "remarks": remarks,
} }

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