Merge branch 'develop' of https://github.com/frappe/erpnext into in_words_pe

This commit is contained in:
Deepesh Garg 2024-01-22 09:33:46 +05:30
commit 13aae34e9c
328 changed files with 1466718 additions and 568614 deletions

View File

@ -21,6 +21,6 @@ jobs:
- name: Run backport - name: Run backport
uses: ./actions/backport uses: ./actions/backport
with: with:
token: ${{secrets.BACKPORT_BOT_TOKEN}} token: ${{secrets.RELEASE_TOKEN}}
labelsToAdd: "backport" labelsToAdd: "backport"
title: "{{originalTitle}}" title: "{{originalTitle}}"

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

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

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

@ -91,8 +91,8 @@ class Account(NestedSet):
super(Account, self).on_update() super(Account, self).on_update()
def onload(self): def onload(self):
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_single_value(
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier" "Accounts Settings", "frozen_accounts_modifier"
) )
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles(): if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True) self.set_onload("can_freeze_account", True)

View File

@ -77,7 +77,7 @@ frappe.treeview_settings["Account"] = {
// show Dr if positive since balance is calculated as debit - credit else show Cr // show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance; const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr"; const dr_or_cr = balance > 0 ? __("Dr"): __("Cr");
const format = (value, currency) => format_currency(Math.abs(value), currency); const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) { if (account.balance!==undefined) {

View File

@ -74,7 +74,7 @@ def create_charts(
# after all accounts are already inserted. # after all accounts are already inserted.
frappe.local.flags.ignore_update_nsm = True frappe.local.flags.ignore_update_nsm = True
_import_accounts(chart, None, None, root_account=True) _import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account", "parent_account") rebuild_tree("Account")
frappe.local.flags.ignore_update_nsm = False frappe.local.flags.ignore_update_nsm = False
@ -231,6 +231,8 @@ def build_account_tree(tree, parent, all_accounts):
tree[child.account_name]["account_type"] = child.account_type tree[child.account_name]["account_type"] = child.account_type
if child.tax_rate: if child.tax_rate:
tree[child.account_name]["tax_rate"] = child.tax_rate tree[child.account_name]["tax_rate"] = child.tax_rate
if child.account_currency:
tree[child.account_name]["account_currency"] = child.account_currency
if not parent: if not parent:
tree[child.account_name]["root_type"] = child.root_type tree[child.account_name]["root_type"] = child.root_type

View File

@ -444,6 +444,10 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
vouchers = json.loads(vouchers) vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
transaction.add_payment_entries(vouchers) transaction.add_payment_entries(vouchers)
transaction.validate_duplicate_references()
transaction.allocate_payment_entries()
transaction.update_allocated_amount()
transaction.set_status()
transaction.save() transaction.save()
return transaction return transaction

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,12 +3,12 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt from frappe.utils import flt
from erpnext.controllers.status_updater import StatusUpdater
class BankTransaction(Document):
class BankTransaction(StatusUpdater):
# begin: auto-generated types # begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block. # This code is auto-generated. Do not modify anything in this block.
@ -49,6 +49,33 @@ class BankTransaction(StatusUpdater):
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):
if self.docstatus == 2:
self.db_set("status", "Cancelled")
elif self.docstatus == 1:
if self.unallocated_amount > 0:
self.db_set("status", "Unreconciled")
elif self.unallocated_amount <= 0:
self.db_set("status", "Reconciled")
def validate_duplicate_references(self): def validate_duplicate_references(self):
"""Make sure the same voucher is not allocated twice within the same Bank Transaction""" """Make sure the same voucher is not allocated twice within the same Bank Transaction"""
@ -83,12 +110,13 @@ class BankTransaction(StatusUpdater):
self.validate_duplicate_references() self.validate_duplicate_references()
self.allocate_payment_entries() self.allocate_payment_entries()
self.update_allocated_amount() self.update_allocated_amount()
self.set_status()
def on_cancel(self): def on_cancel(self):
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
self.clear_linked_payment_entry(payment_entry, for_cancel=True) self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.set_status(update=True) self.set_status()
def add_payment_entries(self, vouchers): def add_payment_entries(self, vouchers):
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance" "Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
@ -366,15 +394,17 @@ def set_voucher_clearance(doctype, docname, clearance_date, self):
and len(get_reconciled_bank_transactions(doctype, docname)) < 2 and len(get_reconciled_bank_transactions(doctype, docname)) < 2
): ):
return return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Sales Invoice": if doctype == "Sales Invoice":
frappe.db.set_value( frappe.db.set_value(
"Sales Invoice Payment", "Sales Invoice Payment",
dict(parenttype=doctype, parent=docname), dict(parenttype=doctype, parent=docname),
"clearance_date", "clearance_date",
clearance_date, clearance_date,
) )
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction": elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund # For when a second bank transaction has fixed another, e.g. refund
@ -404,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

@ -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

@ -435,8 +435,8 @@ def update_outstanding_amt(
def validate_frozen_account(account, adv_adj=None): def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account") frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == "Yes" and not adv_adj: if frozen_account == "Yes" and not adv_adj:
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_single_value(
"Accounts Settings", None, "frozen_accounts_modifier" "Accounts Settings", "frozen_accounts_modifier"
) )
if not frozen_accounts_modifier: if not frozen_accounts_modifier:

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

