Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into payment_entry_taxes_v12
This commit is contained in:
commit
30f771b26c
@ -13,7 +13,8 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
|
||||
|
||||
class ChartofAccountsImporter(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
validate_accounts(self.import_file)
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
@ -301,28 +302,27 @@ def validate_accounts(file_name):
|
||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||
|
||||
message = validate_root(accounts_dict)
|
||||
if message: return message
|
||||
message = validate_account_types(accounts_dict)
|
||||
if message: return message
|
||||
validate_root(accounts_dict)
|
||||
|
||||
validate_account_types(accounts_dict)
|
||||
|
||||
return [True, len(accounts)]
|
||||
|
||||
def validate_root(accounts):
|
||||
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
|
||||
if len(roots) < 4:
|
||||
return _("Number of root accounts cannot be less than 4")
|
||||
frappe.throw(_("Number of root accounts cannot be less than 4"))
|
||||
|
||||
error_messages = []
|
||||
|
||||
for account in roots:
|
||||
if not account.get("root_type") and account.get("account_name"):
|
||||
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
|
||||
error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name")))
|
||||
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
|
||||
error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name")))
|
||||
error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
|
||||
|
||||
if error_messages:
|
||||
return "<br>".join(error_messages)
|
||||
frappe.throw("<br>".join(error_messages))
|
||||
|
||||
def get_root_types():
|
||||
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
|
||||
@ -356,7 +356,7 @@ def validate_account_types(accounts):
|
||||
|
||||
missing = list(set(account_types_for_ledger) - set(account_types))
|
||||
if missing:
|
||||
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
|
||||
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
account_types_for_group = ["Bank", "Cash", "Stock"]
|
||||
# fix logic bug
|
||||
@ -364,7 +364,7 @@ def validate_account_types(accounts):
|
||||
|
||||
missing = list(set(account_types_for_group) - set(account_groups))
|
||||
if missing:
|
||||
return _("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))
|
||||
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
def unset_existing_data(company):
|
||||
linked = frappe.db.sql('''select fieldname from tabDocField
|
||||
|
@ -25,7 +25,7 @@ class Dunning(AccountsController):
|
||||
|
||||
def validate_amount(self):
|
||||
amounts = calculate_interest_and_amount(
|
||||
self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||
if self.interest_amount != amounts.get('interest_amount'):
|
||||
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
|
||||
if self.dunning_amount != amounts.get('dunning_amount'):
|
||||
@ -91,13 +91,13 @@ def resolve_dunning(doc, state):
|
||||
for dunning in dunnings:
|
||||
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
|
||||
|
||||
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||
interest_amount = 0
|
||||
grand_total = 0
|
||||
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
||||
if rate_of_interest:
|
||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
||||
grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
|
||||
grand_total += flt(interest_amount)
|
||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
||||
return {
|
||||
'interest_amount': interest_amount,
|
||||
|
@ -16,6 +16,7 @@ class TestDunning(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
create_dunning_type()
|
||||
create_dunning_type_with_zero_interest_rate()
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
|
||||
@classmethod
|
||||
@ -25,11 +26,20 @@ class TestDunning(unittest.TestCase):
|
||||
def test_dunning(self):
|
||||
dunning = create_dunning()
|
||||
amounts = calculate_interest_and_amount(
|
||||
dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
|
||||
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
|
||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
||||
|
||||
def test_dunning_with_zero_interest_rate(self):
|
||||
dunning = create_dunning_with_zero_interest_rate()
|
||||
amounts = calculate_interest_and_amount(
|
||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0)
|
||||
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20)
|
||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120)
|
||||
|
||||
|
||||
def test_gl_entries(self):
|
||||
dunning = create_dunning()
|
||||
dunning.submit()
|
||||
@ -83,6 +93,27 @@ def create_dunning():
|
||||
dunning.save()
|
||||
return dunning
|
||||
|
||||
def create_dunning_with_zero_interest_rate():
|
||||
posting_date = add_days(today(), -20)
|
||||
due_date = add_days(today(), -15)
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=posting_date, due_date=due_date, status='Overdue')
|
||||
dunning_type = frappe.get_doc("Dunning Type", 'First Notice with 0% Rate of Interest')
|
||||
dunning = frappe.new_doc("Dunning")
|
||||
dunning.sales_invoice = sales_invoice.name
|
||||
dunning.customer_name = sales_invoice.customer_name
|
||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
||||
dunning.debit_to = sales_invoice.debit_to
|
||||
dunning.currency = sales_invoice.currency
|
||||
dunning.company = sales_invoice.company
|
||||
dunning.posting_date = nowdate()
|
||||
dunning.due_date = sales_invoice.due_date
|
||||
dunning.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
||||
dunning.dunning_fee = dunning_type.dunning_fee
|
||||
dunning.save()
|
||||
return dunning
|
||||
|
||||
def create_dunning_type():
|
||||
dunning_type = frappe.new_doc("Dunning Type")
|
||||
dunning_type.dunning_type = 'First Notice'
|
||||
@ -98,3 +129,19 @@ def create_dunning_type():
|
||||
}
|
||||
)
|
||||
dunning_type.save()
|
||||
|
||||
def create_dunning_type_with_zero_interest_rate():
|
||||
dunning_type = frappe.new_doc("Dunning Type")
|
||||
dunning_type.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||
dunning_type.start_day = 10
|
||||
dunning_type.end_day = 20
|
||||
dunning_type.dunning_fee = 20
|
||||
dunning_type.rate_of_interest = 0
|
||||
dunning_type.append(
|
||||
"dunning_letter_text", {
|
||||
'language': 'en',
|
||||
'body_text': 'We have still not received payment for our invoice ',
|
||||
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
|
||||
}
|
||||
)
|
||||
dunning_type.save()
|
@ -183,6 +183,13 @@ class PaymentEntry(AccountsController):
|
||||
d.reference_name, self.party_account_currency)
|
||||
|
||||
for field, value in iteritems(ref_details):
|
||||
if d.exchange_gain_loss:
|
||||
# for cases where gain/loss is booked into invoice
|
||||
# exchange_gain_loss is calculated from invoice & populated
|
||||
# and row.exchange_rate is already set to payment entry's exchange rate
|
||||
# refer -> `update_reference_in_payment_entry()` in utils.py
|
||||
continue
|
||||
|
||||
if field == 'exchange_rate' or not d.get(field) or force:
|
||||
d.db_set(field, value)
|
||||
|
||||
@ -684,8 +691,8 @@ class PaymentEntry(AccountsController):
|
||||
gl_entries.append(gle)
|
||||
|
||||
if self.unallocated_amount:
|
||||
base_unallocated_amount = self.unallocated_amount * \
|
||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
||||
exchange_rate = self.get_exchange_rate()
|
||||
base_unallocated_amount = (self.unallocated_amount * exchange_rate)
|
||||
|
||||
gle = party_gl_dict.copy()
|
||||
|
||||
@ -831,10 +838,17 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
if account_details:
|
||||
row.update(account_details)
|
||||
|
||||
if not row.get('amount'):
|
||||
# if no difference amount
|
||||
return
|
||||
|
||||
self.append('deductions', row)
|
||||
self.set_unallocated_amount()
|
||||
|
||||
def get_exchange_rate(self):
|
||||
return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate
|
||||
|
||||
def initialize_taxes(self):
|
||||
for tax in self.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
@ -1343,9 +1357,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
|
||||
return frappe._dict({
|
||||
"due_date": ref_doc.get("due_date"),
|
||||
"total_amount": total_amount,
|
||||
"outstanding_amount": outstanding_amount,
|
||||
"exchange_rate": exchange_rate,
|
||||
"total_amount": flt(total_amount),
|
||||
"outstanding_amount": flt(outstanding_amount),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"bill_no": bill_no
|
||||
})
|
||||
|
||||
|
@ -589,9 +589,9 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center)
|
||||
|
||||
self.assertEqual(pe.cost_center, si.cost_center)
|
||||
self.assertEqual(expected_account_balance, account_balance)
|
||||
self.assertEqual(expected_party_balance, party_balance)
|
||||
self.assertEqual(expected_party_account_balance, party_account_balance)
|
||||
self.assertEqual(flt(expected_account_balance), account_balance)
|
||||
self.assertEqual(flt(expected_party_balance), party_balance)
|
||||
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
|
||||
|
||||
def create_payment_terms_template():
|
||||
|
||||
|
@ -14,7 +14,8 @@
|
||||
"total_amount",
|
||||
"outstanding_amount",
|
||||
"allocated_amount",
|
||||
"exchange_rate"
|
||||
"exchange_rate",
|
||||
"exchange_gain_loss"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -90,12 +91,19 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-10 11:25:47.144392",
|
||||
"modified": "2021-04-21 13:30:11.605388",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
@ -207,10 +207,9 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
billing_email = frappe.db.sql("""
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
|
||||
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
|
||||
c.is_billing_contact=1 \
|
||||
order by c.creation desc""")
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent
|
||||
WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1
|
||||
order by c.creation desc""", customer_name)
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
|
@ -451,6 +451,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
@ -953,6 +953,109 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
def test_gain_loss_with_advance_entry(self):
|
||||
unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice")
|
||||
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
|
||||
pay = frappe.get_doc({
|
||||
'doctype': 'Payment Entry',
|
||||
'company': '_Test Company',
|
||||
'payment_type': 'Pay',
|
||||
'party_type': 'Supplier',
|
||||
'party': '_Test Supplier USD',
|
||||
'paid_to': '_Test Payable USD - _TC',
|
||||
'paid_from': 'Cash - _TC',
|
||||
'paid_amount': 70000,
|
||||
'target_exchange_rate': 70,
|
||||
'received_amount': 1000,
|
||||
})
|
||||
pay.insert()
|
||||
pay.submit()
|
||||
|
||||
pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||
conversion_rate=75, rate=500, do_not_save=1, qty=1)
|
||||
pi.cost_center = "_Test Cost Center - _TC"
|
||||
pi.advances = []
|
||||
pi.append("advances", {
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": pay.name,
|
||||
"advance_amount": 1000,
|
||||
"remarks": pay.remarks,
|
||||
"allocated_amount": 500,
|
||||
"ref_exchange_rate": 70
|
||||
})
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||
["_Test Payable USD - _TC", -40000.0],
|
||||
["Exchange Gain/Loss - _TC", 2500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s
|
||||
group by account order by account asc""", (pi.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||
conversion_rate=73, rate=500, do_not_save=1, qty=1)
|
||||
pi_2.cost_center = "_Test Cost Center - _TC"
|
||||
pi_2.advances = []
|
||||
pi_2.append("advances", {
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": pay.name,
|
||||
"advance_amount": 500,
|
||||
"remarks": pay.remarks,
|
||||
"allocated_amount": 500,
|
||||
"ref_exchange_rate": 70
|
||||
})
|
||||
pi_2.save()
|
||||
pi_2.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||
["_Test Payable USD - _TC", -38000.0],
|
||||
["Exchange Gain/Loss - _TC", 1500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s
|
||||
group by account order by account asc""", (pi_2.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Payable USD - _TC", 70000.0],
|
||||
["Cash - _TC", -70000.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||
where voucher_no=%s and is_cancelled=0
|
||||
group by account order by account asc""", (pay.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||
|
||||
pi.reload()
|
||||
pi.cancel()
|
||||
|
||||
pi_2.reload()
|
||||
pi_2.cancel()
|
||||
|
||||
pay.reload()
|
||||
pay.cancel()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
|
||||
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
|
@ -1,235 +1,127 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-03-08 15:36:46",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2013-03-08 15:36:46",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"remarks",
|
||||
"reference_row",
|
||||
"col_break1",
|
||||
"advance_amount",
|
||||
"allocated_amount",
|
||||
"exchange_gain_loss",
|
||||
"ref_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "180px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"print_width": "180px",
|
||||
"read_only": 1,
|
||||
"width": "180px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Row",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "80px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Row",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Date",
|
||||
"print_hide": 1,
|
||||
"print_width": "80px",
|
||||
"read_only": 1,
|
||||
"width": "80px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Advance Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Advance Amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "100px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated Amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "100px",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-08-26 02:30:54.407138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-20 16:26:53.820530",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -840,6 +840,7 @@ class SalesInvoice(SellingController):
|
||||
self.make_customer_gl_entry(gl_entries)
|
||||
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
@ -1,235 +1,128 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:41",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2013-02-22 01:27:41",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"remarks",
|
||||
"reference_row",
|
||||
"col_break1",
|
||||
"advance_amount",
|
||||
"allocated_amount",
|
||||
"exchange_gain_loss",
|
||||
"ref_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Type",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "250px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "journal_voucher",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"print_width": "250px",
|
||||
"read_only": 1,
|
||||
"width": "250px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Remarks",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Row",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"fieldname": "reference_row",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Row",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "jv_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"width": "120px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Advance amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "advance_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Advance amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "advance_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "120px",
|
||||
"read_only": 1,
|
||||
"width": "120px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "120px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated amount",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "allocated_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "party_account_currency",
|
||||
"print_width": "120px",
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"fieldname": "exchange_gain_loss",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Exchange Gain/Loss",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ref_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reference Exchange Rate",
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2016-08-26 02:36:10.718057",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-04 20:25:49.832052",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Advance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -397,6 +397,7 @@ def get_chart_data(filters, columns, data):
|
||||
{'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
|
||||
{'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
|
||||
]
|
||||
}
|
||||
},
|
||||
'type' : 'bar'
|
||||
}
|
||||
|
||||
|
@ -48,13 +48,12 @@ def validate_filters(filters, account_details):
|
||||
|
||||
if not filters.get("from_date") and not filters.get("to_date"):
|
||||
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||
|
||||
for account in filters.account:
|
||||
if not account_details.get(account):
|
||||
frappe.throw(_("Account {0} does not exists").format(account))
|
||||
|
||||
if filters.get('account'):
|
||||
filters.account = frappe.parse_json(filters.get('account'))
|
||||
for account in filters.account:
|
||||
if not account_details.get(account):
|
||||
frappe.throw(_("Account {0} does not exists").format(account))
|
||||
|
||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')
|
||||
and account_details[filters.account].is_group == 0):
|
||||
|
@ -472,7 +472,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
||||
"total_amount": d.grand_total,
|
||||
"outstanding_amount": d.outstanding_amount,
|
||||
"allocated_amount": d.allocated_amount,
|
||||
"exchange_rate": d.exchange_rate
|
||||
"exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||
"exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation
|
||||
}
|
||||
|
||||
if d.voucher_detail_no:
|
||||
@ -498,12 +499,15 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
||||
payment_entry.set_amounts()
|
||||
|
||||
if d.difference_amount and d.difference_account:
|
||||
payment_entry.set_gain_or_loss(account_details={
|
||||
account_details = {
|
||||
'account': d.difference_account,
|
||||
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
||||
payment_entry.company, "cost_center"),
|
||||
'amount': d.difference_amount
|
||||
})
|
||||
payment_entry.company, "cost_center")
|
||||
}
|
||||
if d.difference_amount:
|
||||
account_details['amount'] = d.difference_amount
|
||||
|
||||
payment_entry.set_gain_or_loss(account_details=account_details)
|
||||
|
||||
if not do_not_save:
|
||||
payment_entry.save(ignore_permissions=True)
|
||||
|
@ -124,6 +124,8 @@ class AccountsController(TransactionBase):
|
||||
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
|
||||
self.set_advances()
|
||||
|
||||
self.set_advance_gain_or_loss()
|
||||
|
||||
if self.is_return:
|
||||
self.validate_qty()
|
||||
else:
|
||||
@ -584,15 +586,18 @@ class AccountsController(TransactionBase):
|
||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||
advance_allocated += flt(allocated_amount)
|
||||
|
||||
self.append("advances", {
|
||||
advance_row = {
|
||||
"doctype": self.doctype + " Advance",
|
||||
"reference_type": d.reference_type,
|
||||
"reference_name": d.reference_name,
|
||||
"reference_row": d.reference_row,
|
||||
"remarks": d.remarks,
|
||||
"advance_amount": flt(d.amount),
|
||||
"allocated_amount": allocated_amount
|
||||
})
|
||||
"allocated_amount": allocated_amount,
|
||||
"ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry
|
||||
}
|
||||
|
||||
self.append("advances", advance_row)
|
||||
|
||||
def get_advance_entries(self, include_unallocated=True):
|
||||
if self.doctype == "Sales Invoice":
|
||||
@ -650,6 +655,66 @@ class AccountsController(TransactionBase):
|
||||
"Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
|
||||
.format(d.reference_name, d.against_order))
|
||||
|
||||
def set_advance_gain_or_loss(self):
|
||||
if not self.get("advances"):
|
||||
return
|
||||
|
||||
for d in self.get("advances"):
|
||||
advance_exchange_rate = d.ref_exchange_rate
|
||||
if (d.allocated_amount and self.conversion_rate != 1
|
||||
and self.conversion_rate != advance_exchange_rate):
|
||||
|
||||
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
|
||||
base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount
|
||||
difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
|
||||
|
||||
d.exchange_gain_loss = difference
|
||||
|
||||
def make_exchange_gain_loss_gl_entries(self, gl_entries):
|
||||
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
||||
for d in self.get("advances"):
|
||||
if d.exchange_gain_loss:
|
||||
party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
|
||||
party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
|
||||
party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
|
||||
|
||||
gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
|
||||
|
||||
# for purchase
|
||||
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": gain_loss_account,
|
||||
"account_currency": account_currency,
|
||||
"against": party,
|
||||
dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project
|
||||
}, item=d)
|
||||
)
|
||||
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": party_account,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"against": gain_loss_account,
|
||||
dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate),
|
||||
dr_or_cr: abs(d.exchange_gain_loss),
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
def update_against_document_in_jv(self):
|
||||
"""
|
||||
Links invoice and advance voucher:
|
||||
@ -690,7 +755,9 @@ class AccountsController(TransactionBase):
|
||||
if self.party_account_currency != self.company_currency else 1),
|
||||
'grand_total': (self.base_grand_total
|
||||
if self.party_account_currency == self.company_currency else self.grand_total),
|
||||
'outstanding_amount': self.outstanding_amount
|
||||
'outstanding_amount': self.outstanding_amount,
|
||||
'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'),
|
||||
'exchange_gain_loss': flt(d.get('exchange_gain_loss'))
|
||||
})
|
||||
lst.append(args)
|
||||
|
||||
@ -1289,6 +1356,8 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
|
||||
currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
|
||||
payment_type = "Receive" if party_type == "Customer" else "Pay"
|
||||
exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
|
||||
|
||||
payment_entries_against_order, unallocated_payment_entries = [], []
|
||||
limit_cond = "limit %s" % limit if limit else ""
|
||||
|
||||
@ -1305,27 +1374,28 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
|
||||
"Payment Entry" as reference_type, t1.name as reference_name,
|
||||
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
|
||||
t2.reference_name as against_order, t1.posting_date,
|
||||
t1.{0} as currency
|
||||
t1.{0} as currency, t1.{4} as exchange_rate
|
||||
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
|
||||
where
|
||||
t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
|
||||
and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
|
||||
and t2.reference_doctype = %s {2}
|
||||
order by t1.posting_date {3}
|
||||
""".format(currency_field, party_account_field, reference_condition, limit_cond),
|
||||
""".format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field),
|
||||
[party_account, payment_type, party_type, party,
|
||||
order_doctype] + order_list, as_dict=1)
|
||||
|
||||
if include_unallocated:
|
||||
unallocated_payment_entries = frappe.db.sql("""
|
||||
select "Payment Entry" as reference_type, name as reference_name,
|
||||
remarks, unallocated_amount as amount
|
||||
remarks, unallocated_amount as amount, {2} as exchange_rate
|
||||
from `tabPayment Entry`
|
||||
where
|
||||
{0} = %s and party_type = %s and party = %s and payment_type = %s
|
||||
and docstatus = 1 and unallocated_amount > 0
|
||||
order by posting_date {1}
|
||||
""".format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1)
|
||||
""".format(party_account_field, limit_cond, exchange_rate_field),
|
||||
(party_account, party_type, party, payment_type), as_dict=1)
|
||||
|
||||
return list(payment_entries_against_order) + list(unallocated_payment_entries)
|
||||
|
||||
|
@ -356,42 +356,68 @@ class StockController(AccountsController):
|
||||
}, update_modified)
|
||||
|
||||
def validate_inspection(self):
|
||||
'''Checks if quality inspection is set for Items that require inspection.
|
||||
On submit, throw an exception'''
|
||||
inspection_required_fieldname = None
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
inspection_required_fieldname = "inspection_required_before_purchase"
|
||||
elif self.doctype in ["Delivery Note", "Sales Invoice"]:
|
||||
inspection_required_fieldname = "inspection_required_before_delivery"
|
||||
"""Checks if quality inspection is set/ is valid for Items that require inspection."""
|
||||
inspection_fieldname_map = {
|
||||
"Purchase Receipt": "inspection_required_before_purchase",
|
||||
"Purchase Invoice": "inspection_required_before_purchase",
|
||||
"Sales Invoice": "inspection_required_before_delivery",
|
||||
"Delivery Note": "inspection_required_before_delivery"
|
||||
}
|
||||
inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)
|
||||
|
||||
# return if inspection is not required on document level
|
||||
if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
|
||||
(self.doctype == "Stock Entry" and not self.inspection_required) or
|
||||
(self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
|
||||
return
|
||||
|
||||
for d in self.get('items'):
|
||||
qa_required = False
|
||||
if (inspection_required_fieldname and not d.quality_inspection and
|
||||
frappe.db.get_value("Item", d.item_code, inspection_required_fieldname)):
|
||||
qa_required = True
|
||||
elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse:
|
||||
qa_required = True
|
||||
if self.docstatus == 1 and d.quality_inspection:
|
||||
qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection)
|
||||
if qa_doc.docstatus == 0:
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
|
||||
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)
|
||||
for row in self.get('items'):
|
||||
qi_required = False
|
||||
if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
|
||||
qi_required = True
|
||||
elif self.doctype == "Stock Entry" and row.t_warehouse:
|
||||
qi_required = True # inward stock needs inspection
|
||||
|
||||
if qa_doc.status != 'Accepted':
|
||||
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
|
||||
.format(d.idx, d.item_code), QualityInspectionRejectedError)
|
||||
elif qa_required :
|
||||
action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted
|
||||
if self.docstatus==1 and action == 'Stop':
|
||||
frappe.throw(_("Quality Inspection required for Item {0} to submit").format(frappe.bold(d.item_code)),
|
||||
exc=QualityInspectionRequiredError)
|
||||
else:
|
||||
frappe.msgprint(_("Create Quality Inspection for Item {0}").format(frappe.bold(d.item_code)))
|
||||
if qi_required: # validate row only if inspection is required on item level
|
||||
self.validate_qi_presence(row)
|
||||
if self.docstatus == 1:
|
||||
self.validate_qi_submission(row)
|
||||
self.validate_qi_rejection(row)
|
||||
|
||||
def validate_qi_presence(self, row):
|
||||
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
|
||||
if not row.quality_inspection:
|
||||
msg = f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}"
|
||||
if self.docstatus == 1:
|
||||
frappe.throw(_(msg), title=_("Inspection Required"), exc=QualityInspectionRequiredError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), title=_("Inspection Required"), indicator="blue")
|
||||
|
||||
def validate_qi_submission(self, row):
|
||||
"""Check if QI is submitted on row level, during submission"""
|
||||
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
|
||||
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
|
||||
|
||||
if not qa_docstatus == 1:
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), alert=True, indicator="orange")
|
||||
|
||||
def validate_qi_rejection(self, row):
|
||||
"""Check if QI is rejected on row level, during submission"""
|
||||
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_rejected")
|
||||
qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")
|
||||
|
||||
if qa_status == "Rejected":
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
|
||||
else:
|
||||
frappe.msgprint(_(msg), alert=True, indicator="orange")
|
||||
|
||||
def update_blanket_order(self):
|
||||
blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))
|
||||
|
@ -33,7 +33,8 @@ frappe.ui.form.on('Training Event', {
|
||||
frm.set_query("employee", "employees", function () {
|
||||
return {
|
||||
filters: {
|
||||
name: ["NOT IN", emp]
|
||||
name: ["NOT IN", emp],
|
||||
status: "Active"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -28,7 +28,8 @@ frappe.ui.form.on('Loan', {
|
||||
frm.set_query("loan_type", function () {
|
||||
return {
|
||||
"filters": {
|
||||
"docstatus": 1
|
||||
"docstatus": 1,
|
||||
"company": frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -14,6 +14,13 @@ frappe.ui.form.on('Loan Application', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields");
|
||||
frm.trigger("add_toolbar_buttons");
|
||||
frm.set_query('loan_type', () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
repayment_method: function(frm) {
|
||||
frm.doc.repayment_amount = frm.doc.repayment_periods = ""
|
||||
|
@ -117,7 +117,6 @@ class PayrollEntry(Document):
|
||||
Creates salary slip for selected employees if already not created
|
||||
"""
|
||||
self.check_permission('write')
|
||||
self.created = 1
|
||||
employees = [emp.employee for emp in self.employees]
|
||||
if employees:
|
||||
args = frappe._dict({
|
||||
@ -686,7 +685,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
if filters.start_date and filters.end_date:
|
||||
employee_list = get_employee_list(filters)
|
||||
emp = filters.get('employees')
|
||||
emp = filters.get('employees') or []
|
||||
include_employees = [employee.employee for employee in employee_list if employee.employee not in emp]
|
||||
filters.pop('start_date')
|
||||
filters.pop('end_date')
|
||||
|
@ -1091,6 +1091,7 @@ class SalarySlip(TransactionBase):
|
||||
"applicant": self.employee,
|
||||
"docstatus": 1,
|
||||
"repay_from_salary": 1,
|
||||
"company": self.company
|
||||
})
|
||||
|
||||
def make_loan_repayment_entry(self):
|
||||
|
@ -482,14 +482,19 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
|
||||
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
|
||||
|
||||
|
||||
employee = frappe.db.get_value("Employee", {"user_id": user})
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
|
||||
employee = frappe.db.get_value("Employee",
|
||||
{
|
||||
"user_id": user
|
||||
},
|
||||
["name", "company", "employee_name"],
|
||||
as_dict=True)
|
||||
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee.name, company=employee.company)
|
||||
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
|
||||
|
||||
if not salary_slip_name:
|
||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
|
||||
salary_slip.employee_name = frappe.get_value("Employee",
|
||||
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
|
||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee.name)
|
||||
salary_slip.employee_name = employee.employee_name
|
||||
salary_slip.payroll_frequency = payroll_frequency
|
||||
salary_slip.posting_date = nowdate()
|
||||
salary_slip.insert()
|
||||
|
@ -119,26 +119,25 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
||||
if test_tax:
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
|
||||
|
||||
if not frappe.db.exists('Salary Structure', salary_structure):
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": company or erpnext.get_default_company(),
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
details.update(other_details)
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
salary_structure_doc.insert()
|
||||
if not dont_submit:
|
||||
salary_structure_doc.submit()
|
||||
if frappe.db.exists("Salary Structure", salary_structure):
|
||||
frappe.db.delete("Salary Structure", salary_structure)
|
||||
|
||||
else:
|
||||
salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure)
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": company or erpnext.get_default_company(),
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
details.update(other_details)
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
salary_structure_doc.insert()
|
||||
if not dont_submit:
|
||||
salary_structure_doc.submit()
|
||||
|
||||
filters = {'employee':employee, 'docstatus': 1}
|
||||
if not from_date and payroll_period:
|
||||
|
@ -1,6 +1,8 @@
|
||||
erpnext.setup_einvoice_actions = (doctype) => {
|
||||
frappe.ui.form.on(doctype, {
|
||||
async refresh(frm) {
|
||||
if (frm.doc.docstatus == 2) return;
|
||||
|
||||
const res = await frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
|
||||
args: { doc: frm.doc }
|
||||
@ -111,7 +113,7 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
||||
|
||||
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
|
||||
const action = () => {
|
||||
let message = __('Cancellation of e-way bill is currently not supported. ');
|
||||
let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
|
||||
message += '<br><br>';
|
||||
message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
|
||||
|
||||
|
@ -42,7 +42,10 @@ def validate_eligibility(doc):
|
||||
invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
|
||||
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
|
||||
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
|
||||
no_taxes_applied = not doc.get('taxes')
|
||||
|
||||
# if export invoice, then taxes can be empty
|
||||
# invoice can only be ineligible if no taxes applied and is not an export invoice
|
||||
no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas'
|
||||
has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
|
||||
|
||||
if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
|
||||
@ -188,9 +191,10 @@ def get_item_list(invoice):
|
||||
|
||||
item.qty = abs(item.qty)
|
||||
|
||||
item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty)
|
||||
item.gross_amount = abs(item.taxable_value) + item.discount_amount
|
||||
item.unit_rate = abs(item.taxable_value / item.qty)
|
||||
item.gross_amount = abs(item.taxable_value)
|
||||
item.taxable_value = abs(item.taxable_value)
|
||||
item.discount_amount = 0
|
||||
|
||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||
|
@ -472,12 +472,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total;
|
||||
this.render_grand_total(grand_total);
|
||||
|
||||
const taxes = frm.doc.taxes.map(t => {
|
||||
return {
|
||||
description: t.description, rate: t.rate
|
||||
};
|
||||
});
|
||||
this.render_taxes(frm.doc.total_taxes_and_charges, taxes);
|
||||
this.render_taxes(frm.doc.taxes);
|
||||
}
|
||||
|
||||
render_net_total(value) {
|
||||
@ -502,14 +497,14 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
);
|
||||
}
|
||||
|
||||
render_taxes(value, taxes) {
|
||||
render_taxes(taxes) {
|
||||
if (taxes.length) {
|
||||
const currency = this.events.get_frm().doc.currency;
|
||||
const taxes_html = taxes.map(t => {
|
||||
const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
|
||||
return `<div class="tax-row">
|
||||
<div class="tax-label">${description}</div>
|
||||
<div class="tax-value">${format_currency(value, currency)}</div>
|
||||
<div class="tax-value">${format_currency(t.tax_amount_after_discount_amount, currency)}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
this.$totals_section.find('.taxes-container').css('display', 'flex').html(taxes_html);
|
||||
|
@ -56,7 +56,7 @@ erpnext.PointOfSale.Payment = class {
|
||||
);
|
||||
let df_events = {
|
||||
onchange: function() {
|
||||
frm.set_value(this.df.fieldname, this.value);
|
||||
frm.set_value(this.df.fieldname, this.get_value());
|
||||
}
|
||||
};
|
||||
if (df.fieldtype == "Button") {
|
||||
|
@ -395,7 +395,7 @@ class Company(NestedSet):
|
||||
|
||||
@frappe.whitelist()
|
||||
def enqueue_replace_abbr(company, old, new):
|
||||
kwargs = dict(company=company, old=old, new=new)
|
||||
kwargs = dict(queue="long", company=company, old=old, new=new)
|
||||
frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
|
||||
|
||||
|
||||
|
@ -193,7 +193,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2021-01-07 11:10:09.149170",
|
||||
"modified": "2021-07-08 16:22:01.343105",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Batch",
|
||||
@ -217,5 +217,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "batch_id"
|
||||
"title_field": "batch_id",
|
||||
"track_changes": 1
|
||||
}
|
@ -93,7 +93,7 @@ frappe.ui.form.on("Item", {
|
||||
|
||||
erpnext.item.edit_prices_button(frm);
|
||||
erpnext.item.toggle_attributes(frm);
|
||||
|
||||
|
||||
if (!frm.doc.is_fixed_asset) {
|
||||
erpnext.item.make_dashboard(frm);
|
||||
}
|
||||
@ -381,7 +381,8 @@ $.extend(erpnext.item, {
|
||||
// Show Stock Levels only if is_stock_item
|
||||
if (frm.doc.is_stock_item) {
|
||||
frappe.require('assets/js/item-dashboard.min.js', function() {
|
||||
const section = frm.dashboard.add_section('', __("Stock Levels"));
|
||||
frm.dashboard.parent.find('.stock-levels').remove();
|
||||
const section = frm.dashboard.add_section('', __("Stock Levels"), 'stock-levels');
|
||||
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
|
||||
parent: section,
|
||||
item_code: frm.doc.name,
|
||||
|
@ -41,7 +41,7 @@ class LandedCostVoucher(Document):
|
||||
|
||||
def validate(self):
|
||||
self.check_mandatory()
|
||||
self.validate_purchase_receipts()
|
||||
self.validate_receipt_documents()
|
||||
init_landed_taxes_and_totals(self)
|
||||
self.set_total_taxes_and_charges()
|
||||
if not self.get("items"):
|
||||
@ -56,14 +56,23 @@ class LandedCostVoucher(Document):
|
||||
frappe.throw(_("Please enter Receipt Document"))
|
||||
|
||||
|
||||
def validate_purchase_receipts(self):
|
||||
def validate_receipt_documents(self):
|
||||
receipt_documents = []
|
||||
|
||||
for d in self.get("purchase_receipts"):
|
||||
if frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus") != 1:
|
||||
frappe.throw(_("Receipt document must be submitted"))
|
||||
else:
|
||||
receipt_documents.append(d.receipt_document)
|
||||
docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus")
|
||||
if docstatus != 1:
|
||||
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
||||
frappe.throw(_(msg), title=_("Invalid Document"))
|
||||
|
||||
if d.receipt_document_type == "Purchase Invoice":
|
||||
update_stock = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "update_stock")
|
||||
if not update_stock:
|
||||
msg = _("Row {0}: Purchase Invoice {1} has no stock impact.").format(d.idx, frappe.bold(d.receipt_document))
|
||||
msg += "<br>" + _("Please create Landed Cost Vouchers against Invoices that have 'Update Stock' enabled.")
|
||||
frappe.throw(msg, title=_("Incorrect Invoice"))
|
||||
|
||||
receipt_documents.append(d.receipt_document)
|
||||
|
||||
for item in self.get("items"):
|
||||
if not item.receipt_document:
|
||||
|
@ -14,7 +14,7 @@ from erpnext.controllers.stock_controller import (
|
||||
)
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
# test_records = frappe.get_test_records('Quality Inspection')
|
||||
|
||||
@ -159,6 +159,47 @@ class TestQualityInspection(unittest.TestCase):
|
||||
frappe.delete_doc("Quality Inspection", qi)
|
||||
dn.delete()
|
||||
|
||||
def test_rejected_qi_validation(self):
|
||||
"""Test if rejected QI blocks Stock Entry as per Stock Settings."""
|
||||
se = make_stock_entry(
|
||||
item_code="_Test Item with QA",
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=1,
|
||||
basic_rate=100,
|
||||
inspection_required=True,
|
||||
do_not_submit=True
|
||||
)
|
||||
|
||||
readings = [
|
||||
{
|
||||
"specification": "Iron Content",
|
||||
"min_value": 0.1,
|
||||
"max_value": 0.9,
|
||||
"reading_1": "0.4"
|
||||
}
|
||||
]
|
||||
|
||||
qa = create_quality_inspection(
|
||||
reference_type="Stock Entry",
|
||||
reference_name=se.name,
|
||||
readings=readings,
|
||||
status="Rejected"
|
||||
)
|
||||
|
||||
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
|
||||
se.reload()
|
||||
self.assertRaises(QualityInspectionRejectedError, se.submit) # when blocked in Stock settings, block rejected QI
|
||||
|
||||
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Warn")
|
||||
se.reload()
|
||||
se.submit() # when allowed in Stock settings, allow rejected QI
|
||||
|
||||
# teardown
|
||||
qa.reload()
|
||||
qa.cancel()
|
||||
se.reload()
|
||||
se.cancel()
|
||||
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
|
||||
|
||||
def create_quality_inspection(**args):
|
||||
args = frappe._dict(args)
|
||||
@ -175,12 +216,11 @@ def create_quality_inspection(**args):
|
||||
if not args.readings:
|
||||
create_quality_inspection_parameter("Size")
|
||||
readings = {"specification": "Size", "min_value": 0, "max_value": 10}
|
||||
if args.status == "Rejected":
|
||||
readings["reading_1"] = "12" # status is auto set in child on save
|
||||
else:
|
||||
readings = args.readings
|
||||
|
||||
if args.status == "Rejected":
|
||||
readings["reading_1"] = "12" # status is auto set in child on save
|
||||
|
||||
if isinstance(readings, list):
|
||||
for entry in readings:
|
||||
create_quality_inspection_parameter(entry["specification"])
|
||||
|
@ -529,7 +529,7 @@ class StockEntry(StockController):
|
||||
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
|
||||
|
||||
# Get raw materials cost from BOM if multiple material consumption entries
|
||||
if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
|
||||
if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
|
||||
bom_items = self.get_bom_raw_materials(finished_item_qty)
|
||||
outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
|
||||
|
||||
|
@ -45,6 +45,8 @@ def make_stock_entry(**args):
|
||||
s.posting_date = args.posting_date
|
||||
if args.posting_time:
|
||||
s.posting_time = args.posting_time
|
||||
if args.inspection_required:
|
||||
s.inspection_required = args.inspection_required
|
||||
|
||||
# map names
|
||||
if args.from_warehouse:
|
||||
|
@ -307,6 +307,7 @@
|
||||
"fieldname": "quality_inspection",
|
||||
"fieldtype": "Link",
|
||||
"label": "Quality Inspection",
|
||||
"no_copy": 1,
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
@ -548,7 +549,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-22 20:08:23.799715",
|
||||
"modified": "2021-06-21 16:03:18.834880",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
@ -178,3 +178,4 @@ def on_doctype_update():
|
||||
|
||||
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
|
||||
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
|
||||
frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"])
|
||||
|
@ -23,7 +23,10 @@
|
||||
"allow_negative_stock",
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
"quality_inspection_settings_section",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"column_break_21",
|
||||
"action_if_quality_inspection_is_rejected",
|
||||
"section_break_7",
|
||||
"automatically_set_serial_nos_based_on_fifo",
|
||||
"set_qty_in_transactions_based_on_serial_no_input",
|
||||
@ -264,6 +267,22 @@
|
||||
{
|
||||
"fieldname": "column_break_31",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "quality_inspection_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quality Inspection Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Stop",
|
||||
"fieldname": "action_if_quality_inspection_is_rejected",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action If Quality Inspection Is Rejected",
|
||||
"options": "Stop\nWarn"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -271,7 +290,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-30 17:27:42.709231",
|
||||
"modified": "2021-07-10 16:17:42.159829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
@ -6,13 +6,14 @@ import frappe
|
||||
import erpnext
|
||||
import copy
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, cstr, now, get_link_to_form
|
||||
from frappe.utils import cint, flt, cstr, now, get_link_to_form, getdate
|
||||
from frappe.model.meta import get_field_precision
|
||||
from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
|
||||
from erpnext.stock.utils import get_bin
|
||||
import json
|
||||
from six import iteritems
|
||||
|
||||
|
||||
# future reposting
|
||||
class NegativeStockError(frappe.ValidationError): pass
|
||||
class SerialNoExistsInFutureTransaction(frappe.ValidationError):
|
||||
@ -130,7 +131,13 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
|
||||
if not args and voucher_type and voucher_no:
|
||||
args = get_args_for_voucher(voucher_type, voucher_no)
|
||||
|
||||
distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args]
|
||||
distinct_item_warehouses = {}
|
||||
for i, d in enumerate(args):
|
||||
distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
|
||||
"reposting_status": False,
|
||||
"sle": d,
|
||||
"args_idx": i
|
||||
}))
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
@ -139,13 +146,21 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
|
||||
"warehouse": args[i].warehouse,
|
||||
"posting_date": args[i].posting_date,
|
||||
"posting_time": args[i].posting_time,
|
||||
"creation": args[i].get("creation")
|
||||
"creation": args[i].get("creation"),
|
||||
"distinct_item_warehouses": distinct_item_warehouses
|
||||
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||
|
||||
for item_wh, new_sle in iteritems(obj.new_items):
|
||||
if item_wh not in distinct_item_warehouses:
|
||||
args.append(new_sle)
|
||||
distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
|
||||
|
||||
if obj.new_items_found:
|
||||
for item_wh, data in iteritems(distinct_item_warehouses):
|
||||
if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status):
|
||||
data.args_idx = len(args)
|
||||
args.append(data.sle)
|
||||
elif data.sle_changed and not data.reposting_status:
|
||||
args[data.args_idx] = data.sle
|
||||
|
||||
data.sle_changed = False
|
||||
i += 1
|
||||
|
||||
def get_args_for_voucher(voucher_type, voucher_no):
|
||||
@ -186,11 +201,12 @@ class update_entries_after(object):
|
||||
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
|
||||
self.get_precision()
|
||||
self.valuation_method = get_valuation_method(self.item_code)
|
||||
self.new_items = {}
|
||||
|
||||
self.new_items_found = False
|
||||
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
|
||||
|
||||
self.data = frappe._dict()
|
||||
self.initialize_previous_data(self.args)
|
||||
|
||||
self.build()
|
||||
|
||||
def get_precision(self):
|
||||
@ -296,11 +312,29 @@ class update_entries_after(object):
|
||||
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
|
||||
return entries_to_fix
|
||||
elif dependant_sle.item_code != self.item_code:
|
||||
if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
|
||||
self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
|
||||
self.update_distinct_item_warehouses(dependant_sle)
|
||||
return entries_to_fix
|
||||
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
||||
return entries_to_fix
|
||||
else:
|
||||
return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
|
||||
|
||||
def update_distinct_item_warehouses(self, dependant_sle):
|
||||
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
||||
val = frappe._dict({
|
||||
"sle": dependant_sle
|
||||
})
|
||||
if key not in self.distinct_item_warehouses:
|
||||
self.distinct_item_warehouses[key] = val
|
||||
self.new_items_found = True
|
||||
else:
|
||||
existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
|
||||
if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
|
||||
val.sle_changed = True
|
||||
self.distinct_item_warehouses[key] = val
|
||||
self.new_items_found = True
|
||||
|
||||
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
|
||||
self.initialize_previous_data(dependant_sle)
|
||||
|
||||
args = self.data[dependant_sle.warehouse].previous_sle \
|
||||
@ -393,6 +427,7 @@ class update_entries_after(object):
|
||||
rate = 0
|
||||
# Material Transfer, Repack, Manufacturing
|
||||
if sle.voucher_type == "Stock Entry":
|
||||
self.recalculate_amounts_in_stock_entry(sle.voucher_no)
|
||||
rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate")
|
||||
# Sales and Purchase Return
|
||||
elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
|
||||
@ -442,7 +477,11 @@ class update_entries_after(object):
|
||||
frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
|
||||
|
||||
# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
|
||||
stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
|
||||
if not sle.dependant_sle_voucher_detail_no:
|
||||
self.recalculate_amounts_in_stock_entry(sle.voucher_no)
|
||||
|
||||
def recalculate_amounts_in_stock_entry(self, voucher_no):
|
||||
stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True)
|
||||
stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
|
||||
stock_entry.db_update()
|
||||
for d in stock_entry.items:
|
||||
|
Loading…
x
Reference in New Issue
Block a user