Merge branch 'develop' into add-blank-for-status-issue-reports
This commit is contained in:
commit
dc73a9995d
@ -15,12 +15,14 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
||||
test_dependencies = ["Item", "Cost Center"]
|
||||
|
||||
class TestBankTransaction(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_pos_profile()
|
||||
add_transactions()
|
||||
add_vouchers()
|
||||
|
||||
def tearDown(self):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
for bt in frappe.get_all("Bank Transaction"):
|
||||
doc = frappe.get_doc("Bank Transaction", bt.name)
|
||||
doc.cancel()
|
||||
@ -33,9 +35,6 @@ class TestBankTransaction(unittest.TestCase):
|
||||
# Delete POS Profile
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
frappe.flags.test_bank_transactions_created = False
|
||||
frappe.flags.test_payments_created = False
|
||||
|
||||
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
||||
def test_linked_payments(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
|
||||
@ -44,8 +43,8 @@ class TestBankTransaction(unittest.TestCase):
|
||||
|
||||
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
|
||||
def test_reconcile(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
|
||||
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
|
||||
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,
|
||||
@ -62,7 +61,6 @@ class TestBankTransaction(unittest.TestCase):
|
||||
def test_debit_credit_output(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
|
||||
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
|
||||
print(linked_payments)
|
||||
self.assertTrue(linked_payments[0][3])
|
||||
|
||||
# Check error if already reconciled
|
||||
@ -116,10 +114,6 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||
pass
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
create_bank_account()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
@ -172,14 +166,8 @@ def add_transactions():
|
||||
}).insert()
|
||||
doc.submit()
|
||||
|
||||
frappe.flags.test_bank_transactions_created = True
|
||||
|
||||
def add_vouchers():
|
||||
if frappe.flags.test_payments_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
try:
|
||||
frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
@ -272,13 +260,6 @@ def add_vouchers():
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Fayva Oct 18"
|
||||
pe.reference_date = "2018-10-29"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
mode_of_payment = frappe.get_doc({
|
||||
"doctype": "Mode of Payment",
|
||||
"name": "Cash"
|
||||
@ -291,14 +272,12 @@ def add_vouchers():
|
||||
})
|
||||
mode_of_payment.save()
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
|
||||
si.is_pos = 1
|
||||
si.append("payments", {
|
||||
"mode_of_payment": "Cash",
|
||||
"account": "_Test Bank - _TC",
|
||||
"amount": 109080
|
||||
})
|
||||
si.save()
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
frappe.flags.test_payments_created = True
|
||||
|
@ -293,6 +293,11 @@ def validate_accounts(file_name):
|
||||
accounts_dict = {}
|
||||
for account in accounts:
|
||||
accounts_dict.setdefault(account["account_name"], account)
|
||||
if not hasattr(account, "parent_account"):
|
||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||
msg += "<br><br>"
|
||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||
frappe.throw(msg, title=_("Parent Account Missing"))
|
||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||
|
||||
|
@ -290,4 +290,8 @@ def rename_temporarily_named_docs(doctype):
|
||||
oldname = doc.name
|
||||
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
||||
newname = doc.name
|
||||
frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname))
|
||||
frappe.db.sql(
|
||||
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
|
||||
(newname, oldname),
|
||||
auto_commit=True
|
||||
)
|
||||
|
@ -637,13 +637,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
let to_field = fields[key][1];
|
||||
|
||||
if (filters[from_field] && !filters[to_field]) {
|
||||
frappe.throw(__("Error: {0} is mandatory field",
|
||||
[to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")])
|
||||
);
|
||||
} else if (filters[from_field] && filters[from_field] > filters[to_field]) {
|
||||
frappe.throw(__("{0}: {1} must be less than {2}",
|
||||
[key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")])
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -692,6 +692,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
c.total_amount = d.invoice_amount;
|
||||
c.outstanding_amount = d.outstanding_amount;
|
||||
c.bill_no = d.bill_no;
|
||||
c.payment_term = d.payment_term;
|
||||
c.allocated_amount = d.allocated_amount;
|
||||
|
||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
|
||||
if(flt(d.outstanding_amount) > 0)
|
||||
@ -774,12 +776,15 @@ frappe.ui.form.on('Payment Entry', {
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
if(paid_amount > total_negative_outstanding) {
|
||||
if(total_negative_outstanding == 0) {
|
||||
frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice",
|
||||
[frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]));
|
||||
frappe.msgprint(
|
||||
__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
|
||||
);
|
||||
return false
|
||||
} else {
|
||||
frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]));
|
||||
frappe.msgprint(
|
||||
__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -791,10 +796,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
$.each(frm.doc.references || [], function(i, row) {
|
||||
row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
if(frappe.flags.allocate_payment_amount != 0){
|
||||
if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if(row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
if (frappe.flags.allocate_payment_amount == 0) {
|
||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
row.allocated_amount = 0;
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if (row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
row.allocated_amount = allocated_positive_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
@ -802,9 +810,11 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
if(Math.abs(row.outstanding_amount) >= allocated_negative_outstanding)
|
||||
if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
|
||||
row.allocated_amount = -1*allocated_negative_outstanding;
|
||||
else row.allocated_amount = row.outstanding_amount;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
};
|
||||
|
||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
||||
}
|
||||
|
@ -333,33 +333,50 @@ class PaymentEntry(AccountsController):
|
||||
invoice_payment_amount_map = {}
|
||||
invoice_paid_amount_map = {}
|
||||
|
||||
for reference in self.get('references'):
|
||||
if reference.payment_term and reference.reference_name:
|
||||
key = (reference.payment_term, reference.reference_name)
|
||||
for ref in self.get('references'):
|
||||
if ref.payment_term and ref.reference_name:
|
||||
key = (ref.payment_term, ref.reference_name)
|
||||
invoice_payment_amount_map.setdefault(key, 0.0)
|
||||
invoice_payment_amount_map[key] += reference.allocated_amount
|
||||
invoice_payment_amount_map[key] += ref.allocated_amount
|
||||
|
||||
if not invoice_paid_amount_map.get(key):
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term'])
|
||||
payment_schedule = frappe.get_all(
|
||||
'Payment Schedule',
|
||||
filters={'parent': ref.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
|
||||
)
|
||||
for term in payment_schedule:
|
||||
invoice_key = (term.payment_term, reference.reference_name)
|
||||
invoice_key = (term.payment_term, ref.reference_name)
|
||||
invoice_paid_amount_map.setdefault(invoice_key, {})
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
|
||||
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
|
||||
|
||||
for key, allocated_amount in iteritems(invoice_payment_amount_map):
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
|
||||
|
||||
for key, amount in iteritems(invoice_payment_amount_map):
|
||||
if cancel:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` - %s,
|
||||
discounted_amount = `discounted_amount` - %s,
|
||||
outstanding = `outstanding` + %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
else:
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
|
||||
if amount > outstanding:
|
||||
if allocated_amount > outstanding:
|
||||
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
|
||||
|
||||
if amount and outstanding:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
if allocated_amount and outstanding:
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` + %s,
|
||||
discounted_amount = `discounted_amount` + %s,
|
||||
outstanding = `outstanding` - %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@ -708,6 +725,8 @@ def get_outstanding_reference_documents(args):
|
||||
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
||||
args.get("party_account"), filters=args, condition=condition)
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency:
|
||||
@ -735,6 +754,46 @@ def get_outstanding_reference_documents(args):
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
|
||||
payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
|
||||
if payment_term_template:
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
|
||||
if allocate_payment_based_on_payment_terms:
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
|
||||
'due_date': d.due_date,
|
||||
'currency': d.currency,
|
||||
'voucher_no': d.voucher_no,
|
||||
'voucher_type': d.voucher_type,
|
||||
'posting_date': d.posting_date,
|
||||
'invoice_amount': flt(d.invoice_amount),
|
||||
'outstanding_amount': flt(d.outstanding_amount),
|
||||
'payment_amount': payment_term.payment_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': payment_term.outstanding
|
||||
}))
|
||||
|
||||
if invoice_ref_based_on_payment_terms:
|
||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
||||
voucher_no = outstanding_invoices[idx]['voucher_no']
|
||||
voucher_type = outstanding_invoices[idx]['voucher_type']
|
||||
|
||||
frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
|
||||
voucher_type, voucher_no, len(ref)), alert=True)
|
||||
|
||||
outstanding_invoices.pop(idx - 1)
|
||||
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
return outstanding_invoices
|
||||
|
||||
def get_orders_to_be_billed(posting_date, party_type, party,
|
||||
company, party_account_currency, company_currency, cost_center=None, filters=None):
|
||||
if party_type == "Customer":
|
||||
@ -1091,6 +1150,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||
|
||||
paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = payment_type
|
||||
pe.company = doc.company
|
||||
@ -1160,11 +1221,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
|
||||
pe.setup_party_account_field()
|
||||
pe.set_missing_values()
|
||||
|
||||
if party_account and bank:
|
||||
if dt == "Employee Advance":
|
||||
reference_doc = doc
|
||||
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||
pe.set_amounts()
|
||||
if discount_amount:
|
||||
pe.set_gain_or_loss(account_details={
|
||||
'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
|
||||
'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
|
||||
'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
|
||||
})
|
||||
pe.set_difference_amount()
|
||||
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
@ -1285,6 +1355,33 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||
return paid_amount, received_amount
|
||||
|
||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||
total_discount = 0
|
||||
if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
|
||||
for term in doc.payment_schedule:
|
||||
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
|
||||
if term.discount_type == 'Percentage':
|
||||
discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
|
||||
else:
|
||||
discount_amount = term.discount
|
||||
|
||||
discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
|
||||
|
||||
if doc.doctype == 'Sales Invoice':
|
||||
paid_amount -= discount_amount
|
||||
received_amount -= discount_amount_in_foreign_currency
|
||||
else:
|
||||
received_amount -= discount_amount
|
||||
paid_amount -= discount_amount_in_foreign_currency
|
||||
|
||||
total_discount += discount_amount
|
||||
|
||||
if total_discount:
|
||||
money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
|
||||
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
|
||||
|
||||
return paid_amount, received_amount, total_discount
|
||||
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
|
@ -193,6 +193,34 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
|
||||
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
|
||||
|
||||
def test_payment_entry_against_payment_terms_with_discount(self):
|
||||
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
create_payment_terms_template_with_discount()
|
||||
si.payment_terms_template = 'Test Discount Template'
|
||||
|
||||
frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
|
||||
|
||||
si.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 18
|
||||
})
|
||||
si.save()
|
||||
|
||||
si.submit()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
|
||||
pe.submit()
|
||||
si.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
|
||||
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
|
||||
self.assertEqual(si.payment_schedule[0].outstanding, 0)
|
||||
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
|
||||
|
||||
|
||||
def test_payment_against_purchase_invoice_to_check_status(self):
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
|
||||
@ -591,6 +619,26 @@ def create_payment_terms_template():
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_terms_template_with_discount():
|
||||
|
||||
create_payment_term('30 Credit Days with 10% Discount')
|
||||
|
||||
if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
|
||||
payment_term_template = frappe.get_doc({
|
||||
'doctype': 'Payment Terms Template',
|
||||
'template_name': 'Test Discount Template',
|
||||
'allocate_payment_based_on_payment_terms': 1,
|
||||
'terms': [{
|
||||
'doctype': 'Payment Terms Template Detail',
|
||||
'payment_term': '30 Credit Days with 10% Discount',
|
||||
'invoice_portion': 100,
|
||||
'credit_days_based_on': 'Day(s) after invoice date',
|
||||
'credit_days': 2,
|
||||
'discount': 10,
|
||||
'discount_validity_based_on': 'Day(s) after invoice date',
|
||||
'discount_validity': 1
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_term(name):
|
||||
if not frappe.db.exists('Payment Term', name):
|
||||
|
@ -58,7 +58,7 @@
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Amount",
|
||||
"label": "Grand Total",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -92,9 +92,10 @@
|
||||
"options": "Payment Term"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 12:07:19.362539",
|
||||
"modified": "2021-02-10 11:25:47.144392",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
@ -6,11 +6,23 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_15",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"due_date",
|
||||
"invoice_portion",
|
||||
"payment_amount",
|
||||
"mode_of_payment",
|
||||
"column_break_5",
|
||||
"invoice_portion",
|
||||
"section_break_6",
|
||||
"discount_type",
|
||||
"discount_date",
|
||||
"column_break_9",
|
||||
"discount",
|
||||
"section_break_9",
|
||||
"payment_amount",
|
||||
"discounted_amount",
|
||||
"column_break_3",
|
||||
"outstanding",
|
||||
"paid_amount"
|
||||
],
|
||||
"fields": [
|
||||
@ -25,6 +37,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
@ -62,14 +75,82 @@
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"depends_on": "paid_amount",
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "discounted_amount",
|
||||
"fieldname": "discounted_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discounted Amount",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_amount",
|
||||
"fieldname": "outstanding",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Outstanding",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Discount Date",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 17:58:24.729526",
|
||||
"modified": "2021-02-15 21:03:12.540546",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
|
@ -1,2 +1,22 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Payment Term', {
|
||||
onload(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount_type(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
set_dynamic_description(frm) {
|
||||
if (frm.doc.discount) {
|
||||
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
||||
if (frm.doc.discount_type == 'Amount') {
|
||||
description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
|
||||
}
|
||||
frm.set_df_property("discount", "description", description);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,386 +1,166 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term_name",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity",
|
||||
"section_break_6",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Payment Term Name",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"description": "Provide the invoice portion in percent",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"label": "Invoice Portion (%)"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"description": "Give number of days according to prior selection",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Days"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"bold": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-10-14 10:47:32.830478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-02-15 20:30:56.256403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -3,11 +3,6 @@
|
||||
|
||||
frappe.ui.form.on('Payment Terms Template', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("payment_term", "description", "description");
|
||||
frm.add_fetch("payment_term", "invoice_portion", "invoice_portion");
|
||||
frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on");
|
||||
frm.add_fetch("payment_term", "credit_days", "credit_days");
|
||||
frm.add_fetch("payment_term", "credit_months", "credit_months");
|
||||
frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment");
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -13,7 +13,6 @@ from frappe import _
|
||||
class PaymentTermsTemplate(Document):
|
||||
def validate(self):
|
||||
self.validate_invoice_portion()
|
||||
self.validate_credit_days()
|
||||
self.check_duplicate_terms()
|
||||
|
||||
def validate_invoice_portion(self):
|
||||
@ -24,11 +23,6 @@ class PaymentTermsTemplate(Document):
|
||||
if flt(total_portion, 2) != 100.00:
|
||||
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
|
||||
|
||||
def validate_credit_days(self):
|
||||
for term in self.terms:
|
||||
if cint(term.credit_days) < 0:
|
||||
frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red')
|
||||
|
||||
def check_duplicate_terms(self):
|
||||
terms = []
|
||||
for term in self.terms:
|
||||
|
@ -1,278 +1,164 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_13",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.invoice_portion",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Portion (%)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.due_date_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fetch_from": "payment_term.credit_days",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Credit Days",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fetch_from": "payment_term.credit_months",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fetch_from": "payment_term.mode_of_payment",
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:55.143025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-24 11:56:12.410807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -12,6 +12,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
class TestPOSInvoice(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def tearDown(self):
|
||||
if frappe.session.user != "Administrator":
|
||||
frappe.set_user("Administrator")
|
||||
|
@ -43,7 +43,7 @@
|
||||
}
|
||||
],
|
||||
"grand_total": 0,
|
||||
"naming_series": "_T-BILL",
|
||||
"naming_series": "T-PINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
@ -167,7 +167,7 @@
|
||||
}
|
||||
],
|
||||
"grand_total": 0,
|
||||
"naming_series": "_T-Purchase Invoice-",
|
||||
"naming_series": "T-PINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
|
@ -1952,13 +1952,12 @@
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
{
|
||||
"custom": 1,
|
||||
"group": "Reference",
|
||||
"link_doctype": "POS Invoice",
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-01 15:42:26.261540",
|
||||
"modified": "2021-03-31 15:42:26.261540",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -31,7 +31,7 @@
|
||||
"base_grand_total": 561.8,
|
||||
"grand_total": 561.8,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"base_net_total": 500.0,
|
||||
"taxes": [
|
||||
{
|
||||
@ -104,7 +104,7 @@
|
||||
"base_grand_total": 630.0,
|
||||
"grand_total": 630.0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"base_net_total": 500.0,
|
||||
"taxes": [
|
||||
{
|
||||
@ -175,7 +175,7 @@
|
||||
],
|
||||
"grand_total": 0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
@ -301,7 +301,7 @@
|
||||
],
|
||||
"grand_total": 0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
|
@ -2115,6 +2115,7 @@ def create_sales_invoice(**args):
|
||||
si.return_against = args.return_against
|
||||
si.currency=args.currency or "INR"
|
||||
si.conversion_rate = args.conversion_rate or 1
|
||||
si.naming_series = args.naming_series or "T-SINV-"
|
||||
|
||||
si.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -14,10 +14,15 @@ test_records = frappe.get_test_records('Tax Rule')
|
||||
from six import iteritems
|
||||
|
||||
class TestTaxRule(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def tearDown(self):
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def test_conflict(self):
|
||||
|
@ -364,7 +364,7 @@ class ReceivablePayableReport(object):
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@ -395,13 +395,13 @@ class ReceivablePayableReport(object):
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"paid": d.paid_amount,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
}))
|
||||
|
||||
if d.paid_amount:
|
||||
row['paid'] -= d.paid_amount
|
||||
row['paid'] -= d.paid_amount + d.discounted_amount
|
||||
|
||||
def allocate_closing_to_term(self, row, term, key):
|
||||
if row[key]:
|
||||
|
@ -253,6 +253,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# Must be called after updating ordered qty in Material Request
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
|
||||
@ -367,7 +368,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"Purchase Order": {
|
||||
"doctype": "Purchase Receipt",
|
||||
"field_map": {
|
||||
"per_billed": "per_billed",
|
||||
"supplier_warehouse":"supplier_warehouse"
|
||||
},
|
||||
"validation": {
|
||||
|
@ -90,6 +90,50 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0)
|
||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
|
||||
|
||||
def test_update_remove_child_linked_to_mr(self):
|
||||
"""Test impact on linked PO and MR on deleting/updating row."""
|
||||
mr = make_material_request(qty=10)
|
||||
po = make_purchase_order(mr.name)
|
||||
po.supplier = "_Test Supplier"
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
first_item_of_po = po.get("items")[0]
|
||||
existing_ordered_qty = get_ordered_qty() # 10
|
||||
existing_requested_qty = get_requested_qty() # 0
|
||||
|
||||
# decrease ordered qty by 3 (10 -> 7) and add item
|
||||
trans_item = json.dumps([
|
||||
{
|
||||
'item_code': first_item_of_po.item_code,
|
||||
'rate': first_item_of_po.rate,
|
||||
'qty': 7,
|
||||
'docname': first_item_of_po.name
|
||||
},
|
||||
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
|
||||
])
|
||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||
mr.reload()
|
||||
|
||||
# requested qty increases as ordered qty decreases
|
||||
self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
|
||||
self.assertEqual(mr.items[0].ordered_qty, 7)
|
||||
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
|
||||
|
||||
# delete first item linked to Material Request
|
||||
trans_item = json.dumps([
|
||||
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
|
||||
])
|
||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||
mr.reload()
|
||||
|
||||
# requested qty increases as ordered qty is 0 (deleted row)
|
||||
self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
|
||||
self.assertEqual(mr.items[0].ordered_qty, 0)
|
||||
|
||||
# ordered qty decreases as ordered qty is 0 (deleted row)
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
|
||||
|
||||
def test_update_child(self):
|
||||
mr = make_material_request(qty=10)
|
||||
@ -120,7 +164,6 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEqual(po.get("items")[0].amount, 1400)
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
||||
|
||||
|
||||
def test_update_child_adding_new_item(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
po.items[0].qty = 4
|
||||
@ -129,6 +172,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
pr = make_pr_against_po(po.name, 2)
|
||||
|
||||
po.load_from_db()
|
||||
existing_ordered_qty = get_ordered_qty()
|
||||
first_item_of_po = po.get("items")[0]
|
||||
|
||||
trans_item = json.dumps([
|
||||
@ -145,7 +189,8 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
po.reload()
|
||||
self.assertEquals(len(po.get('items')), 2)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
|
||||
# ordered qty should increase on row addition
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
|
||||
|
||||
def test_update_child_removing_item(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
@ -156,6 +201,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
|
||||
po.reload()
|
||||
first_item_of_po = po.get("items")[0]
|
||||
existing_ordered_qty = get_ordered_qty()
|
||||
# add an item
|
||||
trans_item = json.dumps([
|
||||
{
|
||||
@ -168,6 +214,10 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||
|
||||
po.reload()
|
||||
|
||||
# ordered qty should increase on row addition
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
|
||||
|
||||
# check if can remove received item
|
||||
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
|
||||
@ -187,6 +237,9 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEquals(len(po.get('items')), 1)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
|
||||
# ordered qty should decrease (back to initial) on row deletion
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty)
|
||||
|
||||
def test_update_child_perm(self):
|
||||
po = create_purchase_order(item_code= "_Test Item", qty=4)
|
||||
|
||||
@ -230,11 +283,13 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
|
||||
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
||||
|
||||
new_item_with_tax.append("taxes", {
|
||||
"item_tax_template": "Test Update Items Template - _TC",
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
new_item_with_tax.save()
|
||||
if not frappe.db.exists("Item Tax",
|
||||
{"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}):
|
||||
new_item_with_tax.append("taxes", {
|
||||
"item_tax_template": "Test Update Items Template - _TC",
|
||||
"valid_from": nowdate()
|
||||
})
|
||||
new_item_with_tax.save()
|
||||
|
||||
tax_template = "_Test Account Excise Duty @ 10 - _TC"
|
||||
item = "_Test Item Home Desktop 100"
|
||||
|
@ -9,9 +9,7 @@ import unittest
|
||||
class TestSupplierScorecard(unittest.TestCase):
|
||||
|
||||
def test_create_scorecard(self):
|
||||
delete_test_scorecards()
|
||||
my_doc = make_supplier_scorecard()
|
||||
doc = my_doc.insert()
|
||||
doc = make_supplier_scorecard().insert()
|
||||
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
|
||||
|
||||
def test_criteria_weight(self):
|
||||
@ -121,7 +119,8 @@ valid_scorecard = [
|
||||
{
|
||||
"weight":100.0,
|
||||
"doctype":"Supplier Scorecard Scoring Criteria",
|
||||
"criteria_name":"Delivery"
|
||||
"criteria_name":"Delivery",
|
||||
"formula": "100"
|
||||
}
|
||||
],
|
||||
"supplier":"_Test Supplier",
|
||||
|
@ -923,7 +923,8 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
@ -1238,18 +1239,24 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat
|
||||
term_details.description = term.description
|
||||
term_details.invoice_portion = term.invoice_portion
|
||||
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
||||
term_details.discount_type = term.discount_type
|
||||
term_details.discount = term.discount
|
||||
# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
|
||||
term_details.outstanding = term_details.payment_amount
|
||||
term_details.mode_of_payment = term.mode_of_payment
|
||||
|
||||
if bill_date:
|
||||
term_details.due_date = get_due_date(term, bill_date)
|
||||
term_details.discount_date = get_discount_date(term, bill_date)
|
||||
elif posting_date:
|
||||
term_details.due_date = get_due_date(term, posting_date)
|
||||
term_details.discount_date = get_discount_date(term, posting_date)
|
||||
|
||||
if getdate(term_details.due_date) < getdate(posting_date):
|
||||
term_details.due_date = posting_date
|
||||
term_details.mode_of_payment = term.mode_of_payment
|
||||
|
||||
return term_details
|
||||
|
||||
|
||||
def get_due_date(term, posting_date=None, bill_date=None):
|
||||
due_date = None
|
||||
date = bill_date or posting_date
|
||||
@ -1261,6 +1268,16 @@ def get_due_date(term, posting_date=None, bill_date=None):
|
||||
due_date = add_months(get_last_day(date), term.credit_months)
|
||||
return due_date
|
||||
|
||||
def get_discount_date(term, posting_date=None, bill_date=None):
|
||||
discount_validity = None
|
||||
date = bill_date or posting_date
|
||||
if term.discount_validity_based_on == "Day(s) after invoice date":
|
||||
discount_validity = add_days(date, term.discount_validity)
|
||||
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
|
||||
discount_validity = add_days(get_last_day(date), term.discount_validity)
|
||||
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
|
||||
discount_validity = add_months(get_last_day(date), term.discount_validity)
|
||||
return discount_validity
|
||||
|
||||
def get_supplier_block_status(party_name):
|
||||
"""
|
||||
@ -1319,25 +1336,63 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
|
||||
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
|
||||
item = frappe.get_doc("Item", trans_item.get('item_code'))
|
||||
|
||||
for field in ("item_code", "item_name", "description", "item_group"):
|
||||
child_item.update({field: item.get(field)})
|
||||
child_item.update({field: item.get(field)})
|
||||
|
||||
date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
|
||||
child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
|
||||
child_item.stock_uom = item.stock_uom
|
||||
child_item.uom = trans_item.get("uom") or item.stock_uom
|
||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
||||
|
||||
if child_doctype == "Purchase Order Item":
|
||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||
# Initialized value will update in parent validation
|
||||
child_item.base_rate = 1
|
||||
child_item.base_amount = 1
|
||||
if child_doctype == "Sales Order Item":
|
||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||
if not child_item.warehouse:
|
||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||
.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
|
||||
|
||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||
add_taxes_from_tax_template(child_item, p_doc)
|
||||
return child_item
|
||||
|
||||
def validate_child_on_delete(row, parent):
|
||||
"""Check if partially transacted item (row) is being deleted."""
|
||||
if parent.doctype == "Sales Order":
|
||||
if flt(row.delivered_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
|
||||
if flt(row.work_order_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
|
||||
if flt(row.ordered_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
|
||||
|
||||
if parent.doctype == "Purchase Order" and flt(row.received_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
|
||||
|
||||
if flt(row.billed_amt):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
|
||||
|
||||
def update_bin_on_delete(row, doctype):
|
||||
"""Update bin for deleted item (row)."""
|
||||
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty
|
||||
qty_dict = {}
|
||||
|
||||
if doctype == "Sales Order":
|
||||
qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
|
||||
else:
|
||||
if row.material_request_item:
|
||||
qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
|
||||
|
||||
qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
|
||||
|
||||
update_bin_qty(row.item_code, row.warehouse, qty_dict)
|
||||
|
||||
def validate_and_delete_children(parent, data):
|
||||
deleted_children = []
|
||||
updated_item_names = [d.get("docname") for d in data]
|
||||
@ -1346,23 +1401,17 @@ def validate_and_delete_children(parent, data):
|
||||
deleted_children.append(item)
|
||||
|
||||
for d in deleted_children:
|
||||
if parent.doctype == "Sales Order":
|
||||
if flt(d.delivered_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
|
||||
if flt(d.work_order_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
|
||||
if flt(d.ordered_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
|
||||
|
||||
if parent.doctype == "Purchase Order" and flt(d.received_qty):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
|
||||
|
||||
if flt(d.billed_amt):
|
||||
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
|
||||
|
||||
validate_child_on_delete(d, parent)
|
||||
d.cancel()
|
||||
d.delete()
|
||||
|
||||
# need to update ordered qty in Material Request first
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
parent.update_prevdoc_status()
|
||||
|
||||
for d in deleted_children:
|
||||
update_bin_on_delete(d, parent.doctype)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
|
||||
def check_doc_permissions(doc, perm_type='create'):
|
||||
|
@ -113,10 +113,10 @@ class calculate_taxes_and_totals(object):
|
||||
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
|
||||
if flt(item.rate_with_margin) > 0:
|
||||
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||
if not item.discount_amount:
|
||||
item.discount_amount = item.rate_with_margin - item.rate
|
||||
elif not item.discount_percentage:
|
||||
if item.discount_amount and not item.discount_percentage:
|
||||
item.rate -= item.discount_amount
|
||||
else:
|
||||
item.discount_amount = item.rate_with_margin - item.rate
|
||||
elif flt(item.price_list_rate) > 0:
|
||||
item.discount_amount = item.price_list_rate - item.rate
|
||||
elif flt(item.price_list_rate) > 0 and not item.discount_amount:
|
||||
@ -808,4 +808,4 @@ class init_landed_taxes_and_totals(object):
|
||||
def set_amounts_in_company_currency(self):
|
||||
for d in self.doc.get(self.tax_field):
|
||||
d.amount = flt(d.amount, d.precision("amount"))
|
||||
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
||||
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
||||
|
@ -23,14 +23,9 @@ class TestPlaidSettings(unittest.TestCase):
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
for ba in frappe.get_all("Bank Account"):
|
||||
frappe.get_doc("Bank Account", ba.name).delete()
|
||||
|
||||
for at in frappe.get_all("Bank Account Type"):
|
||||
frappe.get_doc("Bank Account Type", at.name).delete()
|
||||
|
||||
for ast in frappe.get_all("Bank Account Subtype"):
|
||||
frappe.get_doc("Bank Account Subtype", ast.name).delete()
|
||||
for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"):
|
||||
for d in frappe.get_all(doctype):
|
||||
frappe.delete_doc(doctype, d.name, force=True)
|
||||
|
||||
def test_plaid_disabled(self):
|
||||
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
||||
|
@ -81,15 +81,8 @@ class TestInpatientMedicationOrder(unittest.TestCase):
|
||||
self.ip_record.reload()
|
||||
discharge_patient(self.ip_record)
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Entry'):
|
||||
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Order'):
|
||||
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]:
|
||||
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
||||
|
||||
def create_dosage_form():
|
||||
if not frappe.db.exists('Dosage Form', 'Tablet'):
|
||||
|
@ -260,7 +260,10 @@ doc_events = {
|
||||
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
|
||||
],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.validate_document_name"
|
||||
]
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
"validate": [
|
||||
@ -282,9 +285,6 @@ doc_events = {
|
||||
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
||||
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
||||
},
|
||||
('Sales Invoice', 'Purchase Invoice'): {
|
||||
'validate': ['erpnext.regional.india.utils.validate_document_name']
|
||||
},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, add_days, getdate, cint
|
||||
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||
@ -40,7 +40,12 @@ class CompensatoryLeaveRequest(Document):
|
||||
def validate_holidays(self):
|
||||
holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
|
||||
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
|
||||
frappe.throw(_("Compensatory leave request days not in valid holidays"))
|
||||
if date_diff(self.work_end_date, self.work_from_date):
|
||||
msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
|
||||
else:
|
||||
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
|
||||
|
||||
frappe.throw(msg)
|
||||
|
||||
def on_submit(self):
|
||||
company = frappe.db.get_value("Employee", self.employee, "company")
|
||||
@ -63,7 +68,7 @@ class CompensatoryLeaveRequest(Document):
|
||||
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
||||
self.leave_allocation=leave_allocation.name
|
||||
else:
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
|
||||
|
||||
def on_cancel(self):
|
||||
if self.leave_allocation:
|
||||
|
@ -80,6 +80,7 @@ class Employee(NestedSet):
|
||||
self.update_user()
|
||||
self.update_user_permissions()
|
||||
self.reset_employee_emails_cache()
|
||||
self.update_approver_role()
|
||||
|
||||
def update_user_permissions(self):
|
||||
if not self.create_user_permission: return
|
||||
@ -145,6 +146,17 @@ class Employee(NestedSet):
|
||||
|
||||
user.save()
|
||||
|
||||
def update_approver_role(self):
|
||||
if self.leave_approver:
|
||||
user = frappe.get_doc("User", self.leave_approver)
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Leave Approver")
|
||||
|
||||
if self.expense_approver:
|
||||
user = frappe.get_doc("User", self.expense_approver)
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Expense Approver")
|
||||
|
||||
def validate_date(self):
|
||||
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
|
||||
throw(_("Date of Birth cannot be greater than today."))
|
||||
@ -503,7 +515,7 @@ def has_user_permission_for_employee(user_name, employee_name):
|
||||
})
|
||||
|
||||
def has_upload_permission(doc, ptype='read', user=None):
|
||||
if not user:
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
|
||||
return True
|
||||
|
@ -181,7 +181,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -201,7 +200,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-25 12:01:55.980721",
|
||||
"modified": "2021-03-31 14:42:47.321368",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Advance",
|
||||
|
@ -6,7 +6,7 @@ import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
@ -53,6 +53,9 @@ class ExpenseClaim(AccountsController):
|
||||
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
||||
self.status = 'Rejected'
|
||||
|
||||
def on_update(self):
|
||||
share_doc_with_approver(self, self.expense_approver)
|
||||
|
||||
def set_payable_account(self):
|
||||
if not self.payable_account and not self.is_paid:
|
||||
self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
|
||||
|
@ -95,12 +95,12 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
def test_rejected_expense_claim(self):
|
||||
payable_account = get_payable_account(company_name)
|
||||
expense_claim = frappe.get_doc({
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-00001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-00001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
|
||||
})
|
||||
expense_claim.submit()
|
||||
|
||||
@ -110,6 +110,34 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
|
||||
self.assertEquals(len(gl_entry), 0)
|
||||
|
||||
def test_expense_approver_perms(self):
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# check doc shared
|
||||
payable_account = get_payable_account("_Test Company")
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
|
||||
expense_claim.expense_approver = user
|
||||
expense_claim.save()
|
||||
self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
|
||||
|
||||
# check shared doc revoked
|
||||
expense_claim.reload()
|
||||
expense_claim.expense_approver = "test@example.com"
|
||||
expense_claim.save()
|
||||
self.assertTrue(expense_claim.name not in frappe.share.get_shared("Expense Claim", user))
|
||||
|
||||
expense_claim.reload()
|
||||
expense_claim.expense_approver = user
|
||||
expense_claim.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
expense_claim.reload()
|
||||
expense_claim.status = "Approved"
|
||||
expense_claim.submit()
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
|
||||
def get_payable_account(company):
|
||||
return frappe.get_cached_value('Company', company, 'default_payable_account')
|
||||
|
||||
@ -133,21 +161,21 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco
|
||||
|
||||
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
|
||||
expense_claim = {
|
||||
"doctype": "Expense Claim",
|
||||
"employee": employee,
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Approved",
|
||||
"company": company,
|
||||
'currency': currency,
|
||||
"expenses": [{
|
||||
"doctype": "Expense Claim",
|
||||
"employee": employee,
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Approved",
|
||||
"company": company,
|
||||
"currency": currency,
|
||||
"expenses": [{
|
||||
"expense_type": "Travel",
|
||||
"default_account": account,
|
||||
"currency": currency,
|
||||
"amount": amount,
|
||||
"sanctioned_amount": sanctioned_amount,
|
||||
"cost_center": cost_center
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
if taxes:
|
||||
expense_claim.update(taxes)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
|
||||
comma_or, get_fullname, add_days, nowdate, get_datetime_str
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
|
||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
||||
@ -43,6 +43,8 @@ class LeaveApplication(Document):
|
||||
if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
|
||||
self.notify_leave_approver()
|
||||
|
||||
share_doc_with_approver(self, self.leave_approver)
|
||||
|
||||
def on_submit(self):
|
||||
if self.status == "Open":
|
||||
frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
|
||||
@ -417,6 +419,7 @@ class LeaveApplication(Document):
|
||||
))
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
|
||||
def get_allocation_expiry(employee, leave_type, to_date, from_date):
|
||||
''' Returns expiry of carry forward allocation in leave ledger entry '''
|
||||
expiry = frappe.get_all("Leave Ledger Entry",
|
||||
|
@ -11,6 +11,7 @@ from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
|
||||
|
||||
@ -56,6 +57,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
set_leave_approver()
|
||||
frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
@ -230,8 +232,9 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
def test_optional_leave(self):
|
||||
leave_period = get_leave_period()
|
||||
today = nowdate()
|
||||
from datetime import date
|
||||
holiday_list = 'Test Holiday List for Optional Holiday'
|
||||
optional_leave_date = add_days(today, 7)
|
||||
|
||||
if not frappe.db.exists('Holiday List', holiday_list):
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Holiday List',
|
||||
@ -239,7 +242,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
from_date = add_months(today, -6),
|
||||
to_date = add_months(today, 6),
|
||||
holidays = [
|
||||
dict(holiday_date = today, description = 'Test')
|
||||
dict(holiday_date = optional_leave_date, description = 'Test')
|
||||
]
|
||||
)).insert()
|
||||
employee = get_employee()
|
||||
@ -255,7 +258,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
allocate_leaves(employee, leave_period, leave_type, 10)
|
||||
|
||||
date = add_days(today, - 1)
|
||||
date = add_days(today, 6)
|
||||
|
||||
leave_application = frappe.get_doc(dict(
|
||||
doctype = 'Leave Application',
|
||||
@ -270,14 +273,14 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
# can only apply on optional holidays
|
||||
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
|
||||
|
||||
leave_application.from_date = today
|
||||
leave_application.to_date = today
|
||||
leave_application.from_date = optional_leave_date
|
||||
leave_application.to_date = optional_leave_date
|
||||
leave_application.status = "Approved"
|
||||
leave_application.insert()
|
||||
leave_application.submit()
|
||||
|
||||
# check leave balance is reduced
|
||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9)
|
||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9)
|
||||
|
||||
def test_leaves_allowed(self):
|
||||
employee = get_employee()
|
||||
@ -341,7 +344,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
status = "Approved"
|
||||
))
|
||||
|
||||
self.assertRaises(frappe.ValidationError, leave_application.insert)
|
||||
@ -363,7 +366,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
status = "Approved"
|
||||
))
|
||||
|
||||
self.assertTrue(leave_application.insert())
|
||||
@ -393,7 +396,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
to_date = add_days(date, 4),
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
status = "Approved"
|
||||
))
|
||||
|
||||
self.assertRaises(frappe.ValidationError, leave_application.insert)
|
||||
@ -508,7 +511,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
description = "_Test Reason",
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
status = "Approved"
|
||||
))
|
||||
leave_application.submit()
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
|
||||
@ -540,7 +543,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
description = "_Test Reason",
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
status = "Approved"
|
||||
))
|
||||
leave_application.submit()
|
||||
|
||||
@ -565,6 +568,48 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
|
||||
|
||||
def test_leave_approver_perms(self):
|
||||
employee = get_employee()
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# set approver for employee
|
||||
employee.reload()
|
||||
employee.leave_approver = user
|
||||
employee.save()
|
||||
self.assertTrue("Leave Approver" in frappe.get_roles(user))
|
||||
|
||||
make_allocation_record(employee.name)
|
||||
|
||||
application = self.get_application(_test_records[0])
|
||||
application.from_date = '2018-01-01'
|
||||
application.to_date = '2018-01-03'
|
||||
application.leave_approver = user
|
||||
application.insert()
|
||||
self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
|
||||
|
||||
# check shared doc revoked
|
||||
application.reload()
|
||||
application.leave_approver = "test@example.com"
|
||||
application.save()
|
||||
self.assertTrue(application.name not in frappe.share.get_shared("Leave Application", user))
|
||||
|
||||
application.reload()
|
||||
application.leave_approver = user
|
||||
application.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
application.reload()
|
||||
application.status = "Approved"
|
||||
application.submit()
|
||||
|
||||
# unset leave approver
|
||||
frappe.set_user("Administrator")
|
||||
employee.reload()
|
||||
employee.leave_approver = ""
|
||||
employee.save()
|
||||
|
||||
|
||||
def create_carry_forwarded_allocation(employee, leave_type):
|
||||
# initial leave allocation
|
||||
leave_allocation = create_leave_allocation(
|
||||
|
@ -130,7 +130,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -155,7 +154,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-25 11:56:06.777241",
|
||||
"modified": "2021-03-31 14:45:27.948207",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Encashment",
|
||||
|
@ -34,8 +34,8 @@ def validate_leave_allocation_against_leave_application(ledger):
|
||||
""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
|
||||
|
||||
if leave_application_records:
|
||||
frappe.throw(_("Leave allocation %s is linked with leave application %s"
|
||||
% (ledger.transaction_name, ', '.join(leave_application_records))))
|
||||
frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
|
||||
ledger.transaction_name, ', '.join(leave_application_records)))
|
||||
|
||||
def create_leave_ledger_entry(ref_doc, args, submit=True):
|
||||
ledger = frappe._dict(
|
||||
@ -52,7 +52,9 @@ def create_leave_ledger_entry(ref_doc, args, submit=True):
|
||||
ledger.update(args)
|
||||
|
||||
if submit:
|
||||
frappe.get_doc(ledger).submit()
|
||||
doc = frappe.get_doc(ledger)
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.submit()
|
||||
else:
|
||||
delete_ledger_entry(ledger)
|
||||
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import formatdate, getdate
|
||||
from erpnext.hr.utils import share_doc_with_approver
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
|
||||
@ -17,6 +18,9 @@ class ShiftRequest(Document):
|
||||
self.validate_approver()
|
||||
self.validate_default_shift()
|
||||
|
||||
def on_update(self):
|
||||
share_doc_with_approver(self, self.approver)
|
||||
|
||||
def on_submit(self):
|
||||
if self.status not in ["Approved", "Rejected"]:
|
||||
frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted"))
|
||||
@ -29,6 +33,7 @@ class ShiftRequest(Document):
|
||||
if self.to_date:
|
||||
assignment_doc.end_date = self.to_date
|
||||
assignment_doc.shift_request = self.name
|
||||
assignment_doc.flags.ignore_permissions = 1
|
||||
assignment_doc.insert()
|
||||
assignment_doc.submit()
|
||||
|
||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate, add_days
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
test_dependencies = ["Shift Type"]
|
||||
|
||||
@ -19,19 +20,8 @@ class TestShiftRequest(unittest.TestCase):
|
||||
set_shift_approver(department)
|
||||
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
|
||||
|
||||
shift_request = frappe.get_doc({
|
||||
"doctype": "Shift Request",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"employee_name": "_Test Employee",
|
||||
"from_date": nowdate(),
|
||||
"to_date": add_days(nowdate(), 10),
|
||||
"approver": approver,
|
||||
"status": "Approved"
|
||||
})
|
||||
shift_request.insert()
|
||||
shift_request.submit()
|
||||
shift_request = make_shift_request(approver)
|
||||
|
||||
shift_assignments = frappe.db.sql('''
|
||||
SELECT shift_request, employee
|
||||
FROM `tabShift Assignment`
|
||||
@ -44,8 +34,65 @@ class TestShiftRequest(unittest.TestCase):
|
||||
shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
|
||||
self.assertEqual(shift_assignment_doc.docstatus, 2)
|
||||
|
||||
def test_shift_request_approver_perms(self):
|
||||
employee = frappe.get_doc("Employee", "_T-Employee-00001")
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# set approver for employee
|
||||
employee.reload()
|
||||
employee.shift_request_approver = user
|
||||
employee.save()
|
||||
|
||||
shift_request = make_shift_request(user, do_not_submit=True)
|
||||
self.assertTrue(shift_request.name in frappe.share.get_shared("Shift Request", user))
|
||||
|
||||
# check shared doc revoked
|
||||
shift_request.reload()
|
||||
department = frappe.get_value("Employee", "_T-Employee-00001", "department")
|
||||
set_shift_approver(department)
|
||||
department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
|
||||
shift_request.approver = department_approver
|
||||
shift_request.save()
|
||||
self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
|
||||
|
||||
shift_request.reload()
|
||||
shift_request.approver = user
|
||||
shift_request.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
shift_request.reload()
|
||||
shift_request.status = "Approved"
|
||||
shift_request.submit()
|
||||
|
||||
# unset approver
|
||||
frappe.set_user("Administrator")
|
||||
employee.reload()
|
||||
employee.shift_request_approver = ""
|
||||
employee.save()
|
||||
|
||||
|
||||
def set_shift_approver(department):
|
||||
department_doc = frappe.get_doc("Department", department)
|
||||
department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
|
||||
department_doc.save()
|
||||
department_doc.reload()
|
||||
|
||||
def make_shift_request(approver, do_not_submit=0):
|
||||
shift_request = frappe.get_doc({
|
||||
"doctype": "Shift Request",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"employee_name": "_Test Employee",
|
||||
"from_date": nowdate(),
|
||||
"to_date": add_days(nowdate(), 10),
|
||||
"approver": approver,
|
||||
"status": "Approved"
|
||||
}).insert()
|
||||
|
||||
if do_not_submit:
|
||||
return shift_request
|
||||
|
||||
shift_request.submit()
|
||||
return shift_request
|
@ -504,3 +504,25 @@ def grant_leaves_automatically():
|
||||
lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
|
||||
for assignment in lpa:
|
||||
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
|
||||
|
||||
def share_doc_with_approver(doc, user):
|
||||
# if approver does not have permissions, share
|
||||
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
|
||||
frappe.share.add(doc.doctype, doc.name, user, submit=1,
|
||||
flags={"ignore_share_permission": True})
|
||||
|
||||
frappe.msgprint(_("Shared with the user {0} with {1} access").format(
|
||||
user, frappe.bold("submit"), alert=True))
|
||||
|
||||
# remove shared doc if approver changes
|
||||
doc_before_save = doc.get_doc_before_save()
|
||||
if doc_before_save:
|
||||
approvers = {
|
||||
"Leave Application": "leave_approver",
|
||||
"Expense Claim": "expense_approver",
|
||||
"Shift Request": "approver"
|
||||
}
|
||||
|
||||
approver = approvers.get(doc.doctype)
|
||||
if doc_before_save.get(approver) != doc.get(approver):
|
||||
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
|
||||
|
@ -15,6 +15,7 @@
|
||||
"hide_custom": 0,
|
||||
"icon": "hr",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "HR",
|
||||
"links": [
|
||||
@ -226,42 +227,12 @@
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Application",
|
||||
"link_to": "Leave Application",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Allocation",
|
||||
"link_to": "Leave Allocation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Type",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy",
|
||||
"link_to": "Leave Policy",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Period",
|
||||
"link_to": "Leave Period",
|
||||
"label": "Holiday List",
|
||||
"link_to": "Holiday List",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -280,8 +251,28 @@
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Holiday List",
|
||||
"link_to": "Holiday List",
|
||||
"label": "Leave Period",
|
||||
"link_to": "Leave Period",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Type",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy",
|
||||
"link_to": "Leave Policy",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Policy",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Policy Assignment",
|
||||
"link_to": "Leave Policy Assignment",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -290,8 +281,18 @@
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Compensatory Leave Request",
|
||||
"link_to": "Compensatory Leave Request",
|
||||
"label": "Leave Application",
|
||||
"link_to": "Leave Application",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Leave Allocation",
|
||||
"link_to": "Leave Allocation",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -317,12 +318,12 @@
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Leave Application",
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance",
|
||||
"link_to": "Employee Leave Balance",
|
||||
"link_type": "Report",
|
||||
"is_query_report": 0,
|
||||
"label": "Compensatory Leave Request",
|
||||
"link_to": "Compensatory Leave Request",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
@ -383,16 +384,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Attendance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Monthly Attendance Sheet",
|
||||
"link_to": "Monthly Attendance Sheet",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -420,6 +411,15 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Travel Request",
|
||||
"link_to": "Travel Request",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -464,6 +464,15 @@
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Driver",
|
||||
"link_to": "Driver",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
@ -541,6 +550,24 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Appointment Letter",
|
||||
"link_to": "Appointment Letter",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Appointment Letter Template",
|
||||
"link_to": "Appointment Letter Template",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -625,33 +652,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Birthday",
|
||||
"link_to": "Employee Birthday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employees working on a holiday",
|
||||
"link_to": "Employees working on a holiday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@ -702,7 +702,74 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax and Benefits",
|
||||
"label": "Key Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "Attendance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Monthly Attendance Sheet",
|
||||
"link_to": "Monthly Attendance Sheet",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Staffing Plan",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Recruitment Analytics",
|
||||
"link_to": "Recruitment Analytics",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Analytics",
|
||||
"link_to": "Employee Analytics",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance",
|
||||
"link_to": "Employee Leave Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Leave Balance Summary",
|
||||
"link_to": "Employee Leave Balance Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee Advance",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Advance Summary",
|
||||
"link_to": "Employee Advance Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Other Reports",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
@ -710,74 +777,44 @@
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Declaration",
|
||||
"link_to": "Employee Tax Exemption Declaration",
|
||||
"link_type": "DocType",
|
||||
"label": "Employee Information",
|
||||
"link_to": "Employee Information",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Proof Submission",
|
||||
"link_to": "Employee Tax Exemption Proof Submission",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee, Payroll Period",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Other Income",
|
||||
"link_to": "Employee Other Income",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Employee Birthday",
|
||||
"link_to": "Employee Birthday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Benefit Application",
|
||||
"link_to": "Employee Benefit Application",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Employees Working on a Holiday",
|
||||
"link_to": "Employees working on a holiday",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"dependencies": "Daily Work Summary",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Benefit Claim",
|
||||
"link_to": "Employee Benefit Claim",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Category",
|
||||
"link_to": "Employee Tax Exemption Category",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Employee",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Employee Tax Exemption Sub Category",
|
||||
"link_to": "Employee Tax Exemption Sub Category",
|
||||
"link_type": "DocType",
|
||||
"is_query_report": 1,
|
||||
"label": "Daily Work Summary Replies",
|
||||
"link_to": "Daily Work Summary Replies",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-21 13:38:38.941001",
|
||||
"modified": "2021-03-24 17:35:21.483297",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "HR",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"interest_payable",
|
||||
"payable_amount",
|
||||
"column_break_9",
|
||||
"shortfall_amount",
|
||||
"payable_principal_amount",
|
||||
"penalty_amount",
|
||||
"amount_paid",
|
||||
@ -31,6 +32,7 @@
|
||||
"column_break_21",
|
||||
"reference_date",
|
||||
"principal_amount_paid",
|
||||
"total_penalty_paid",
|
||||
"total_interest_paid",
|
||||
"repayment_details",
|
||||
"amended_from"
|
||||
@ -226,12 +228,25 @@
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate Of Interest",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shortfall_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Shortfall Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_penalty_paid",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Penalty Paid",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:06:58.792841",
|
||||
"modified": "2021-04-05 13:45:19.137896",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
|
@ -21,6 +21,7 @@ class LoanRepayment(AccountsController):
|
||||
def validate(self):
|
||||
amounts = calculate_amounts(self.against_loan, self.posting_date)
|
||||
self.set_missing_values(amounts)
|
||||
self.check_future_entries()
|
||||
self.validate_amount()
|
||||
self.allocate_amounts(amounts)
|
||||
|
||||
@ -60,16 +61,29 @@ class LoanRepayment(AccountsController):
|
||||
if not self.payable_amount:
|
||||
self.payable_amount = flt(amounts['payable_amount'], precision)
|
||||
|
||||
shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
|
||||
'shortfall_amount'))
|
||||
|
||||
if shortfall_amount:
|
||||
self.shortfall_amount = shortfall_amount
|
||||
|
||||
if amounts.get('due_date'):
|
||||
self.due_date = amounts.get('due_date')
|
||||
|
||||
def check_future_entries(self):
|
||||
future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
|
||||
"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
|
||||
|
||||
if future_repayment_date:
|
||||
frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
|
||||
|
||||
def validate_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
if not self.amount_paid:
|
||||
frappe.throw(_("Amount paid cannot be zero"))
|
||||
|
||||
if self.amount_paid < self.penalty_amount:
|
||||
if not self.shortfall_amount and self.amount_paid < self.penalty_amount:
|
||||
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
@ -148,11 +162,28 @@ class LoanRepayment(AccountsController):
|
||||
def allocate_amounts(self, repayment_details):
|
||||
self.set('repayment_details', [])
|
||||
self.principal_amount_paid = 0
|
||||
total_interest_paid = 0
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
self.total_penalty_paid = 0
|
||||
interest_paid = self.amount_paid
|
||||
|
||||
if self.amount_paid - self.penalty_amount > 0:
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
|
||||
self.principal_amount_paid = self.shortfall_amount
|
||||
elif self.shortfall_amount:
|
||||
self.principal_amount_paid = self.amount_paid
|
||||
|
||||
interest_paid -= self.principal_amount_paid
|
||||
|
||||
if interest_paid > 0:
|
||||
if self.penalty_amount and interest_paid > self.penalty_amount:
|
||||
self.total_penalty_paid = self.penalty_amount
|
||||
elif self.penalty_amount:
|
||||
self.total_penalty_paid = interest_paid
|
||||
|
||||
interest_paid -= self.total_penalty_paid
|
||||
|
||||
total_interest_paid = 0
|
||||
# interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount
|
||||
|
||||
if interest_paid > 0:
|
||||
for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])):
|
||||
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
|
||||
interest_amount = amounts['interest_amount']
|
||||
@ -177,7 +208,7 @@ class LoanRepayment(AccountsController):
|
||||
'paid_principal_amount': paid_principal
|
||||
})
|
||||
|
||||
if repayment_details['unaccrued_interest'] and interest_paid:
|
||||
if repayment_details['unaccrued_interest'] and interest_paid > 0:
|
||||
# no of days for which to accrue interest
|
||||
# Interest can only be accrued for an entire day and not partial
|
||||
if interest_paid > repayment_details['unaccrued_interest']:
|
||||
@ -193,20 +224,20 @@ class LoanRepayment(AccountsController):
|
||||
interest_paid -= no_of_days * per_day_interest
|
||||
|
||||
self.total_interest_paid = total_interest_paid
|
||||
if interest_paid:
|
||||
if interest_paid > 0:
|
||||
self.principal_amount_paid += interest_paid
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
gle_map = []
|
||||
loan_details = frappe.get_doc("Loan", self.against_loan)
|
||||
|
||||
if self.penalty_amount:
|
||||
if self.total_penalty_paid:
|
||||
gle_map.append(
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.loan_account,
|
||||
"against": loan_details.payment_account,
|
||||
"debit": self.penalty_amount,
|
||||
"debit_in_account_currency": self.penalty_amount,
|
||||
"debit": self.total_penalty_paid,
|
||||
"debit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
@ -221,8 +252,8 @@ class LoanRepayment(AccountsController):
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.penalty_income_account,
|
||||
"against": loan_details.payment_account,
|
||||
"credit": self.penalty_amount,
|
||||
"credit_in_account_currency": self.penalty_amount,
|
||||
"credit": self.total_penalty_paid,
|
||||
"credit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
@ -284,7 +315,9 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
||||
|
||||
return lr
|
||||
|
||||
def get_accrued_interest_entries(against_loan):
|
||||
def get_accrued_interest_entries(against_loan, posting_date=None):
|
||||
if not posting_date:
|
||||
posting_date = getdate()
|
||||
|
||||
unpaid_accrued_entries = frappe.db.sql(
|
||||
"""
|
||||
@ -295,12 +328,13 @@ def get_accrued_interest_entries(against_loan):
|
||||
`tabLoan Interest Accrual`
|
||||
WHERE
|
||||
loan = %s
|
||||
AND posting_date <= %s
|
||||
AND (interest_amount - paid_interest_amount > 0 OR
|
||||
payable_principal_amount - paid_principal_amount > 0)
|
||||
AND
|
||||
docstatus = 1
|
||||
ORDER BY posting_date
|
||||
""", (against_loan), as_dict=1)
|
||||
""", (against_loan, posting_date), as_dict=1)
|
||||
|
||||
return unpaid_accrued_entries
|
||||
|
||||
@ -312,7 +346,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
|
||||
against_loan_doc = frappe.get_doc("Loan", against_loan)
|
||||
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
|
||||
|
||||
pending_accrual_entries = {}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "LM-LSS-.#####",
|
||||
"creation": "2019-09-06 11:33:34.709540",
|
||||
"doctype": "DocType",
|
||||
@ -14,6 +15,7 @@
|
||||
"shortfall_amount",
|
||||
"column_break_8",
|
||||
"security_value",
|
||||
"shortfall_percentage",
|
||||
"section_break_8",
|
||||
"process_loan_security_shortfall"
|
||||
],
|
||||
@ -85,10 +87,18 @@
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shortfall_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Shortfall Percentage",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-10-24 06:24:26.128997",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-01 08:13:43.263772",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Shortfall",
|
||||
|
@ -12,7 +12,7 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
|
||||
class LoanSecurityShortfall(Document):
|
||||
pass
|
||||
|
||||
def update_shortfall_status(loan, security_value):
|
||||
def update_shortfall_status(loan, security_value, on_cancel=0):
|
||||
loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
|
||||
{"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
|
||||
|
||||
@ -22,7 +22,9 @@ def update_shortfall_status(loan, security_value):
|
||||
if security_value >= loan_security_shortfall.shortfall_amount:
|
||||
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
|
||||
"status": "Completed",
|
||||
"shortfall_amount": loan_security_shortfall.shortfall_amount})
|
||||
"shortfall_amount": loan_security_shortfall.shortfall_amount,
|
||||
"shortfall_percentage": 0
|
||||
})
|
||||
else:
|
||||
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
|
||||
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
|
||||
@ -65,7 +67,8 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
outstanding_amount = loan.disbursed_amount
|
||||
outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
|
||||
pledged_securities = get_pledged_security_qty(loan.name)
|
||||
ltv_ratio = ''
|
||||
@ -81,14 +84,15 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
if current_ratio > ltv_ratio:
|
||||
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
|
||||
create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
|
||||
process_loan_security_shortfall)
|
||||
current_ratio, process_loan_security_shortfall)
|
||||
elif loan_shortfall_map.get(loan.name):
|
||||
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
|
||||
if shortfall_amount <= 0:
|
||||
shortfall = loan_shortfall_map.get(loan.name)
|
||||
update_pending_shortfall(shortfall)
|
||||
|
||||
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
|
||||
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio,
|
||||
process_loan_security_shortfall):
|
||||
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
|
||||
|
||||
if existing_shortfall:
|
||||
@ -101,6 +105,7 @@ def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_
|
||||
ltv_shortfall.loan_amount = loan_amount
|
||||
ltv_shortfall.security_value = security_value
|
||||
ltv_shortfall.shortfall_amount = shortfall_amount
|
||||
ltv_shortfall.shortfall_percentage = shortfall_ratio
|
||||
ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall
|
||||
ltv_shortfall.save()
|
||||
|
||||
@ -114,6 +119,7 @@ def update_pending_shortfall(shortfall):
|
||||
frappe.db.set_value("Loan Security Shortfall", shortfall,
|
||||
{
|
||||
"status": "Completed",
|
||||
"shortfall_amount": 0
|
||||
"shortfall_amount": 0,
|
||||
"shortfall_percentage": 0
|
||||
})
|
||||
|
||||
|
@ -70,7 +70,9 @@
|
||||
{
|
||||
"fieldname": "loan_repayment_entry",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Loan Repayment Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Loan Repayment",
|
||||
"read_only": 1
|
||||
},
|
||||
@ -83,9 +85,10 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 13:17:04.798335",
|
||||
"modified": "2021-03-14 20:47:11.725818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Salary Slip Loan",
|
||||
|
@ -63,9 +63,11 @@ def get_active_loan_details(filters):
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for loan in loan_details:
|
||||
total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount
|
||||
|
||||
loan.update({
|
||||
"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
|
||||
"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
|
||||
"principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \
|
||||
- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
|
||||
"total_repayment": flt(payments.get(loan.loan)),
|
||||
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
|
||||
@ -81,15 +81,27 @@ class TestBOM(unittest.TestCase):
|
||||
bom = frappe.copy_doc(test_records[2])
|
||||
bom.insert()
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.operating_cost, 100)
|
||||
self.assertEqual(bom.raw_material_cost, 351.68)
|
||||
self.assertEqual(bom.total_cost, 451.68)
|
||||
raw_material_cost = 0.0
|
||||
op_cost = 0.0
|
||||
|
||||
for op_row in bom.operations:
|
||||
op_cost += op_row.operating_cost
|
||||
|
||||
for row in bom.items:
|
||||
raw_material_cost += row.amount
|
||||
|
||||
base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
|
||||
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.base_operating_cost, 6000)
|
||||
self.assertEqual(bom.base_raw_material_cost, 21100.80)
|
||||
self.assertEqual(bom.base_total_cost, 27100.80)
|
||||
self.assertEqual(bom.operating_cost, op_cost)
|
||||
self.assertEqual(bom.raw_material_cost, raw_material_cost)
|
||||
self.assertEqual(bom.total_cost, raw_material_cost + op_cost)
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.base_operating_cost, base_op_cost)
|
||||
self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost)
|
||||
self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
|
||||
|
||||
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
|
||||
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
|
||||
@ -134,7 +146,13 @@ class TestBOM(unittest.TestCase):
|
||||
bom.items[0].conversion_factor = 6
|
||||
bom.insert()
|
||||
|
||||
reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200)
|
||||
reset_item_valuation_rate(
|
||||
item_code='_Test Item',
|
||||
warehouse_list=frappe.get_all("Warehouse",
|
||||
{"is_group":0, "company": bom.company}, pluck="name"),
|
||||
qty=200,
|
||||
rate=200
|
||||
)
|
||||
|
||||
bom.update_cost()
|
||||
|
||||
|
@ -47,6 +47,8 @@ class JobCard(Document):
|
||||
if d.completed_qty:
|
||||
self.total_completed_qty += d.completed_qty
|
||||
|
||||
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
||||
|
||||
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||
production_capacity = 1
|
||||
|
||||
|
@ -25,6 +25,16 @@ frappe.ui.form.on('Production Plan', {
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query('material_request', 'material_requests', function() {
|
||||
return {
|
||||
filters: {
|
||||
material_request_type: "Manufacture",
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
@ -370,4 +380,4 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu
|
||||
['Sales Order','docstatus', '=' ,1]
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ class ProductionPlan(Document):
|
||||
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
|
||||
where mr_item.parent = mr.name
|
||||
and mr.material_request_type = "Manufacture"
|
||||
and mr.docstatus = 1 and mr.company = %(company)s
|
||||
and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s
|
||||
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
|
||||
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
|
||||
and bom.is_active = 1))
|
||||
|
@ -13,8 +13,15 @@ from erpnext.manufacturing.doctype.workstation.test_workstation import make_work
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
|
||||
class TestRouting(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.item_code = "Test Routing Item - A"
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql('delete from tabBOM where item=%s', cls.item_code)
|
||||
|
||||
def test_sequence_id(self):
|
||||
item_code = "Test Routing Item - A"
|
||||
operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
|
||||
{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
|
||||
|
||||
@ -22,8 +29,8 @@ class TestRouting(unittest.TestCase):
|
||||
|
||||
setup_operations(operations)
|
||||
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
|
||||
bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
|
||||
wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
|
||||
bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name)
|
||||
wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name)
|
||||
|
||||
for row in routing_doc.operations:
|
||||
self.assertEqual(row.sequence_id, row.idx)
|
||||
|
@ -371,14 +371,14 @@ class TestWorkOrder(unittest.TestCase):
|
||||
|
||||
def test_job_card(self):
|
||||
stock_entries = []
|
||||
data = frappe.get_cached_value('BOM',
|
||||
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||
bom = frappe.get_doc('BOM', {
|
||||
'docstatus': 1,
|
||||
'with_operations': 1,
|
||||
'company': '_Test Company'
|
||||
})
|
||||
|
||||
bom, bom_item = data
|
||||
|
||||
bom_doc = frappe.get_doc('BOM', bom)
|
||||
work_order = make_wo_order_test_record(item=bom_item, qty=1,
|
||||
bom_no=bom, source_warehouse="_Test Warehouse - _TC")
|
||||
work_order = make_wo_order_test_record(item=bom.item, qty=1,
|
||||
bom_no=bom.name, source_warehouse="_Test Warehouse - _TC")
|
||||
|
||||
for row in work_order.required_items:
|
||||
stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
|
||||
@ -390,14 +390,14 @@ class TestWorkOrder(unittest.TestCase):
|
||||
stock_entries.append(ste)
|
||||
|
||||
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
|
||||
self.assertEqual(len(job_cards), len(bom_doc.operations))
|
||||
self.assertEqual(len(job_cards), len(bom.operations))
|
||||
|
||||
for i, job_card in enumerate(job_cards):
|
||||
doc = frappe.get_doc("Job Card", job_card)
|
||||
doc.append("time_logs", {
|
||||
"from_time": now(),
|
||||
"hours": i,
|
||||
"to_time": add_to_date(now(), i),
|
||||
"from_time": add_to_date(None, i),
|
||||
"hours": 1,
|
||||
"to_time": add_to_date(None, i + 1),
|
||||
"completed_qty": doc.for_quantity
|
||||
})
|
||||
doc.submit()
|
||||
|
@ -99,7 +99,7 @@ execute:frappe.delete_doc("DocType", "Purchase Request")
|
||||
execute:frappe.delete_doc("DocType", "Purchase Request Item")
|
||||
erpnext.patches.v4_2.recalculate_bom_cost
|
||||
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
|
||||
erpnext.patches.v4_2.update_requested_and_ordered_qty
|
||||
erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
|
||||
execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
|
||||
erpnext.patches.v4_4.make_email_accounts
|
||||
execute:frappe.delete_doc("DocType", "Contact Control")
|
||||
@ -208,7 +208,7 @@ erpnext.patches.v5_7.update_item_description_based_on_item_master
|
||||
erpnext.patches.v5_7.item_template_attributes
|
||||
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
|
||||
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")
|
||||
erpnext.patches.v4_2.repost_reserved_qty #2016-04-15
|
||||
erpnext.patches.v4_2.repost_reserved_qty #2021-03-31
|
||||
erpnext.patches.v5_4.update_purchase_cost_against_project
|
||||
erpnext.patches.v5_8.update_order_reference_in_return_entries
|
||||
erpnext.patches.v5_8.add_credit_note_print_heading
|
||||
@ -752,6 +752,7 @@ erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
||||
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
||||
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
||||
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
|
||||
erpnext.patches.v13_0.update_payment_terms_outstanding
|
||||
erpnext.patches.v12_0.add_state_code_for_ladakh
|
||||
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
||||
@ -762,3 +763,4 @@ erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
|
||||
erpnext.patches.v13_0.setup_uae_vat_fields
|
||||
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
|
||||
erpnext.patches.v13_0.rename_discharge_date_in_ip_record
|
||||
erpnext.patches.v12_0.purchase_receipt_status
|
||||
|
30
erpnext/patches/v12_0/purchase_receipt_status.py
Normal file
30
erpnext/patches/v12_0/purchase_receipt_status.py
Normal file
@ -0,0 +1,30 @@
|
||||
""" This patch fixes old purchase receipts (PR) where even after submitting
|
||||
the PR, the `status` remains "Draft". `per_billed` field was copied over from previous
|
||||
doc (PO), hence it is recalculated for setting new correct status of PR.
|
||||
"""
|
||||
|
||||
import frappe
|
||||
|
||||
logger = frappe.logger("patch", allow_site=True, file_count=50)
|
||||
|
||||
def execute():
|
||||
affected_purchase_receipts = frappe.db.sql(
|
||||
"""select name from `tabPurchase Receipt`
|
||||
where status = 'Draft' and per_billed = 100 and docstatus = 1"""
|
||||
)
|
||||
|
||||
if not affected_purchase_receipts:
|
||||
return
|
||||
|
||||
logger.info("purchase_receipt_status: begin patch, PR count: {}"
|
||||
.format(len(affected_purchase_receipts)))
|
||||
|
||||
|
||||
for pr in affected_purchase_receipts:
|
||||
pr_name = pr[0]
|
||||
logger.info("purchase_receipt_status: patching PR - {}".format(pr_name))
|
||||
|
||||
pr_doc = frappe.get_doc("Purchase Receipt", pr_name)
|
||||
|
||||
pr_doc.update_billing_status(update_modified=False)
|
||||
pr_doc.set_status(update=True, update_modified=False)
|
@ -6,6 +6,8 @@ def execute():
|
||||
if "Healthcare" not in frappe.get_active_domains():
|
||||
return
|
||||
|
||||
frappe.reload_doc("healthcare", "doctype", "Therapy Session")
|
||||
frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
|
||||
|
15
erpnext/patches/v13_0/update_payment_terms_outstanding.py
Normal file
15
erpnext/patches/v13_0/update_payment_terms_outstanding.py
Normal file
@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("accounts", "doctype", "Payment Schedule")
|
||||
if frappe.db.count('Payment Schedule'):
|
||||
frappe.db.sql('''
|
||||
UPDATE
|
||||
`tabPayment Schedule` ps
|
||||
SET
|
||||
ps.outstanding = (ps.payment_amount - ps.paid_amount)
|
||||
''')
|
@ -163,7 +163,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -176,7 +175,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-20 17:51:13.419716",
|
||||
"modified": "2021-03-31 14:45:48.566756",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Additional Salary",
|
||||
|
@ -124,7 +124,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -148,7 +147,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-14 15:52:08.566418",
|
||||
"modified": "2021-03-31 14:46:22.465521",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Benefit Application",
|
||||
|
@ -21,7 +21,6 @@ frappe.ui.form.on('Employee Benefit Claim', {
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.set_df_property('currency', 'hidden', 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -125,10 +125,9 @@
|
||||
"label": "Attachments"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval: doc.employee",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1,
|
||||
@ -145,7 +144,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-25 11:49:56.097352",
|
||||
"modified": "2021-03-31 15:51:51.489269",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Benefit Claim",
|
||||
|
@ -75,7 +75,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -95,7 +94,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-20 17:22:16.468042",
|
||||
"modified": "2021-03-31 14:48:00.919839",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Incentive",
|
||||
|
@ -47,5 +47,26 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', {
|
||||
});
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frm.trigger('get_employee_currency');
|
||||
}
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -108,7 +108,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval: doc.employee",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
@ -119,7 +119,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-20 16:42:24.493761",
|
||||
"modified": "2021-03-31 20:41:57.387749",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Declaration",
|
||||
|
@ -58,5 +58,26 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
|
||||
|
||||
currency: function(frm) {
|
||||
frm.refresh_fields();
|
||||
}
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frm.trigger('get_employee_currency');
|
||||
}
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -131,7 +131,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval: doc.employee",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
@ -142,7 +142,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-20 16:47:03.410020",
|
||||
"modified": "2021-03-31 20:48:32.639885",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Proof Submission",
|
||||
|
@ -93,7 +93,7 @@
|
||||
"options": "Income Tax Slab Other Charges"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"fetch_from": "company.default_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
@ -104,7 +104,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-19 13:54:24.728075",
|
||||
"modified": "2021-03-31 20:53:33.323712",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Income Tax Slab",
|
||||
|
@ -133,45 +133,59 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
if (!frm.doc.company) {
|
||||
frappe.msgprint(__("Please set a Company"));
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
|
||||
filters: frm.events.get_employee_filters(frm)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_filters: function (frm) {
|
||||
let filters = {};
|
||||
filters['company'] = frm.doc.company;
|
||||
filters['start_date'] = frm.doc.start_date;
|
||||
filters['end_date'] = frm.doc.end_date;
|
||||
|
||||
if (frm.doc.department) {
|
||||
filters['department'] = frm.doc.department;
|
||||
}
|
||||
if (frm.doc.branch) {
|
||||
filters['branch'] = frm.doc.branch;
|
||||
}
|
||||
if (frm.doc.designation) {
|
||||
filters['designation'] = frm.doc.designation;
|
||||
}
|
||||
if (frm.doc.employees) {
|
||||
filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee);
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
|
||||
payroll_frequency: function (frm) {
|
||||
frm.trigger("set_start_end_dates").then( ()=> {
|
||||
frm.events.clear_employee_table(frm);
|
||||
frm.events.get_employee_with_salary_slip_and_set_query(frm);
|
||||
});
|
||||
},
|
||||
|
||||
employee_filters: function (frm, emp_list) {
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ["not in", emp_list]
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_with_salary_slip_and_set_query: function (frm) {
|
||||
frappe.db.get_list('Salary Slip', {
|
||||
filters: {
|
||||
start_date: frm.doc.start_date,
|
||||
end_date: frm.doc.end_date,
|
||||
docstatus: 1,
|
||||
},
|
||||
fields: ['employee']
|
||||
}).then((emp) => {
|
||||
var emp_list = [];
|
||||
emp.forEach((employee_data) => {
|
||||
emp_list.push(Object.values(employee_data)[0]);
|
||||
});
|
||||
frm.events.employee_filters(frm, emp_list);
|
||||
});
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
frm.events.clear_employee_table(frm);
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
frm.trigger("set_payable_account_and_currency");
|
||||
},
|
||||
|
||||
set_payable_account_and_currency: function (frm) {
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => {
|
||||
frm.set_value('currency', r.default_currency);
|
||||
});
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => {
|
||||
frm.set_value('payroll_payable_account', r.default_payroll_payable_account);
|
||||
});
|
||||
},
|
||||
|
||||
currency: function (frm) {
|
||||
@ -345,11 +359,3 @@ let render_employee_attendance = function (frm, data) {
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Payroll Employee Detail', {
|
||||
employee: function(frm) {
|
||||
if (!frm.doc.payroll_frequency) {
|
||||
frappe.throw(__("Please set a Payroll Frequency"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -10,16 +10,17 @@ from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT,
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
|
||||
class PayrollEntry(Document):
|
||||
def onload(self):
|
||||
if not self.docstatus==1 or self.salary_slips_submitted:
|
||||
return
|
||||
return
|
||||
|
||||
# check if salary slips were manually submitted
|
||||
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
|
||||
if cint(entries) == len(self.employees):
|
||||
self.set_onload("submitted_ss", True)
|
||||
self.set_onload("submitted_ss", True)
|
||||
|
||||
def validate(self):
|
||||
self.number_of_employees = len(self.employees)
|
||||
@ -59,16 +60,16 @@ class PayrollEntry(Document):
|
||||
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
sal_struct = frappe.db.sql_list("""
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
@ -176,15 +177,15 @@ class PayrollEntry(Document):
|
||||
"""
|
||||
Returns list of salary slips based on selected criteria
|
||||
"""
|
||||
cond = self.get_filter_condition()
|
||||
|
||||
ss_list = frappe.db.sql("""
|
||||
select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
|
||||
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
|
||||
""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
return ss_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_salary_slips(self):
|
||||
self.check_permission('write')
|
||||
ss_list = self.get_sal_slip_list(ss_status=0)
|
||||
@ -270,26 +271,26 @@ class PayrollEntry(Document):
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount += flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Deductions
|
||||
for acc_cc, amount in deductions.items():
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount -= flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Payable amount
|
||||
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
|
||||
@ -335,10 +336,9 @@ class PayrollEntry(Document):
|
||||
def make_payment_entry(self):
|
||||
self.check_permission('write')
|
||||
|
||||
cond = self.get_filter_condition()
|
||||
salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
|
||||
""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
|
||||
""", (self.start_date, self.end_date, self.name), as_list = True)
|
||||
|
||||
if salary_slip_name_list and len(salary_slip_name_list) > 0:
|
||||
salary_slip_total = 0
|
||||
@ -370,20 +370,20 @@ class PayrollEntry(Document):
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
|
||||
if len(currencies) > 1:
|
||||
multi_currency = 1
|
||||
@ -409,6 +409,7 @@ class PayrollEntry(Document):
|
||||
self.update(get_start_end_dates(self.payroll_frequency,
|
||||
self.start_date or self.posting_date, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_employee_attendance(self):
|
||||
employees_to_mark_attendance = []
|
||||
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
|
||||
@ -424,7 +425,7 @@ class PayrollEntry(Document):
|
||||
employees_to_mark_attendance.append({
|
||||
"employee": employee_detail.employee,
|
||||
"employee_name": employee_detail.employee_name
|
||||
})
|
||||
})
|
||||
return employees_to_mark_attendance
|
||||
|
||||
def get_count_holidays_of_employee(self, employee, start_date):
|
||||
@ -441,11 +442,11 @@ class PayrollEntry(Document):
|
||||
def get_count_employee_attendance(self, employee, start_date):
|
||||
marked_days = 0
|
||||
attendances = frappe.get_all("Attendance",
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
if attendances and attendances[0][0]:
|
||||
marked_days = attendances[0][0]
|
||||
return marked_days
|
||||
@ -553,6 +554,7 @@ def payroll_entry_has_bank_entries(name):
|
||||
def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
salary_slips_exists_for = get_existing_salary_slips(employees, args)
|
||||
count=0
|
||||
salary_slips_not_created = []
|
||||
for emp in employees:
|
||||
if emp not in salary_slips_exists_for:
|
||||
args.update({
|
||||
@ -566,33 +568,24 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
|
||||
title = _("Creating Salary Slips..."))
|
||||
else:
|
||||
salary_slip_name = frappe.db.sql(
|
||||
'''SELECT
|
||||
name
|
||||
FROM `tabSalary Slip`
|
||||
WHERE company=%s
|
||||
AND start_date >= %s
|
||||
AND end_date <= %s
|
||||
AND employee = %s
|
||||
''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
|
||||
|
||||
salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
|
||||
salary_slip_doc.exchange_rate = args.exchange_rate
|
||||
salary_slip_doc.set_totals()
|
||||
salary_slip_doc.db_update()
|
||||
salary_slips_not_created.append(emp)
|
||||
|
||||
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
|
||||
payroll_entry.db_set("salary_slips_created", 1)
|
||||
payroll_entry.notify_update()
|
||||
|
||||
if salary_slips_not_created:
|
||||
frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
|
||||
.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
|
||||
|
||||
def get_existing_salary_slips(employees, args):
|
||||
return frappe.db.sql_list("""
|
||||
select distinct employee from `tabSalary Slip`
|
||||
where docstatus!= 2 and company = %s
|
||||
where docstatus!= 2 and company = %s and payroll_entry = %s
|
||||
and start_date >= %s and end_date <= %s
|
||||
and employee in (%s)
|
||||
""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.start_date, args.end_date] + employees)
|
||||
""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.payroll_entry, args.start_date, args.end_date] + employees)
|
||||
|
||||
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
|
||||
submitted_ss = []
|
||||
@ -644,3 +637,61 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'start': start, 'page_len': page_len
|
||||
})
|
||||
|
||||
def get_employee_with_existing_salary_slip(start_date, end_date, company):
|
||||
return frappe.db.sql_list("""
|
||||
select employee from `tabSalary Slip`
|
||||
where
|
||||
(start_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
end_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
%(start_date)s between start_date and end_date)
|
||||
and company = %(company)s
|
||||
and docstatus = 1
|
||||
""", {'start_date': start_date, 'end_date': end_date, 'company': company})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = frappe._dict(filters)
|
||||
conditions = []
|
||||
exclude_employees = []
|
||||
emp_cond = ''
|
||||
if filters.start_date and filters.end_date:
|
||||
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
|
||||
emp = filters.get('employees')
|
||||
filters.pop('start_date')
|
||||
filters.pop('end_date')
|
||||
if filters.employees is not None:
|
||||
filters.pop('employees')
|
||||
if employee_list:
|
||||
exclude_employees.extend(employee_list)
|
||||
if emp:
|
||||
exclude_employees.extend(emp)
|
||||
if exclude_employees:
|
||||
emp_cond += 'and employee not in %(exclude_employees)s'
|
||||
|
||||
return frappe.db.sql("""select name, employee_name from `tabEmployee`
|
||||
where status = 'Active'
|
||||
and docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
or employee_name like %(txt)s)
|
||||
{emp_cond}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond': get_match_cond(doctype),
|
||||
'emp_cond': emp_cond
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
'exclude_employees': exclude_employees})
|
||||
|
@ -51,21 +51,22 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
|
||||
company_doc = frappe.get_doc('Company', company)
|
||||
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company)
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD')
|
||||
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
|
||||
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
|
||||
payroll_entry.make_payment_entry()
|
||||
|
||||
salary_slip.load_from_db()
|
||||
|
||||
payroll_je = salary_slip.journal_entry
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
if payroll_je:
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
|
||||
payment_entry = frappe.db.sql('''
|
||||
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
|
||||
|
@ -93,7 +93,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@ -106,7 +105,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-20 17:27:47.003134",
|
||||
"modified": "2021-03-31 14:50:29.401020",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Retention Bonus",
|
||||
|
@ -39,7 +39,8 @@ frappe.ui.form.on("Salary Slip", {
|
||||
|
||||
frm.set_query("employee", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
filters: frm.doc.company
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -93,28 +94,31 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, company_currency) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property("exchange_rate", "hidden", 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property("exchange_rate", "hidden", 1);
|
||||
frm.set_df_property("exchange_rate", "description", "");
|
||||
}
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -216,7 +220,7 @@ frappe.ui.form.on('Salary Slip Timesheet', {
|
||||
});
|
||||
|
||||
var set_totals = function(frm) {
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") {
|
||||
if (frm.doc.earnings || frm.doc.deductions) {
|
||||
frappe.call({
|
||||
method: "set_totals",
|
||||
|
@ -500,7 +500,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
|
||||
"fetch_from": "salary_structure.currency",
|
||||
"fieldname": "currency",
|
||||
@ -632,7 +631,7 @@
|
||||
"idx": 9,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-19 11:48:05.383945",
|
||||
"modified": "2021-03-31 15:39:28.817166",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Slip",
|
||||
|
@ -124,9 +124,12 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def check_existing(self):
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
cond = ""
|
||||
if self.payroll_entry:
|
||||
cond += "and payroll_entry = '{0}'".format(self.payroll_entry)
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where start_date = %s and end_date = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
and employee = %s and name != %s {0}""".format(cond),
|
||||
(self.start_date, self.end_date, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
@ -618,13 +621,16 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
component_row = self.append(component_type)
|
||||
for attr in (
|
||||
'depends_on_payment_days', 'salary_component', 'abbr'
|
||||
'depends_on_payment_days', 'salary_component',
|
||||
'do_not_include_in_total', 'is_tax_applicable',
|
||||
'is_flexible_benefit', 'variable_based_on_taxable_salary',
|
||||
'exempted_from_income_tax'
|
||||
):
|
||||
component_row.set(attr, component_data.get(attr))
|
||||
|
||||
abbr = component_data.get('abbr') or component_data.get('salary_component_abbr')
|
||||
component_row.set('abbr', abbr)
|
||||
|
||||
if additional_salary:
|
||||
component_row.default_amount = 0
|
||||
component_row.additional_amount = amount
|
||||
@ -1050,7 +1056,7 @@ class SalarySlip(TransactionBase):
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
loan.loan_repayment_entry = repayment_entry.name
|
||||
frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
|
||||
|
||||
def cancel_loan_repayment_entry(self):
|
||||
for loan in self.loans:
|
||||
|
@ -232,7 +232,7 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-30 11:30:32.190798",
|
||||
"modified": "2021-03-31 15:41:12.342380",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Structure",
|
||||
|
@ -100,7 +100,7 @@ class SalaryStructure(Document):
|
||||
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
else:
|
||||
assign_salary_structure_for_employees(employees, self,
|
||||
payroll_payable_account=payroll_payable_account,
|
||||
payroll_payable_account=payroll_payable_account,
|
||||
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
else:
|
||||
frappe.msgprint(_("No Employee Found"))
|
||||
|
@ -125,7 +125,6 @@
|
||||
"options": "Income Tax Slab"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
|
||||
"fetch_from": "salary_structure.currency",
|
||||
"fieldname": "currency",
|
||||
@ -146,7 +145,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 18:07:48.251311",
|
||||
"modified": "2021-03-31 15:49:36.361253",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Structure Assignment",
|
||||
|
@ -18,8 +18,8 @@ frappe.ui.form.on("Project", {
|
||||
};
|
||||
},
|
||||
onload: function (frm) {
|
||||
var so = frappe.meta.get_docfield("Project", "sales_order");
|
||||
so.get_route_options_for_new_doc = function (field) {
|
||||
const so = frm.get_docfield("sales_order");
|
||||
so.get_route_options_for_new_doc = () => {
|
||||
if (frm.is_new()) return;
|
||||
return {
|
||||
"customer": frm.doc.customer,
|
||||
|
@ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
this.frm.trigger("item_code", cdt, cdn);
|
||||
}
|
||||
else {
|
||||
var valid_serial_nos = [];
|
||||
var serialnos = [];
|
||||
// Replacing all occurences of comma with carriage return
|
||||
item.serial_no = item.serial_no.replace(/,/g, '\n');
|
||||
serialnos = item.serial_no.split("\n");
|
||||
for (var i = 0; i < serialnos.length; i++) {
|
||||
if (serialnos[i] != "") {
|
||||
valid_serial_nos.push(serialnos[i]);
|
||||
}
|
||||
}
|
||||
item.conversion_factor = item.conversion_factor || 1;
|
||||
|
||||
refresh_field("serial_no", item.name, item.parentfield);
|
||||
if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
|
||||
frappe.model.set_value(item.doctype, item.name,
|
||||
"qty", valid_serial_nos.length / item.conversion_factor);
|
||||
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
|
||||
if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
|
||||
setTimeout(() => {
|
||||
me.update_qty(cdt, cdn);
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
update_qty: function(cdt, cdn) {
|
||||
var valid_serial_nos = [];
|
||||
var serialnos = [];
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
serialnos = item.serial_no.split("\n");
|
||||
for (var i = 0; i < serialnos.length; i++) {
|
||||
if (serialnos[i] != "") {
|
||||
valid_serial_nos.push(serialnos[i]);
|
||||
}
|
||||
}
|
||||
frappe.model.set_value(item.doctype, item.name,
|
||||
"qty", valid_serial_nos.length / item.conversion_factor);
|
||||
frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
this.calculate_taxes_and_totals(false);
|
||||
},
|
||||
@ -1167,6 +1173,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
this.calculate_net_weight();
|
||||
}
|
||||
|
||||
// for handling customization not to fetch price list rate
|
||||
if(frappe.flags.dont_fetch_price_list_rate) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!dont_fetch_price_list_rate &&
|
||||
frappe.meta.has_field(doc.doctype, "price_list_currency")) {
|
||||
this.apply_price_list(item, true);
|
||||
|
@ -109,7 +109,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{__("Suppliies made to Composition Taxable Persons")}}</td>
|
||||
<td>{{__("Supplies made to Composition Taxable Persons")}}</td>
|
||||
<td class="right">
|
||||
{% for row in data.inter_sup.comp_details %}
|
||||
{% if row %}
|
||||
|
@ -172,7 +172,6 @@ class GSTR3BReport(Document):
|
||||
self.json_output = frappe.as_json(self.report_dict)
|
||||
|
||||
def set_inward_nil_exempt(self, inward_nil_exempt):
|
||||
|
||||
self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2)
|
||||
self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2)
|
||||
self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2)
|
||||
@ -238,7 +237,6 @@ class GSTR3BReport(Document):
|
||||
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
|
||||
|
||||
def set_inter_state_supply(self, inter_state_supply):
|
||||
|
||||
osup_det = self.report_dict["sup_details"]["osup_det"]
|
||||
|
||||
for key, value in iteritems(inter_state_supply):
|
||||
@ -352,10 +350,18 @@ class GSTR3BReport(Document):
|
||||
inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
|
||||
i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
|
||||
where p.docstatus = 1 and p.name = i.parent
|
||||
and p.gst_category != 'Registered Composition'
|
||||
and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
|
||||
month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
|
||||
group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply
|
||||
FROM `tabPurchase Invoice`
|
||||
WHERE docstatus = 1 and gst_category = 'Registered Composition'
|
||||
and month(posting_date) = %s and year(posting_date) = %s
|
||||
and company = %s and company_gstin = %s
|
||||
group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
inward_nil_exempt_details = {
|
||||
"gst": {
|
||||
"intra": 0.0,
|
||||
@ -369,9 +375,11 @@ class GSTR3BReport(Document):
|
||||
|
||||
for d in inward_nil_exempt:
|
||||
if d.place_of_supply:
|
||||
if d.is_nil_exempt == 1 and state == d.place_of_supply.split("-")[1]:
|
||||
if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
|
||||
and state == d.place_of_supply.split("-")[1]:
|
||||
inward_nil_exempt_details["gst"]["intra"] += d.base_amount
|
||||
elif d.is_nil_exempt == 1 and state != d.place_of_supply.split("-")[1]:
|
||||
elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
|
||||
and state != d.place_of_supply.split("-")[1]:
|
||||
inward_nil_exempt_details["gst"]["inter"] += d.base_amount
|
||||
elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]:
|
||||
inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount
|
||||
|
@ -64,7 +64,7 @@ class TestGSTR3BReport(unittest.TestCase):
|
||||
self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
|
||||
self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
|
||||
self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
|
||||
self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250)
|
||||
self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
|
||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
|
||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
|
||||
|
||||
@ -228,6 +228,19 @@ def create_purchase_invoices():
|
||||
|
||||
pi1.submit()
|
||||
|
||||
pi2 = make_purchase_invoice(company="_Test Company GST",
|
||||
customer = '_Test Registered Supplier',
|
||||
currency = 'INR',
|
||||
item = 'Milk',
|
||||
warehouse = 'Finished Goods - _GST',
|
||||
expense_account = 'Cost of Goods Sold - _GST',
|
||||
cost_center = 'Main - _GST',
|
||||
rate=250,
|
||||
qty=1,
|
||||
do_not_save=1
|
||||
)
|
||||
pi2.submit()
|
||||
|
||||
def make_suppliers():
|
||||
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
|
||||
frappe.get_doc({
|
||||
|
@ -919,7 +919,8 @@
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"pattern": "^([0-9A-Z/-]){1,15}$",
|
||||
"description": "Tranport Document Number"
|
||||
"description": "Tranport Document Number",
|
||||
"validationMsg": "Transport Receipt No is invalid"
|
||||
},
|
||||
"TransDocDt": {
|
||||
"type": "string",
|
||||
|
@ -87,10 +87,10 @@ def get_doc_details(invoice):
|
||||
invoice_date=invoice_date
|
||||
))
|
||||
|
||||
def get_party_details(address_name):
|
||||
def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None):
|
||||
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||
|
||||
if (not d.gstin
|
||||
if ((not d.gstin and not shipping_address)
|
||||
or not d.city
|
||||
or not d.pincode
|
||||
or not d.address_title
|
||||
@ -108,8 +108,7 @@ def get_party_details(address_name):
|
||||
# according to einvoice standard
|
||||
pincode = 999999
|
||||
|
||||
return frappe._dict(dict(
|
||||
gstin=d.gstin,
|
||||
party_address_details = frappe._dict(dict(
|
||||
legal_name=sanitize_for_json(d.address_title),
|
||||
location=sanitize_for_json(d.city),
|
||||
pincode=d.pincode,
|
||||
@ -117,6 +116,9 @@ def get_party_details(address_name):
|
||||
address_line1=sanitize_for_json(d.address_line1),
|
||||
address_line2=sanitize_for_json(d.address_line2)
|
||||
))
|
||||
if d.gstin:
|
||||
party_address_details.gstin = d.gstin
|
||||
return party_address_details
|
||||
|
||||
def get_gstin_details(gstin):
|
||||
if not hasattr(frappe.local, 'gstin_cache'):
|
||||
@ -328,14 +330,17 @@ def make_einvoice(invoice):
|
||||
item_list = get_item_list(invoice)
|
||||
doc_details = get_doc_details(invoice)
|
||||
invoice_value_details = get_invoice_value_details(invoice)
|
||||
seller_details = get_party_details(invoice.company_address)
|
||||
seller_details = get_party_details(invoice.company_address, company_address=1)
|
||||
|
||||
if invoice.gst_category == 'Overseas':
|
||||
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||
else:
|
||||
buyer_details = get_party_details(invoice.customer_address)
|
||||
place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin)
|
||||
place_of_supply = place_of_supply[:2]
|
||||
buyer_details = get_party_details(invoice.customer_address, billing_address=1)
|
||||
place_of_supply = get_place_of_supply(invoice, invoice.doctype)
|
||||
if place_of_supply:
|
||||
place_of_supply = place_of_supply.split('-')[0]
|
||||
else:
|
||||
place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
|
||||
buyer_details.update(dict(place_of_supply=place_of_supply))
|
||||
|
||||
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
|
||||
@ -343,7 +348,7 @@ def make_einvoice(invoice):
|
||||
if invoice.gst_category == 'Overseas':
|
||||
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
|
||||
else:
|
||||
shipping_details = get_party_details(invoice.shipping_address_name)
|
||||
shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1)
|
||||
|
||||
if invoice.is_pos and invoice.base_paid_amount:
|
||||
payment_details = get_payment_details(invoice)
|
||||
@ -391,7 +396,9 @@ def safe_json_load(json_string):
|
||||
snippet = json_string[start:end]
|
||||
frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
|
||||
|
||||
def validate_einvoice(validations, einvoice, errors=[]):
|
||||
def validate_einvoice(validations, einvoice, errors=None):
|
||||
if errors is None:
|
||||
errors = []
|
||||
for fieldname, field_validation in validations.items():
|
||||
value = einvoice.get(fieldname, None)
|
||||
if not value or value == "None":
|
||||
@ -780,6 +787,8 @@ class GSPConnector():
|
||||
|
||||
self.invoice.irn = res.get('Irn')
|
||||
self.invoice.ewaybill = res.get('EwbNo')
|
||||
self.invoice.ack_no = res.get('AckNo')
|
||||
self.invoice.ack_date = res.get('AckDt')
|
||||
self.invoice.signed_einvoice = dec_signed_invoice
|
||||
self.invoice.signed_qr_code = res.get('SignedQRCode')
|
||||
|
||||
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe, os, json
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from erpnext.regional.india import states
|
||||
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
|
||||
@ -18,6 +19,7 @@ def setup(company=None, patch=True):
|
||||
# TODO: for all countries
|
||||
def setup_company_independent_fixtures():
|
||||
make_custom_fields()
|
||||
make_property_setters()
|
||||
add_permissions()
|
||||
add_custom_roles_for_reports()
|
||||
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
|
||||
@ -110,6 +112,11 @@ def add_print_formats():
|
||||
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
|
||||
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
|
||||
|
||||
def make_property_setters():
|
||||
# GST rules do not allow for an invoice no. bigger than 16 characters
|
||||
make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
|
||||
make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
|
||||
@ -860,4 +867,4 @@ def create_gratuity_rule():
|
||||
})
|
||||
|
||||
rule.flags.ignore_mandatory = True
|
||||
rule.save()
|
||||
rule.save()
|
||||
|
@ -44,7 +44,7 @@ class Gstr2Report(Gstr1Report):
|
||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||
invoice_details = self.invoices.get(inv)
|
||||
for rate, items in items_based_on_rate.items():
|
||||
if rate:
|
||||
if rate or invoice_details.get('gst_category') == 'Registered Composition':
|
||||
if inv not in self.igst_invoices:
|
||||
rate = rate / 2
|
||||
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
|
||||
@ -86,7 +86,7 @@ class Gstr2Report(Gstr1Report):
|
||||
conditions += opts[1]
|
||||
|
||||
if self.filters.get("type_of_business") == "B2B":
|
||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 "
|
||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ', 'Registered Composition') and is_return != 1 "
|
||||
|
||||
elif self.filters.get("type_of_business") == "CDNR":
|
||||
conditions += """ and is_return = 1 """
|
||||
|
@ -230,13 +230,20 @@ class Customer(TransactionBase):
|
||||
frappe.db.set(self, "customer_name", newdn)
|
||||
|
||||
def set_loyalty_program(self):
|
||||
if self.loyalty_program: return
|
||||
if self.loyalty_program:
|
||||
return
|
||||
|
||||
loyalty_program = get_loyalty_programs(self)
|
||||
if not loyalty_program: return
|
||||
if not loyalty_program:
|
||||
return
|
||||
|
||||
if len(loyalty_program) == 1:
|
||||
self.loyalty_program = loyalty_program[0]
|
||||
else:
|
||||
frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
|
||||
frappe.msgprint(
|
||||
_("Multiple Loyalty Programs found for Customer {}. Please select manually.")
|
||||
.format(frappe.bold(self.customer_name))
|
||||
)
|
||||
|
||||
def create_onboarding_docs(self, args):
|
||||
defaults = frappe.defaults.get_defaults()
|
||||
@ -340,7 +347,6 @@ def _set_missing_values(source, target):
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_programs(doc):
|
||||
''' returns applicable loyalty programs for a customer '''
|
||||
from frappe.desk.treeview import get_children
|
||||
|
||||
lp_details = []
|
||||
loyalty_programs = frappe.get_all("Loyalty Program",
|
||||
@ -349,15 +355,33 @@ def get_loyalty_programs(doc):
|
||||
"ifnull(to_date, '2500-01-01')": [">=", today()]})
|
||||
|
||||
for loyalty_program in loyalty_programs:
|
||||
customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
|
||||
customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
|
||||
|
||||
if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
|
||||
and (not loyalty_program.customer_territory or doc.territory in customer_territories):
|
||||
if (
|
||||
(not loyalty_program.customer_group
|
||||
or doc.customer_group in get_nested_links(
|
||||
"Customer Group",
|
||||
loyalty_program.customer_group,
|
||||
doc.flags.ignore_permissions
|
||||
))
|
||||
and (not loyalty_program.customer_territory
|
||||
or doc.territory in get_nested_links(
|
||||
"Territory",
|
||||
loyalty_program.customer_territory,
|
||||
doc.flags.ignore_permissions
|
||||
))
|
||||
):
|
||||
lp_details.append(loyalty_program.name)
|
||||
|
||||
return lp_details
|
||||
|
||||
def get_nested_links(link_doctype, link_name, ignore_permissions=False):
|
||||
from frappe.desk.treeview import _get_children
|
||||
|
||||
links = [link_name]
|
||||
for d in _get_children(link_doctype, link_name, ignore_permissions):
|
||||
links.append(d.value)
|
||||
|
||||
return links
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
|
||||
@ -572,4 +596,4 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
|
||||
""", {
|
||||
'customer': customer,
|
||||
'txt': '%%%s%%' % txt
|
||||
})
|
||||
})
|
||||
|
@ -150,7 +150,7 @@ class SalesOrder(SellingController):
|
||||
if enq:
|
||||
frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
|
||||
|
||||
def update_prevdoc_status(self, flag):
|
||||
def update_prevdoc_status(self, flag=None):
|
||||
for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
|
||||
if quotation:
|
||||
doc = frappe.get_doc("Quotation", quotation)
|
||||
@ -779,6 +779,7 @@ def get_events(start, end, filters=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None):
|
||||
"""Creates Purchase Order for each Supplier. Returns a list of doc objects."""
|
||||
if not selected_items: return
|
||||
|
||||
if isinstance(selected_items, string_types):
|
||||
@ -821,15 +822,16 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
|
||||
target.project = source_parent.project
|
||||
|
||||
suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
|
||||
suppliers = list(set(suppliers))
|
||||
suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')]
|
||||
suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order
|
||||
|
||||
items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
|
||||
items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')]
|
||||
items_to_map = list(set(items_to_map))
|
||||
|
||||
if not suppliers:
|
||||
frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
|
||||
|
||||
purchase_orders = []
|
||||
for supplier in suppliers:
|
||||
doc = get_mapped_doc("Sales Order", source_name, {
|
||||
"Sales Order": {
|
||||
@ -873,7 +875,9 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
|
||||
doc.insert()
|
||||
frappe.db.commit()
|
||||
return doc
|
||||
purchase_orders.append(doc)
|
||||
|
||||
return purchase_orders
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
|
@ -341,6 +341,9 @@ class TestSalesOrder(unittest.TestCase):
|
||||
prev_total = so.get("base_total")
|
||||
prev_total_in_words = so.get("base_in_words")
|
||||
|
||||
# get reserved qty before update items
|
||||
reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
|
||||
|
||||
first_item_of_so = so.get("items")[0]
|
||||
trans_item = json.dumps([
|
||||
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
|
||||
@ -354,6 +357,10 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertEqual(so.get("items")[-1].rate, 200)
|
||||
self.assertEqual(so.get("items")[-1].qty, 7)
|
||||
self.assertEqual(so.get("items")[-1].amount, 1400)
|
||||
|
||||
# reserved qty should increase after adding row
|
||||
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7)
|
||||
|
||||
self.assertEqual(so.status, 'To Deliver and Bill')
|
||||
|
||||
updated_total = so.get("base_total")
|
||||
@ -373,6 +380,9 @@ class TestSalesOrder(unittest.TestCase):
|
||||
create_dn_against_so(so.name, 2)
|
||||
make_sales_invoice(so.name)
|
||||
|
||||
# get reserved qty before update items
|
||||
reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
|
||||
|
||||
# add an item so as to try removing items
|
||||
trans_item = json.dumps([
|
||||
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
|
||||
@ -382,6 +392,9 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.reload()
|
||||
self.assertEqual(len(so.get("items")), 2)
|
||||
|
||||
# reserved qty should increase after adding row
|
||||
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2)
|
||||
|
||||
# check if delivered items can be removed
|
||||
trans_item = json.dumps([{
|
||||
"item_code": '_Test Item 2',
|
||||
@ -402,6 +415,10 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(len(so.get("items")), 1)
|
||||
|
||||
# reserved qty should decrease (back to initial) after deleting row
|
||||
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item)
|
||||
|
||||
self.assertEqual(so.status, 'To Deliver and Bill')
|
||||
|
||||
|
||||
@ -503,12 +520,18 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
so = make_sales_order(item_code = "_Test Item", warehouse=None)
|
||||
|
||||
# get reserved qty of packed item
|
||||
existing_reserved_qty = get_reserved_qty("_Packed Item")
|
||||
|
||||
added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}])
|
||||
update_child_qty_rate('Sales Order', added_item, so.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.packed_items[0].qty, 4)
|
||||
|
||||
# reserved qty in packed item should increase after adding bundle item
|
||||
self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4)
|
||||
|
||||
# test uom and conversion factor change
|
||||
update_uom_conv_factor = json.dumps([{
|
||||
'item_code': so.get("items")[0].item_code,
|
||||
@ -523,6 +546,9 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.packed_items[0].qty, 8)
|
||||
|
||||
# reserved qty in packed item should increase after changing bundle item uom
|
||||
self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8)
|
||||
|
||||
def test_update_child_with_tax_template(self):
|
||||
"""
|
||||
Test Action: Create a SO with one item having its tax account head already in the SO.
|
||||
@ -736,7 +762,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so = make_sales_order(item_list=so_items, do_not_submit=True)
|
||||
so.submit()
|
||||
|
||||
po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
|
||||
po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
|
||||
po.submit()
|
||||
|
||||
dn = create_dn_against_so(so.name, delivered_qty=2)
|
||||
@ -818,7 +844,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.submit()
|
||||
|
||||
# create po for only one item
|
||||
po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
|
||||
po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
|
||||
po1.submit()
|
||||
|
||||
self.assertEqual(so.customer, po1.customer)
|
||||
@ -828,7 +854,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertEqual(len(po1.items), 1)
|
||||
|
||||
# create po for remaining item
|
||||
po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
|
||||
po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0]
|
||||
po2.submit()
|
||||
|
||||
# teardown
|
||||
@ -839,6 +865,45 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.load_from_db()
|
||||
so.cancel()
|
||||
|
||||
def test_drop_shipping_full_for_default_suppliers(self):
|
||||
"""Test if multiple POs are generated in one go against different default suppliers."""
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier
|
||||
|
||||
if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"):
|
||||
make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
|
||||
|
||||
if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"):
|
||||
make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
|
||||
|
||||
so_items = [
|
||||
{
|
||||
"item_code": "_Test Item for Drop Shipping 1",
|
||||
"warehouse": "",
|
||||
"qty": 2,
|
||||
"rate": 400,
|
||||
"delivered_by_supplier": 1,
|
||||
"supplier": '_Test Supplier'
|
||||
},
|
||||
{
|
||||
"item_code": "_Test Item for Drop Shipping 2",
|
||||
"warehouse": "",
|
||||
"qty": 2,
|
||||
"rate": 400,
|
||||
"delivered_by_supplier": 1,
|
||||
"supplier": '_Test Supplier 1'
|
||||
}
|
||||
]
|
||||
|
||||
# create so and po
|
||||
so = make_sales_order(item_list=so_items, do_not_submit=True)
|
||||
so.submit()
|
||||
|
||||
purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items)
|
||||
|
||||
self.assertEqual(len(purchase_orders), 2)
|
||||
self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
|
||||
self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
|
||||
|
||||
def test_reserved_qty_for_closing_so(self):
|
||||
bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
||||
fields=["reserved_qty"])
|
||||
|
@ -259,6 +259,7 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["default_payroll_payable_account", {"root_type": "Liability"}],
|
||||
["round_off_account", {"root_type": "Expense"}],
|
||||
["write_off_account", {"root_type": "Expense"}],
|
||||
["default_discount_account", {}],
|
||||
["discount_allowed_account", {"root_type": "Expense"}],
|
||||
["discount_received_account", {"root_type": "Income"}],
|
||||
["exchange_gain_loss_account", {"root_type": "Expense"}],
|
||||
@ -275,7 +276,7 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
|
||||
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
|
||||
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
|
||||
["unrealized_profit_loss_account", {"root_type": "Liability"}]
|
||||
["unrealized_profit_loss_account", {"root_type": "Liability"},]
|
||||
], function(i, v) {
|
||||
erpnext.company.set_custom_query(frm, v);
|
||||
});
|
||||
|
@ -59,6 +59,7 @@
|
||||
"default_deferred_expense_account",
|
||||
"default_payroll_payable_account",
|
||||
"default_expense_claim_payable_account",
|
||||
"default_discount_account",
|
||||
"section_break_22",
|
||||
"cost_center",
|
||||
"column_break_26",
|
||||
@ -733,6 +734,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Unrealized Profit / Loss Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Payment Discount Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-building",
|
||||
|
@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', {
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "UOM Conversion Factor",
|
||||
filters: { "category": "Length" },
|
||||
filters: { "category": __("Length") },
|
||||
fields: ["to_uom"],
|
||||
limit_page_length: 500
|
||||
},
|
||||
|
@ -16,6 +16,11 @@ class TestShoppingCart(unittest.TestCase):
|
||||
Note:
|
||||
Shopping Cart == Quotation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
create_test_contact_and_address()
|
||||
@ -51,8 +56,8 @@ class TestShoppingCart(unittest.TestCase):
|
||||
def test_add_to_cart(self):
|
||||
self.login_as_customer()
|
||||
|
||||
# remove from cart
|
||||
self.remove_all_items_from_cart()
|
||||
# clear existing quotations
|
||||
self.clear_existing_quotations()
|
||||
|
||||
# add first item
|
||||
update_cart("_Test Item", 1)
|
||||
@ -100,6 +105,7 @@ class TestShoppingCart(unittest.TestCase):
|
||||
self.assertEqual(len(quotation.get("items")), 1)
|
||||
|
||||
def test_tax_rule(self):
|
||||
self.create_tax_rule()
|
||||
self.login_as_customer()
|
||||
quotation = self.create_quotation()
|
||||
|
||||
@ -115,6 +121,13 @@ class TestShoppingCart(unittest.TestCase):
|
||||
|
||||
self.remove_test_quotation(quotation)
|
||||
|
||||
def create_tax_rule(self):
|
||||
tax_rule = frappe.get_test_records("Tax Rule")[0]
|
||||
try:
|
||||
frappe.get_doc(tax_rule).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def create_quotation(self):
|
||||
quotation = frappe.new_doc("Quotation")
|
||||
|
||||
@ -195,10 +208,15 @@ class TestShoppingCart(unittest.TestCase):
|
||||
"_Test Contact For _Test Customer")
|
||||
frappe.set_user("test_contact_customer@example.com")
|
||||
|
||||
def remove_all_items_from_cart(self):
|
||||
quotation = _get_cart_quotation()
|
||||
quotation.flags.ignore_permissions=True
|
||||
quotation.delete()
|
||||
def clear_existing_quotations(self):
|
||||
quotations = frappe.get_all("Quotation", filters={
|
||||
"party_name": get_party().name,
|
||||
"order_type": "Shopping Cart",
|
||||
"docstatus": 0
|
||||
}, order_by="modified desc", pluck="name")
|
||||
|
||||
for quotation in quotations:
|
||||
frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
|
||||
|
||||
def create_user_if_not_exists(self, email, first_name = None):
|
||||
if frappe.db.exists("User", email):
|
||||
|
@ -1,14 +1,14 @@
|
||||
frappe.provide('erpnext.stock');
|
||||
|
||||
erpnext.stock.ItemDashboard = Class.extend({
|
||||
init: function(opts) {
|
||||
init: function (opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
},
|
||||
make: function() {
|
||||
make: function () {
|
||||
var me = this;
|
||||
this.start = 0;
|
||||
if(!this.sort_by) {
|
||||
if (!this.sort_by) {
|
||||
this.sort_by = 'projected_qty';
|
||||
this.sort_order = 'asc';
|
||||
}
|
||||
@ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
|
||||
this.result = this.content.find('.result');
|
||||
|
||||
this.content.on('click', '.btn-move', function() {
|
||||
handle_move_add($(this), "Move")
|
||||
this.content.on('click', '.btn-move', function () {
|
||||
handle_move_add($(this), "Move");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-add', function() {
|
||||
handle_move_add($(this), "Add")
|
||||
this.content.on('click', '.btn-add', function () {
|
||||
handle_move_add($(this), "Add");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-edit', function() {
|
||||
this.content.on('click', '.btn-edit', function () {
|
||||
let item = unescape($(this).attr('data-item'));
|
||||
let warehouse = unescape($(this).attr('data-warehouse'));
|
||||
let company = unescape($(this).attr('data-company'));
|
||||
frappe.db.get_value('Putaway Rule',
|
||||
{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
});
|
||||
frappe.db.get_value('Putaway Rule', {
|
||||
'item_code': item,
|
||||
'warehouse': warehouse,
|
||||
'company': company
|
||||
}, 'name', (r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
});
|
||||
});
|
||||
|
||||
function handle_move_add(element, action) {
|
||||
@ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
let warehouse = unescape(element.attr('data-warehouse'));
|
||||
let actual_qty = unescape(element.attr('data-actual_qty'));
|
||||
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
|
||||
let entry_type = action === "Move" ? "Material Transfer": null;
|
||||
let entry_type = action === "Move" ? "Material Transfer" : null;
|
||||
|
||||
if (disable_quick_entry) {
|
||||
open_stock_entry(item, warehouse, entry_type);
|
||||
} else {
|
||||
if (action === "Add") {
|
||||
let rate = unescape($(this).attr('data-rate'));
|
||||
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); });
|
||||
}
|
||||
else {
|
||||
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); });
|
||||
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
|
||||
me.refresh();
|
||||
});
|
||||
} else {
|
||||
erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () {
|
||||
me.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function open_stock_entry(item, warehouse, entry_type) {
|
||||
frappe.model.with_doctype('Stock Entry', function() {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
if (entry_type) doc.stock_entry_type = entry_type;
|
||||
|
||||
@ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
row.s_warehouse = warehouse;
|
||||
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// more
|
||||
this.content.find('.btn-more').on('click', function() {
|
||||
this.content.find('.btn-more').on('click', function () {
|
||||
me.start += me.page_length;
|
||||
me.refresh();
|
||||
});
|
||||
|
||||
},
|
||||
refresh: function() {
|
||||
if(this.before_refresh) {
|
||||
refresh: function () {
|
||||
if (this.before_refresh) {
|
||||
this.before_refresh();
|
||||
}
|
||||
|
||||
@ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
frappe.call({
|
||||
method: this.method,
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
me.render(r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
render: function(data) {
|
||||
if (this.start===0) {
|
||||
render: function (data) {
|
||||
if (this.start === 0) {
|
||||
this.max_count = 0;
|
||||
this.result.empty();
|
||||
}
|
||||
@ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
this.max_count = this.max_count;
|
||||
|
||||
// show more button
|
||||
if (data && data.length===(this.page_length + 1)) {
|
||||
if (data && data.length === (this.page_length + 1)) {
|
||||
this.content.find('.more').removeClass('hidden');
|
||||
|
||||
// remove the last element
|
||||
@ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
get_item_dashboard_data: function(data, max_count, show_item) {
|
||||
if(!max_count) max_count = 0;
|
||||
if(!data) data = [];
|
||||
get_item_dashboard_data: function (data, max_count, show_item) {
|
||||
if (!max_count) max_count = 0;
|
||||
if (!data) data = [];
|
||||
|
||||
data.forEach(function(d) {
|
||||
data.forEach(function (d) {
|
||||
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
d.pending_qty = 0;
|
||||
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
if(d.actual_or_pending > d.actual_qty) {
|
||||
if (d.actual_or_pending > d.actual_qty) {
|
||||
d.pending_qty = d.actual_or_pending - d.actual_qty;
|
||||
}
|
||||
|
||||
@ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
return {
|
||||
data: data,
|
||||
max_count: max_count,
|
||||
can_write:can_write,
|
||||
can_write: can_write,
|
||||
show_item: show_item || false
|
||||
};
|
||||
},
|
||||
|
||||
get_capacity_dashboard_data: function(data) {
|
||||
get_capacity_dashboard_data: function (data) {
|
||||
if (!data) data = [];
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
|
||||
data.forEach(function (d) {
|
||||
d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef";
|
||||
});
|
||||
|
||||
let can_write = 0;
|
||||
@ -185,53 +191,77 @@ erpnext.stock.ItemDashboard = Class.extend({
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
|
||||
erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: target ? __('Add Item') : __('Move Item'),
|
||||
fields: [
|
||||
{fieldname: 'item_code', label: __('Item'),
|
||||
fieldtype: 'Link', options: 'Item', read_only: 1},
|
||||
{fieldname: 'source', label: __('Source Warehouse'),
|
||||
fieldtype: 'Link', options: 'Warehouse', read_only: 1},
|
||||
{fieldname: 'target', label: __('Target Warehouse'),
|
||||
fieldtype: 'Link', options: 'Warehouse', reqd: 1},
|
||||
{fieldname: 'qty', label: __('Quantity'), reqd: 1,
|
||||
fieldtype: 'Float', description: __('Available {0}', [actual_qty]) },
|
||||
{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 },
|
||||
fields: [{
|
||||
fieldname: 'item_code',
|
||||
label: __('Item'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Item',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'source',
|
||||
label: __('Source Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'target',
|
||||
label: __('Target Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'qty',
|
||||
label: __('Quantity'),
|
||||
reqd: 1,
|
||||
fieldtype: 'Float',
|
||||
description: __('Available {0}', [actual_qty])
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: __('Rate'),
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
dialog.show();
|
||||
dialog.get_field('item_code').set_input(item);
|
||||
|
||||
if(source) {
|
||||
if (source) {
|
||||
dialog.get_field('source').set_input(source);
|
||||
} else {
|
||||
dialog.get_field('source').df.hidden = 1;
|
||||
dialog.get_field('source').refresh();
|
||||
}
|
||||
|
||||
if(rate) {
|
||||
if (rate) {
|
||||
dialog.get_field('rate').set_value(rate);
|
||||
dialog.get_field('rate').df.hidden = 0;
|
||||
dialog.get_field('rate').refresh();
|
||||
}
|
||||
|
||||
if(target) {
|
||||
if (target) {
|
||||
dialog.get_field('target').df.read_only = 1;
|
||||
dialog.get_field('target').value = target;
|
||||
dialog.get_field('target').refresh();
|
||||
}
|
||||
|
||||
dialog.set_primary_action(__('Submit'), function() {
|
||||
dialog.set_primary_action(__('Submit'), function () {
|
||||
var values = dialog.get_values();
|
||||
if(!values) {
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
if(source && values.qty > actual_qty) {
|
||||
if (source && values.qty > actual_qty) {
|
||||
frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
|
||||
return;
|
||||
}
|
||||
if(values.source === values.target) {
|
||||
if (values.source === values.target) {
|
||||
frappe.msgprint(__('Source and target warehouse must be different'));
|
||||
}
|
||||
|
||||
@ -239,21 +269,21 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
|
||||
args: values,
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
frappe.show_alert(__('Stock Entry {0} created',
|
||||
['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
|
||||
['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
|
||||
dialog.hide();
|
||||
callback(r);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$('<p style="margin-left: 10px;"><a class="link-open text-muted small">'
|
||||
+ __("Add more items or open full form") + '</a></p>')
|
||||
$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
|
||||
__("Add more items or open full form") + '</a></p>')
|
||||
.appendTo(dialog.body)
|
||||
.find('.link-open')
|
||||
.on('click', function() {
|
||||
frappe.model.with_doctype('Stock Entry', function() {
|
||||
.on('click', function () {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
doc.from_warehouse = dialog.get_value('source');
|
||||
doc.to_warehouse = dialog.get_value('target');
|
||||
@ -266,6 +296,6 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb
|
||||
row.transfer_qty = dialog.get_value('qty');
|
||||
row.basic_rate = dialog.get_value('rate');
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.db_query import DatabaseQuery
|
||||
from frappe.utils import flt, cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_data(item_code=None, warehouse=None, item_group=None,
|
||||
@ -42,11 +43,20 @@ def get_data(item_code=None, warehouse=None, item_group=None,
|
||||
limit_start=start,
|
||||
limit_page_length='21')
|
||||
|
||||
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
||||
|
||||
for item in items:
|
||||
item.update({
|
||||
'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
|
||||
'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
|
||||
or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
|
||||
'item_name': frappe.get_cached_value(
|
||||
"Item", item.item_code, 'item_name'),
|
||||
'disable_quick_entry': frappe.get_cached_value(
|
||||
"Item", item.item_code, 'has_batch_no')
|
||||
or frappe.get_cached_value(
|
||||
"Item", item.item_code, 'has_serial_no'),
|
||||
'projected_qty': flt(item.projected_qty, precision),
|
||||
'reserved_qty': flt(item.reserved_qty, precision),
|
||||
'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
|
||||
'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
|
||||
'actual_qty': flt(item.actual_qty, precision),
|
||||
})
|
||||
|
||||
return items
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "MAT-BIN-.YYYY.-.#####",
|
||||
"creation": "2013-01-10 16:34:25",
|
||||
"doctype": "DocType",
|
||||
@ -112,7 +113,8 @@
|
||||
{
|
||||
"fieldname": "reserved_qty_for_sub_contract",
|
||||
"fieldtype": "Float",
|
||||
"label": "Reserved Qty for sub contract"
|
||||
"label": "Reserved Qty for sub contract",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ma_rate",
|
||||
@ -166,7 +168,8 @@
|
||||
"hide_toolbar": 1,
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"modified": "2019-11-18 18:34:59.456882",
|
||||
"links": [],
|
||||
"modified": "2021-03-30 23:09:39.572776",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Bin",
|
||||
@ -196,5 +199,6 @@
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "item_code,warehouse",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
}
|
@ -101,7 +101,7 @@ class DeliveryNote(SellingController):
|
||||
for f in fieldname:
|
||||
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
|
||||
|
||||
super(DeliveryNote, self).before_print()
|
||||
super(DeliveryNote, self).before_print(settings)
|
||||
|
||||
def set_actual_qty(self):
|
||||
for d in self.get('items'):
|
||||
|
@ -12,6 +12,7 @@
|
||||
"item_name": "_Test Item",
|
||||
"apply_warehouse_wise_reorder_level": 1,
|
||||
"gst_hsn_code": "999800",
|
||||
"opening_stock": 10,
|
||||
"valuation_rate": 100,
|
||||
"item_defaults": [{
|
||||
"company": "_Test Company",
|
||||
|
@ -354,6 +354,10 @@ frappe.ui.form.on('Material Request', {
|
||||
},
|
||||
material_request_type: function(frm) {
|
||||
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
|
||||
|
||||
if (frm.doc.material_request_type !== 'Material Transfer' && frm.doc.set_from_warehouse) {
|
||||
frm.set_value('set_from_warehouse', '');
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -20,9 +20,9 @@
|
||||
"company",
|
||||
"amended_from",
|
||||
"warehouse_section",
|
||||
"set_warehouse",
|
||||
"column_break5",
|
||||
"set_from_warehouse",
|
||||
"column_break5",
|
||||
"set_warehouse",
|
||||
"items_section",
|
||||
"scan_barcode",
|
||||
"items",
|
||||
@ -314,7 +314,7 @@
|
||||
"idx": 70,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-19 01:04:09.285862",
|
||||
"modified": "2021-03-31 23:52:55.392512",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user