@ -304,7 +304,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 +750,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 +967,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 +997,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 +1625,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: [
"voucher_type",
"posting_date",
"total_debit",
"company",
"user_remark",
],
get_indicator: function (doc) { get_indicator: function (doc) {
if(doc.docstatus==0) { if (doc.docstatus === 1) {
return [__("Draft", "red", "docstatus,=,0")] return [
} else if(doc.docstatus==2) { __(doc.voucher_type),
return [__("Cancelled", "grey", "docstatus,=,2")] "blue",
} else { `voucher_type,=,${doc.voucher_type}`,
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

@ -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);
@ -747,6 +747,10 @@ frappe.ui.form.on('Payment Entry', {
args["get_orders_to_be_billed"] = true; args["get_orders_to_be_billed"] = true;
} }
if (frm.doc.book_advance_payments_in_separate_party_account) {
args["book_advance_payments_in_separate_party_account"] = true;
}
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({ return frappe.call({
@ -929,7 +933,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

@ -225,6 +225,7 @@
"fieldname": "party_balance", "fieldname": "party_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Party Balance", "label": "Party Balance",
"no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -775,7 +776,7 @@
"table_fieldname": "payment_entries" "table_fieldname": "payment_entries"
} }
], ],
"modified": "2024-01-03 12:46:41.759121", "modified": "2024-01-08 13:17:15.744754",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@ -50,6 +50,88 @@ class InvalidPaymentEntry(ValidationError):
class PaymentEntry(AccountsController): class PaymentEntry(AccountsController):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.advance_taxes_and_charges.advance_taxes_and_charges import (
AdvanceTaxesandCharges,
)
from erpnext.accounts.doctype.payment_entry_deduction.payment_entry_deduction import (
PaymentEntryDeduction,
)
from erpnext.accounts.doctype.payment_entry_reference.payment_entry_reference import (
PaymentEntryReference,
)
amended_from: DF.Link | None
apply_tax_withholding_amount: DF.Check
auto_repeat: DF.Link | None
bank: DF.ReadOnly | None
bank_account: DF.Link | None
bank_account_no: DF.ReadOnly | None
base_paid_amount: DF.Currency
base_paid_amount_after_tax: DF.Currency
base_received_amount: DF.Currency
base_received_amount_after_tax: DF.Currency
base_total_allocated_amount: DF.Currency
base_total_taxes_and_charges: DF.Currency
book_advance_payments_in_separate_party_account: DF.Check
clearance_date: DF.Date | None
company: DF.Link
contact_email: DF.Data | None
contact_person: DF.Link | None
cost_center: DF.Link | None
custom_remarks: DF.Check
deductions: DF.Table[PaymentEntryDeduction]
difference_amount: DF.Currency
letter_head: DF.Link | None
mode_of_payment: DF.Link | None
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
paid_amount: DF.Currency
paid_amount_after_tax: DF.Currency
paid_from: DF.Link
paid_from_account_balance: DF.Currency
paid_from_account_currency: DF.Link
paid_from_account_type: DF.Data | None
paid_to: DF.Link
paid_to_account_balance: DF.Currency
paid_to_account_currency: DF.Link
paid_to_account_type: DF.Data | None
party: DF.DynamicLink | None
party_balance: DF.Currency
party_bank_account: DF.Link | None
party_name: DF.Data | None
party_type: DF.Link | None
payment_order: DF.Link | None
payment_order_status: DF.Literal["Initiated", "Payment Ordered"]
payment_type: DF.Literal["Receive", "Pay", "Internal Transfer"]
posting_date: DF.Date
print_heading: DF.Link | None
project: DF.Link | None
purchase_taxes_and_charges_template: DF.Link | None
received_amount: DF.Currency
received_amount_after_tax: DF.Currency
reference_date: DF.Date | None
reference_no: DF.Data | None
references: DF.Table[PaymentEntryReference]
remarks: DF.SmallText | None
sales_taxes_and_charges_template: DF.Link | None
source_exchange_rate: DF.Float
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
target_exchange_rate: DF.Float
tax_withholding_category: DF.Link | None
taxes: DF.Table[AdvanceTaxesandCharges]
title: DF.Data | None
total_allocated_amount: DF.Currency
total_taxes_and_charges: DF.Currency
unallocated_amount: DF.Currency
# end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PaymentEntry, self).__init__(*args, **kwargs) super(PaymentEntry, self).__init__(*args, **kwargs)
if not self.is_new(): if not self.is_new():
@ -257,6 +339,7 @@ class PaymentEntry(AccountsController):
"get_outstanding_invoices": True, "get_outstanding_invoices": True,
"get_orders_to_be_billed": True, "get_orders_to_be_billed": True,
"vouchers": vouchers, "vouchers": vouchers,
"book_advance_payments_in_separate_party_account": self.book_advance_payments_in_separate_party_account,
}, },
validate=True, validate=True,
) )
@ -1077,9 +1160,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,
}, },
@ -1244,9 +1325,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,
@ -1261,9 +1340,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,
@ -1287,7 +1364,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
@ -1296,9 +1372,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
@ -1323,9 +1397,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
@ -1350,9 +1422,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,
@ -1644,11 +1714,16 @@ def get_outstanding_reference_documents(args, validate=False):
outstanding_invoices = [] outstanding_invoices = []
negative_outstanding_invoices = [] negative_outstanding_invoices = []
if args.get("book_advance_payments_in_separate_party_account"):
party_account = get_party_account(args.get("party_type"), args.get("party"), args.get("company"))
else:
party_account = args.get("party_account")
if args.get("get_outstanding_invoices"): if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices( outstanding_invoices = get_outstanding_invoices(
args.get("party_type"), args.get("party_type"),
args.get("party"), args.get("party"),
get_party_account(args.get("party_type"), args.get("party"), args.get("company")), party_account,
common_filter=common_filter, common_filter=common_filter,
posting_date=posting_and_due_date, posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"), min_outstanding=args.get("outstanding_amt_greater_than"),

View File

@ -424,6 +424,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

@ -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

@ -527,7 +527,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
values.extend(warehouses) values.extend(warehouses)
if items: if items:
condition = " and `tab{child_doc}`.{apply_on} in ({items})".format( condition += " and `tab{child_doc}`.{apply_on} in ({items})".format(
child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items)) child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items))
) )

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

@ -296,6 +296,18 @@ class PurchaseInvoice(BuyingController):
self.reset_default_field_value("set_warehouse", "items", "warehouse") self.reset_default_field_value("set_warehouse", "items", "warehouse")
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse") self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
self.set_percentage_received()
def set_percentage_received(self):
total_billed_qty = 0.0
total_received_qty = 0.0
for row in self.items:
if row.purchase_receipt and row.pr_detail and row.received_qty:
total_billed_qty += row.qty
total_received_qty += row.received_qty
if total_billed_qty and total_received_qty:
self.per_received = total_received_qty / total_billed_qty * 100
def validate_release_date(self): def validate_release_date(self):
if self.release_date and getdate(nowdate()) >= getdate(self.release_date): if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
@ -552,7 +564,7 @@ class PurchaseInvoice(BuyingController):
self.against_expense_account = ",".join(against_accounts) self.against_expense_account = ",".join(against_accounts)
def po_required(self): def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes": if frappe.db.get_single_value("Buying Settings", "po_required") == "Yes":
if frappe.get_value( if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order" "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
@ -572,7 +584,7 @@ class PurchaseInvoice(BuyingController):
def pr_required(self): def pr_required(self):
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
if frappe.db.get_value("Buying Settings", None, "pr_required") == "Yes": if frappe.db.get_single_value("Buying Settings", "pr_required") == "Yes":
if frappe.get_value( if frappe.get_value(
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt" "Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt"
@ -815,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
@ -890,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"),
@ -912,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"),
@ -931,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,
@ -950,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,
@ -971,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"]),
@ -993,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"),
@ -1050,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,
@ -1078,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,
@ -1093,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,
@ -1104,17 +1096,6 @@ class PurchaseInvoice(BuyingController):
item=item, item=item,
) )
) )
# update gross amount of asset bought through this document
assets = frappe.db.get_all(
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
)
for asset in assets:
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
frappe.db.set_value(
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
)
if ( if (
self.auto_accounting_for_stock self.auto_accounting_for_stock
and self.is_opening == "No" and self.is_opening == "No"
@ -1139,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,
@ -1156,17 +1135,24 @@ class PurchaseInvoice(BuyingController):
item.item_tax_amount, item.precision("item_tax_amount") item.item_tax_amount, item.precision("item_tax_amount")
) )
if item.is_fixed_asset and item.landed_cost_voucher_amount:
self.update_gross_purchase_amount_for_linked_assets(item)
def update_gross_purchase_amount_for_linked_assets(self, item):
assets = frappe.db.get_all( assets = frappe.db.get_all(
"Asset", "Asset",
filters={"purchase_invoice": self.name, "item_code": item.item_code}, filters={"purchase_invoice": self.name, "item_code": item.item_code},
fields=["name", "asset_quantity"], fields=["name", "asset_quantity"],
) )
for asset in assets: for asset in assets:
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
frappe.db.set_value( frappe.db.set_value(
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity "Asset",
) asset.name,
frappe.db.set_value( {
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity "gross_purchase_amount": purchase_amount,
"purchase_receipt_amount": purchase_amount,
},
) )
def make_stock_adjustment_entry( def make_stock_adjustment_entry(
@ -1196,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,
@ -1228,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
@ -1278,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"),
}, },
@ -1299,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"),
}, },
@ -1316,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,
@ -1339,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
@ -1362,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
@ -1388,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
@ -1410,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
@ -1439,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

@ -14,7 +14,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.buying.doctype.supplier.test_supplier import create_supplier from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.accounts_controller import get_payment_terms from erpnext.controllers.accounts_controller import InvalidQtyError, get_payment_terms
from erpnext.controllers.buying_controller import QtyMismatchError from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
@ -51,6 +51,16 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def test_purchase_invoice_qty(self):
pi = make_purchase_invoice(qty=0, do_not_save=True)
with self.assertRaises(InvalidQtyError):
pi.save()
# No error with qty=1
pi.items[0].qty = 1
pi.save()
self.assertEqual(pi.items[0].qty, 1)
def test_purchase_invoice_received_qty(self): def test_purchase_invoice_received_qty(self):
""" """
1. Test if received qty is validated against accepted + rejected 1. Test if received qty is validated against accepted + rejected
@ -1227,11 +1237,11 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
def test_gain_loss_with_advance_entry(self): def test_gain_loss_with_advance_entry(self):
unlink_enabled = frappe.db.get_value( unlink_enabled = frappe.db.get_single_value(
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice" "Accounts Settings", "unlink_payment_on_cancellation_of_invoice"
) )
frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 1)
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account") original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
frappe.db.set_value( frappe.db.set_value(
@ -1422,7 +1432,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pay.cancel() pay.cancel()
frappe.db.set_single_value( frappe.db.set_single_value(
"Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled "Accounts Settings", "unlink_payment_on_cancellation_of_invoice", unlink_enabled
) )
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
@ -2114,7 +2124,7 @@ def make_purchase_invoice(**args):
bundle_id = None bundle_id = None
if args.get("batch_no") or args.get("serial_no"): if args.get("batch_no") or args.get("serial_no"):
batches = {} batches = {}
qty = args.qty or 5 qty = args.qty if args.qty is not None else 5
item_code = args.item or args.item_code or "_Test Item" item_code = args.item or args.item_code or "_Test Item"
if args.get("batch_no"): if args.get("batch_no"):
batches = frappe._dict({args.batch_no: qty}) batches = frappe._dict({args.batch_no: qty})
@ -2143,7 +2153,7 @@ def make_purchase_invoice(**args):
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
"item_name": args.item_name, "item_name": args.item_name,
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 5, "qty": args.qty if args.qty is not None else 5,
"received_qty": args.received_qty or 0, "received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0, "rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50, "rate": args.rate or 50,

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) {
@ -898,8 +897,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.events.append_time_log(frm, timesheet, 1.0); frm.events.append_time_log(frm, timesheet, 1.0);
} }
}); });
frm.refresh_field("timesheets");
frm.trigger("calculate_timesheet_totals"); frm.trigger("calculate_timesheet_totals");
frm.refresh();
}, },
async get_exchange_rate(frm, from_currency, to_currency) { async get_exchange_rate(frm, from_currency, to_currency) {

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

@ -23,7 +23,7 @@ from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_d
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depr_schedule, get_depr_schedule,
) )
from erpnext.controllers.accounts_controller import update_invoice_status from erpnext.controllers.accounts_controller import InvalidQtyError, update_invoice_status
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.selling.doctype.customer.test_customer import get_customer_dict from erpnext.selling.doctype.customer.test_customer import get_customer_dict
@ -72,6 +72,16 @@ class TestSalesInvoice(FrappeTestCase):
def tearDownClass(self): def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0) unlink_payment_on_cancel_of_invoice(0)
def test_sales_invoice_qty(self):
si = create_sales_invoice(qty=0, do_not_save=True)
with self.assertRaises(InvalidQtyError):
si.save()
# No error with qty=1
si.items[0].qty = 1
si.save()
self.assertEqual(si.items[0].qty, 1)
def test_timestamp_change(self): def test_timestamp_change(self):
w = frappe.copy_doc(test_records[0]) w = frappe.copy_doc(test_records[0])
w.docstatus = 0 w.docstatus = 0
@ -3636,7 +3646,7 @@ def create_sales_invoice(**args):
bundle_id = None bundle_id = None
if si.update_stock and (args.get("batch_no") or args.get("serial_no")): if si.update_stock and (args.get("batch_no") or args.get("serial_no")):
batches = {} batches = {}
qty = args.qty or 1 qty = args.qty if args.qty is not None else 1
item_code = args.item or args.item_code or "_Test Item" item_code = args.item or args.item_code or "_Test Item"
if args.get("batch_no"): if args.get("batch_no"):
batches = frappe._dict({args.batch_no: qty}) batches = frappe._dict({args.batch_no: qty})
@ -3668,7 +3678,7 @@ def create_sales_invoice(**args):
"description": args.description or "_Test Item", "description": args.description or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"target_warehouse": args.target_warehouse, "target_warehouse": args.target_warehouse,
"qty": args.qty or 1, "qty": args.qty if args.qty is not None else 1,
"uom": args.uom or "Nos", "uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos", "stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100, "rate": args.rate if args.get("rate") is not None else 100,

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

@ -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,
) )
@ -317,6 +318,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 +595,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

@ -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

@ -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:
@ -654,10 +653,10 @@ def check_freezing_date(posting_date, adv_adj=False):
Hence stop admin to bypass if accounts are freezed Hence stop admin to bypass if accounts are freezed
""" """
if not adv_adj: if not adv_adj:
acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto") acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
if acc_frozen_upto: if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_single_value(
"Accounts Settings", None, "frozen_accounts_modifier" "Accounts Settings", "frozen_accounts_modifier"
) )
if getdate(posting_date) <= getdate(acc_frozen_upto) and ( if getdate(posting_date) <= getdate(acc_frozen_upto) and (
frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator" frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"

View File

@ -114,14 +114,12 @@ def _get_party_details(
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype) set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
) )
party = party_details[party_type.lower()] party = party_details[party_type.lower()]
if not ignore_permissions and not (
frappe.has_permission(party_type, "read", party)
or frappe.has_permission(party_type, "select", party)
):
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
party = frappe.get_doc(party_type, party) party = frappe.get_doc(party_type, party)
if not ignore_permissions:
ptype = "select" if frappe.only_has_select_perm(party_type) else "read"
frappe.has_permission(party_type, ptype, party, throw=True)
currency = party.get("default_currency") or currency or get_company_currency(company) currency = party.get("default_currency") or currency or get_company_currency(company)
party_address, shipping_address = set_address_details( party_address, shipping_address = set_address_details(
@ -637,9 +635,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
return due_date return due_date
def validate_due_date( def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
):
if getdate(due_date) < getdate(posting_date): if getdate(due_date) < getdate(posting_date):
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date")) frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
else: else:

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

@ -55,8 +55,8 @@ class ReceivablePayableReport(object):
def run(self, args): def run(self, args):
self.filters.update(args) self.filters.update(args)
self.set_defaults() self.set_defaults()
self.party_naming_by = frappe.db.get_value( self.party_naming_by = frappe.db.get_single_value(
args.get("naming_by")[0], None, args.get("naming_by")[1] args.get("naming_by")[0], args.get("naming_by")[1]
) )
self.get_columns() self.get_columns()
self.get_data() self.get_data()

View File

@ -24,8 +24,8 @@ class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args): def run(self, args):
self.account_type = args.get("account_type") self.account_type = args.get("account_type")
self.party_type = get_party_types_from_account_type(self.account_type) self.party_type = get_party_types_from_account_type(self.account_type)
self.party_naming_by = frappe.db.get_value( self.party_naming_by = frappe.db.get_single_value(
args.get("naming_by")[0], None, args.get("naming_by")[1] args.get("naming_by")[0], args.get("naming_by")[1]
) )
self.get_columns() self.get_columns()
self.get_data(args) self.get_data(args)

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

@ -128,7 +128,7 @@ frappe.query_reports["Consolidated Financial Statement"] = {
} }
value = default_formatter(value, row, column, data); value = default_formatter(value, row, column, data);
if (!data.parent_account) { if (data && !data.parent_account) {
value = $(`<span>${value}</span>`); value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold"); var $value = $(value).css("font-weight", "bold");

View File

@ -21,8 +21,8 @@ class PartyLedgerSummaryReport(object):
frappe.throw(_("From Date must be before To Date")) frappe.throw(_("From Date must be before To Date"))
self.filters.party_type = args.get("party_type") self.filters.party_type = args.get("party_type")
self.party_naming_by = frappe.db.get_value( self.party_naming_by = frappe.db.get_single_value(
args.get("naming_by")[0], None, args.get("naming_by")[1] args.get("naming_by")[0], args.get("naming_by")[1]
) )
self.get_gl_entries() self.get_gl_entries()
@ -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

@ -251,6 +251,7 @@ def get_journal_entries(filters, args):
) )
.where( .where(
(je.voucher_type == "Journal Entry") (je.voucher_type == "Journal Entry")
& (je.docstatus == 1)
& (journal_account.party == filters.get(args.party)) & (journal_account.party == filters.get(args.party))
& (journal_account.account.isin(args.party_account)) & (journal_account.account.isin(args.party_account))
) )
@ -281,7 +282,9 @@ def get_payment_entries(filters, args):
pe.cost_center, pe.cost_center,
) )
.where( .where(
(pe.party == filters.get(args.party)) & (pe[args.account_fieldname].isin(args.party_account)) (pe.docstatus == 1)
& (pe.party == filters.get(args.party))
& (pe[args.account_fieldname].isin(args.party_account))
) )
.orderby(pe.posting_date, pe.name, order=Order.desc) .orderby(pe.posting_date, pe.name, order=Order.desc)
) )
@ -365,7 +368,7 @@ def filter_invoices_based_on_dimensions(filters, query, parent_doc):
dimension.document_type, filters.get(dimension.fieldname) dimension.document_type, filters.get(dimension.fieldname)
) )
fieldname = dimension.fieldname fieldname = dimension.fieldname
query = query.where(parent_doc[fieldname] == filters.fieldname) query = query.where(parent_doc[fieldname].isin(filters[fieldname]))
return query return query

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

@ -642,7 +642,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
@ -1275,7 +1274,7 @@ 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()

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

View File

@ -202,9 +202,8 @@
"fieldname": "purchase_date", "fieldname": "purchase_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Purchase Date", "label": "Purchase Date",
"read_only": 1, "mandatory_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset", "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
"reqd": 1
}, },
{ {
"fieldname": "disposal_date", "fieldname": "disposal_date",
@ -227,15 +226,15 @@
"fieldname": "gross_purchase_amount", "fieldname": "gross_purchase_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Gross Purchase Amount", "label": "Gross Purchase Amount",
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only_depends_on": "eval:!doc.is_existing_asset", "read_only_depends_on": "eval:!doc.is_existing_asset"
"reqd": 1
}, },
{ {
"fieldname": "available_for_use_date", "fieldname": "available_for_use_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Available-for-use Date", "label": "Available-for-use Date",
"reqd": 1 "mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)"
}, },
{ {
"default": "0", "default": "0",
@ -590,7 +589,7 @@
"link_fieldname": "target_asset" "link_fieldname": "target_asset"
} }
], ],
"modified": "2023-12-21 16:46:20.732869", "modified": "2024-01-15 17:35:49.226603",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@ -57,7 +57,7 @@ class Asset(AccountsController):
asset_owner: DF.Literal["", "Company", "Supplier", "Customer"] asset_owner: DF.Literal["", "Company", "Supplier", "Customer"]
asset_owner_company: DF.Link | None asset_owner_company: DF.Link | None
asset_quantity: DF.Int asset_quantity: DF.Int
available_for_use_date: DF.Date available_for_use_date: DF.Date | None
booked_fixed_asset: DF.Check booked_fixed_asset: DF.Check
calculate_depreciation: DF.Check calculate_depreciation: DF.Check
capitalized_in: DF.Link | None capitalized_in: DF.Link | None
@ -92,7 +92,7 @@ class Asset(AccountsController):
number_of_depreciations_booked: DF.Int number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency opening_accumulated_depreciation: DF.Currency
policy_number: DF.Data | None policy_number: DF.Data | None
purchase_date: DF.Date purchase_date: DF.Date | None
purchase_invoice: DF.Link | None purchase_invoice: DF.Link | None
purchase_receipt: DF.Link | None purchase_receipt: DF.Link | None
purchase_receipt_amount: DF.Currency purchase_receipt_amount: DF.Currency
@ -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()
@ -316,7 +317,12 @@ class Asset(AccountsController):
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category): if is_cwip_accounting_enabled(self.asset_category):
if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice: if (
not self.is_existing_asset
and not self.is_composite_asset
and not self.purchase_receipt
and not self.purchase_invoice
):
frappe.throw( frappe.throw(
_("Please create purchase receipt or purchase invoice for the item {0}").format( _("Please create purchase receipt or purchase invoice for the item {0}").format(
self.item_code self.item_code
@ -329,7 +335,7 @@ class Asset(AccountsController):
and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock") and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
): ):
frappe.throw( frappe.throw(
_("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice) _("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice)
) )
if not self.calculate_depreciation: if not self.calculate_depreciation:
@ -512,6 +518,16 @@ 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):
asset_capitalization = frappe.db.get_value(
"Asset Capitalization",
{"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"},
)
if asset_capitalization:
asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization)
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"):
@ -692,9 +708,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,
@ -709,9 +723,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,
@ -1026,6 +1038,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

@ -19,6 +19,7 @@ from frappe.utils import (
) )
from frappe.utils.user import get_users_with_role from frappe.utils.user import get_users_with_role
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, get_checks_for_pl_and_bs_accounts,
) )
@ -35,7 +36,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
def post_depreciation_entries(date=None): def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint( if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically") frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")
): ):
return return
@ -522,6 +523,13 @@ def depreciate_asset(asset_doc, date, notes):
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date) make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
cancel_depreciation_entries(asset_doc, date)
@erpnext.allow_regional
def cancel_depreciation_entries(asset_doc, date):
pass
def reset_depreciation_schedule(asset_doc, date, notes): def reset_depreciation_schedule(asset_doc, date, notes):
if not asset_doc.calculate_depreciation: if not asset_doc.calculate_depreciation:

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

@ -3,6 +3,7 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import now_datetime
class AssetActivity(Document): class AssetActivity(Document):
@ -30,5 +31,6 @@ def add_asset_activity(asset, subject):
"asset": asset, "asset": asset,
"subject": subject, "subject": subject,
"user": frappe.session.user, "user": frappe.session.user,
"date": now_datetime(),
} }
).insert(ignore_permissions=True, ignore_links=True) ).insert(ignore_permissions=True, ignore_links=True)

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,19 @@ 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)
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 +493,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 +534,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 +550,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 +561,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 +586,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 +889,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 +936,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

@ -86,12 +86,12 @@ class AssetCategory(Document):
if selected_key_type not in expected_key_types: if selected_key_type not in expected_key_types:
frappe.throw( frappe.throw(
_( _(
"Row #{}: {} of {} should be {}. Please modify the account or select a different account." "Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account."
).format( ).format(
d.idx, d.idx,
frappe.unscrub(key_to_match), frappe.unscrub(key_to_match),
frappe.bold(selected_account), frappe.bold(selected_account),
frappe.bold(expected_key_types), frappe.bold(" or ".join(expected_key_types)),
), ),
title=_("Invalid Account"), title=_("Invalid Account"),
) )

View File

@ -9,6 +9,7 @@
"field_order": [ "field_order": [
"asset", "asset",
"naming_series", "naming_series",
"company",
"column_break_2", "column_break_2",
"gross_purchase_amount", "gross_purchase_amount",
"opening_accumulated_depreciation", "opening_accumulated_depreciation",
@ -193,12 +194,20 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Depreciate based on shifts", "label": "Depreciate based on shifts",
"read_only": 1 "read_only": 1
},
{
"fetch_from": "asset.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-11-29 00:57:00.461998", "modified": "2024-01-08 16:31:04.533928",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Depreciation Schedule", "name": "Asset Depreciation Schedule",

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):
@ -35,6 +37,7 @@ class AssetDepreciationSchedule(Document):
amended_from: DF.Link | None amended_from: DF.Link | None
asset: DF.Link asset: DF.Link
company: DF.Link | None
daily_prorata_based: DF.Check daily_prorata_based: DF.Check
depreciation_method: DF.Literal[ depreciation_method: DF.Literal[
"", "Straight Line", "Double Declining Balance", "Written Down Value", "Manual" "", "Straight Line", "Double Declining Balance", "Written Down Value", "Manual"
@ -282,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:
@ -297,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,
@ -340,10 +352,7 @@ class AssetDepreciationSchedule(Document):
n == 0 n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation and not self.opening_accumulated_depreciation
and get_updated_rate_of_depreciation_for_wdv_and_dd( and not self.flags.wdv_it_act_applied
asset_doc, value_after_depreciation, row, False
)
== row.rate_of_depreciation
): ):
from_date = add_days( from_date = add_days(
asset_doc.available_for_use_date, -1 asset_doc.available_for_use_date, -1
@ -403,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
@ -584,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,
@ -595,26 +606,18 @@ def get_depreciation_amount(
asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
) )
else: else:
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row
)
return get_wdv_or_dd_depr_amount( return get_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value, depreciable_value,
rate_of_depreciation, yearly_opening_wdv,
fb_row.frequency_of_depreciation,
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,
asset_depr_schedule,
) )
@erpnext.allow_regional
def get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row, show_msg=True
):
return fb_row.rate_of_depreciation
def get_straight_line_or_manual_depr_amount( def get_straight_line_or_manual_depr_amount(
asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
): ):
@ -750,30 +753,57 @@ def get_asset_shift_factors_map():
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True)) return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
@erpnext.allow_regional
def get_wdv_or_dd_depr_amount( def get_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value, depreciable_value,
rate_of_depreciation, yearly_opening_wdv,
frequency_of_depreciation,
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,
asset_depr_schedule,
): ):
if cint(frequency_of_depreciation) == 12: return get_default_wdv_or_dd_depr_amount(
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
)
def get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
):
if cint(fb_row.frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
else: else:
if has_wdv_or_dd_non_yearly_pro_rata: if has_wdv_or_dd_non_yearly_pro_rata:
if schedule_idx == 0: if schedule_idx == 0:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100) return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1: elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
return ( return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) flt(depreciable_value)
* flt(fb_row.frequency_of_depreciation)
* (flt(fb_row.rate_of_depreciation) / 1200)
) )
else: else:
return prev_depreciation_amount return prev_depreciation_amount
else: else:
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0: if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
return ( return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200) flt(depreciable_value)
* flt(fb_row.frequency_of_depreciation)
* (flt(fb_row.rate_of_depreciation) / 1200)
) )
else: else:
return prev_depreciation_amount return prev_depreciation_amount

View File

@ -94,7 +94,6 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
"fieldname": "daily_prorata_based", "fieldname": "daily_prorata_based",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Depreciate based on daily pro-rata" "label": "Depreciate based on daily pro-rata"
@ -110,7 +109,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-11-29 00:57:07.579777", "modified": "2023-12-29 08:49:39.876439",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Finance Book", "name": "Asset Finance Book",

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

@ -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": "2023-11-28 13:01:18.403492", "modified": "2024-01-12 16:42:01.894346",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",
@ -238,6 +238,26 @@
"role": "Purchase Manager", "role": "Purchase Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"read": 1,
"role": "Accounts User"
},
{
"read": 1,
"role": "Accounts Manager"
},
{
"read": 1,
"role": "Stock Manager"
},
{
"read": 1,
"role": "Stock User"
},
{
"read": 1,
"role": "Purchase User"
} }
], ],
"sort_field": "modified", "sort_field": "modified",

View File

@ -452,6 +452,7 @@ class PurchaseOrder(BuyingController):
self.update_requested_qty() self.update_requested_qty()
self.update_ordered_qty() self.update_ordered_qty()
self.update_reserved_qty_for_subcontract() self.update_reserved_qty_for_subcontract()
self.update_subcontracting_order_status()
self.notify_update() self.notify_update()
clear_doctype_notifications(self) clear_doctype_notifications(self)
@ -627,6 +628,17 @@ class PurchaseOrder(BuyingController):
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
make_subcontracting_order(self.name, save=True, notify=True) make_subcontracting_order(self.name, save=True, notify=True)
def update_subcontracting_order_status(self):
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
update_subcontracting_order_status as update_sco_status,
)
if self.is_subcontracted and not self.is_old_subcontracting_flow:
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
if sco:
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0): def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item""" """get last purchase rate for an item"""

View File

@ -29,6 +29,8 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
class TestPurchaseOrder(FrappeTestCase): class TestPurchaseOrder(FrappeTestCase):
def test_purchase_order_qty(self): def test_purchase_order_qty(self):
po = create_purchase_order(qty=1, do_not_save=True) po = create_purchase_order(qty=1, do_not_save=True)
# NonNegativeError with qty=-1
po.append( po.append(
"items", "items",
{ {
@ -39,9 +41,15 @@ class TestPurchaseOrder(FrappeTestCase):
) )
self.assertRaises(frappe.NonNegativeError, po.save) self.assertRaises(frappe.NonNegativeError, po.save)
# InvalidQtyError with qty=0
po.items[1].qty = 0 po.items[1].qty = 0
self.assertRaises(InvalidQtyError, po.save) self.assertRaises(InvalidQtyError, po.save)
# No error with qty=1
po.items[1].qty = 1
po.save()
self.assertEqual(po.items[1].qty, 1)
def test_make_purchase_receipt(self): def test_make_purchase_receipt(self):
po = create_purchase_order(do_not_submit=True) po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name) self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
@ -1108,7 +1116,7 @@ def create_purchase_order(**args):
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"from_warehouse": args.from_warehouse, "from_warehouse": args.from_warehouse,
"qty": args.qty or 10, "qty": args.qty if args.qty is not None else 10,
"rate": args.rate or 500, "rate": args.rate or 500,
"schedule_date": add_days(nowdate(), 1), "schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get("include_exploded_items", 1), "include_exploded_items": args.get("include_exploded_items", 1),

View File

@ -65,6 +65,7 @@ class RequestforQuotation(BuyingController):
def validate(self): def validate(self):
self.validate_duplicate_supplier() self.validate_duplicate_supplier()
self.validate_supplier_list() self.validate_supplier_list()
super(RequestforQuotation, self).validate_qty_is_not_zero()
validate_for_items(self) validate_for_items(self)
super(RequestforQuotation, self).set_qty_as_per_stock_uom() super(RequestforQuotation, self).set_qty_as_per_stock_uom()
self.update_email_id() self.update_email_id()
@ -357,8 +358,8 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=
target_doc.currency = args.currency or get_party_account_currency( target_doc.currency = args.currency or get_party_account_currency(
"Supplier", for_supplier, source.company "Supplier", for_supplier, source.company
) )
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value( target_doc.buying_price_list = args.buying_price_list or frappe.db.get_single_value(
"Buying Settings", None, "buying_price_list" "Buying Settings", "buying_price_list"
) )
set_missing_values(source, target_doc) set_missing_values(source, target_doc)
@ -398,7 +399,7 @@ def create_supplier_quotation(doc):
"currency": doc.get("currency") "currency": doc.get("currency")
or get_party_account_currency("Supplier", doc.get("supplier"), doc.get("company")), or get_party_account_currency("Supplier", doc.get("supplier"), doc.get("company")),
"buying_price_list": doc.get("buying_price_list") "buying_price_list": doc.get("buying_price_list")
or frappe.db.get_value("Buying Settings", None, "buying_price_list"), or frappe.db.get_single_value("Buying Settings", "buying_price_list"),
} }
) )
add_items(sq_doc, doc.get("supplier"), doc.get("items")) add_items(sq_doc, doc.get("supplier"), doc.get("items"))

View File

@ -14,6 +14,7 @@ from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
get_pdf, get_pdf,
make_supplier_quotation_from_rfq, make_supplier_quotation_from_rfq,
) )
from erpnext.controllers.accounts_controller import InvalidQtyError
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
@ -21,6 +22,16 @@ from erpnext.templates.pages.rfq import check_supplier_has_docname_access
class TestRequestforQuotation(FrappeTestCase): class TestRequestforQuotation(FrappeTestCase):
def test_rfq_qty(self):
rfq = make_request_for_quotation(qty=0, do_not_save=True)
with self.assertRaises(InvalidQtyError):
rfq.save()
# No error with qty=1
rfq.items[0].qty = 1
rfq.save()
self.assertEqual(rfq.items[0].qty, 1)
def test_quote_status(self): def test_quote_status(self):
rfq = make_request_for_quotation() rfq = make_request_for_quotation()
@ -161,13 +172,16 @@ def make_request_for_quotation(**args) -> "RequestforQuotation":
"description": "_Test Item", "description": "_Test Item",
"uom": args.uom or "_Test UOM", "uom": args.uom or "_Test UOM",
"stock_uom": args.stock_uom or "_Test UOM", "stock_uom": args.stock_uom or "_Test UOM",
"qty": args.qty or 5, "qty": args.qty if args.qty is not None else 5,
"conversion_factor": args.conversion_factor or 1.0, "conversion_factor": args.conversion_factor or 1.0,
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"schedule_date": nowdate(), "schedule_date": nowdate(),
}, },
) )
if not args.do_not_save:
rfq.insert()
if not args.do_not_submit:
rfq.submit() rfq.submit()
return rfq return rfq

View File

@ -5,8 +5,21 @@
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.controllers.accounts_controller import InvalidQtyError
class TestPurchaseOrder(FrappeTestCase): class TestPurchaseOrder(FrappeTestCase):
def test_supplier_quotation_qty(self):
sq = frappe.copy_doc(test_records[0])
sq.items[0].qty = 0
with self.assertRaises(InvalidQtyError):
sq.save()
# No error with qty=1
sq.items[0].qty = 1
sq.save()
self.assertEqual(sq.items[0].qty, 1)
def test_make_purchase_order(self): def test_make_purchase_order(self):
from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order

View File

@ -44,11 +44,6 @@ def update_last_purchase_rate(doc, is_submit) -> None:
def validate_for_items(doc) -> None: def validate_for_items(doc) -> None:
items = [] items = []
for d in doc.get("items"): for d in doc.get("items"):
if not d.qty:
if doc.doctype == "Purchase Receipt" and d.rejected_qty:
continue
frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code))
set_stock_levels(row=d) # update with latest quantities set_stock_levels(row=d) # update with latest quantities
item = validate_item_and_get_basic_data(row=d) item = validate_item_and_get_basic_data(row=d)
validate_stock_item_warehouse(row=d, item=item) validate_stock_item_warehouse(row=d, item=item)

View File

@ -129,6 +129,17 @@ class AccountsController(TransactionBase):
if self.doctype in relevant_docs: if self.doctype in relevant_docs:
self.set_payment_schedule() self.set_payment_schedule()
def remove_bundle_for_non_stock_invoices(self):
has_sabb = False
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
for item in self.get("items"):
if item.serial_and_batch_bundle:
item.serial_and_batch_bundle = None
has_sabb = True
if has_sabb:
self.remove_serial_and_batch_bundle()
def ensure_supplier_is_not_blocked(self): def ensure_supplier_is_not_blocked(self):
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier" is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"] is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
@ -156,6 +167,9 @@ class AccountsController(TransactionBase):
if self.get("_action") and self._action != "update_after_submit": if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True) self.set_missing_values(for_validate=True)
if self.get("_action") == "submit":
self.remove_bundle_for_non_stock_invoices()
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
self.validate_date_with_fiscal_year() self.validate_date_with_fiscal_year()
@ -561,18 +575,12 @@ class AccountsController(TransactionBase):
validate_due_date( validate_due_date(
self.posting_date, self.posting_date,
self.due_date, self.due_date,
"Customer",
self.customer,
self.company,
self.payment_terms_template, self.payment_terms_template,
) )
elif self.doctype == "Purchase Invoice": elif self.doctype == "Purchase Invoice":
validate_due_date( validate_due_date(
self.bill_date or self.posting_date, self.bill_date or self.posting_date,
self.due_date, self.due_date,
"Supplier",
self.supplier,
self.company,
self.bill_date, self.bill_date,
self.payment_terms_template, self.payment_terms_template,
) )
@ -922,7 +930,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"
@ -961,19 +969,21 @@ 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):
if self.doctype == "Purchase Receipt":
return
for item in self.items: for item in self.items:
if self.doctype == "Purchase Receipt" and item.rejected_qty:
continue
if not flt(item.qty): if not flt(item.qty):
frappe.throw( frappe.throw(
msg=_("Row #{0}: Item quantity cannot be zero").format(item.idx), msg=_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
item.idx, frappe.bold(item.item_code)
),
title=_("Invalid Quantity"), title=_("Invalid Quantity"),
exc=InvalidQtyError, exc=InvalidQtyError,
) )
@ -1151,7 +1161,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:
@ -1159,9 +1168,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
@ -1411,11 +1418,16 @@ class AccountsController(TransactionBase):
reconcile_against_document(lst) reconcile_against_document(lst)
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)
@ -1510,13 +1522,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:
@ -1541,9 +1551,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")
), ),
@ -1561,9 +1569,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")
), ),
@ -1586,9 +1592,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),
}, },
@ -1952,7 +1956,7 @@ class AccountsController(TransactionBase):
self.remove(item) self.remove(item)
def set_payment_schedule(self): def set_payment_schedule(self):
if self.doctype == "Sales Invoice" and self.is_pos: if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
self.payment_terms_template = "" self.payment_terms_template = ""
return return
@ -2135,7 +2139,7 @@ class AccountsController(TransactionBase):
) )
def validate_payment_schedule_amount(self): def validate_payment_schedule_amount(self):
if self.doctype == "Sales Invoice" and self.is_pos: if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
return return
party_account_currency = self.get("party_account_currency") party_account_currency = self.get("party_account_currency")
@ -3089,7 +3093,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
def validate_quantity(child_item, new_data): def validate_quantity(child_item, new_data):
if not flt(new_data.get("qty")): if not flt(new_data.get("qty")):
frappe.throw( frappe.throw(
_("Row # {0}: Quantity for Item {1} cannot be zero").format( _("Row #{0}: Quantity for Item {1} cannot be zero.").format(
new_data.get("idx"), frappe.bold(new_data.get("item_code")) new_data.get("idx"), frappe.bold(new_data.get("item_code"))
), ),
title=_("Invalid Qty"), title=_("Invalid Qty"),

View File

@ -744,11 +744,8 @@ class BuyingController(SubcontractingController):
item_data = frappe.db.get_value( item_data = frappe.db.get_value(
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1 "Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
) )
asset_quantity = row.qty if is_grouped_asset else 1
if is_grouped_asset: purchase_amount = flt(row.valuation_rate) * asset_quantity
purchase_amount = flt(row.base_amount + row.item_tax_amount)
else:
purchase_amount = flt(row.base_rate + row.item_tax_amount)
asset = frappe.get_doc( asset = frappe.get_doc(
{ {
@ -764,7 +761,7 @@ class BuyingController(SubcontractingController):
"calculate_depreciation": 0, "calculate_depreciation": 0,
"purchase_receipt_amount": purchase_amount, "purchase_receipt_amount": purchase_amount,
"gross_purchase_amount": purchase_amount, "gross_purchase_amount": purchase_amount,
"asset_quantity": row.qty if is_grouped_asset else 1, "asset_quantity": asset_quantity,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None, "purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
} }

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

@ -10,7 +10,7 @@ from frappe.utils import flt, format_datetime, get_datetime
import erpnext import erpnext
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate, get_valuation_method
class StockOverReturnError(frappe.ValidationError): class StockOverReturnError(frappe.ValidationError):
@ -116,7 +116,12 @@ def validate_returned_items(doc):
ref = valid_items.get(d.item_code, frappe._dict()) ref = valid_items.get(d.item_code, frappe._dict())
validate_quantity(doc, d, ref, valid_items, already_returned_items) validate_quantity(doc, d, ref, valid_items, already_returned_items)
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate: if (
ref.rate
and flt(d.rate) > ref.rate
and doc.doctype in ("Delivery Note", "Sales Invoice")
and get_valuation_method(ref.item_code) != "Moving Average"
):
frappe.throw( frappe.throw(
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format( _("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
d.idx, doc.doctype, doc.return_against d.idx, doc.doctype, doc.return_against

View File

@ -295,9 +295,6 @@ class SellingController(StockController):
def get_item_list(self): def get_item_list(self):
il = [] il = []
for d in self.get("items"): for d in self.get("items"):
if d.qty is None:
frappe.throw(_("Row {0}: Qty is mandatory").format(d.idx))
if self.has_product_bundle(d.item_code): if self.has_product_bundle(d.item_code):
for p in self.get("packed_items"): for p in self.get("packed_items"):
if p.parent_detail_docname == d.name and p.parent_item == d.item_code: if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
@ -432,6 +429,9 @@ class SellingController(StockController):
items = self.get("items") + (self.get("packed_items") or []) items = self.get("items") + (self.get("packed_items") or [])
for d in items: for d in items:
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
continue
if not self.get("return_against") or ( if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
): ):

View File

@ -131,11 +131,6 @@ status_map = {
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
], ],
], ],
"Bank Transaction": [
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
["Cancelled", "eval:self.docstatus == 2"],
],
"POS Opening Entry": [ "POS Opening Entry": [
["Draft", None], ["Draft", None],
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"], ["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],

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,
} }

View File

@ -68,7 +68,7 @@ class TestQueries(unittest.TestCase):
self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1) self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1)
def test_project_query(self): def test_project_query(self):
query = add_default_params(queries.get_project_name, "BOM") query = add_default_params(queries.get_project_name, "Project")
self.assertGreaterEqual(len(query(txt="_Test Project")), 1) self.assertGreaterEqual(len(query(txt="_Test Project")), 1)

View File

@ -76,7 +76,7 @@ class Appointment(Document):
self.create_calendar_event() self.create_calendar_event()
else: else:
# Set status to unverified # Set status to unverified
self.status = "Unverified" self.db_set("status", "Unverified")
# Send email to confirm # Send email to confirm
self.send_confirmation_email() self.send_confirmation_email()

View File

@ -129,9 +129,7 @@ class TallyMigration(Document):
self.default_cost_center, self.default_round_off_account = frappe.db.get_value( self.default_cost_center, self.default_round_off_account = frappe.db.get_value(
"Company", self.erpnext_company, ["cost_center", "round_off_account"] "Company", self.erpnext_company, ["cost_center", "round_off_account"]
) )
self.default_warehouse = frappe.db.get_value( self.default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse")
"Stock Settings", "Stock Settings", "default_warehouse"
)
def _process_master_data(self): def _process_master_data(self):
def get_company_name(collection): def get_company_name(collection):

View File

@ -489,6 +489,7 @@ bank_reconciliation_doctypes = [
"Payment Entry", "Payment Entry",
"Journal Entry", "Journal Entry",
"Purchase Invoice", "Purchase Invoice",
"Sales Invoice",
] ]
accounting_dimension_doctypes = [ accounting_dimension_doctypes = [

81303
erpnext/locale/af.po Normal file

File diff suppressed because it is too large Load Diff

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