mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-30 15:09:47 +00:00
Merge branch 'develop' into improve_taxes_setup
This commit is contained in:
@@ -214,6 +214,7 @@ class Account(NestedSet):
|
||||
if parent_value_changed:
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_group_to_ledger(self):
|
||||
if self.check_if_child_exists():
|
||||
throw(_("Account with child nodes cannot be converted to ledger"))
|
||||
@@ -224,6 +225,7 @@ class Account(NestedSet):
|
||||
self.save()
|
||||
return 1
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_ledger_to_group(self):
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction can not be converted to group."))
|
||||
|
||||
@@ -39,6 +39,7 @@ class AccountingPeriod(Document):
|
||||
frappe.throw(_("Accounting Period overlaps with {0}")
|
||||
.format(existing_accounting_period[0].get("name")), OverlapError)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctypes_for_closing(self):
|
||||
docs_for_closing = []
|
||||
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
|
||||
|
||||
@@ -11,36 +11,36 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
class TestAccountingPeriod(unittest.TestCase):
|
||||
def test_overlap(self):
|
||||
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||
end_date = "2018-06-30", company = "Wind Power LLC")
|
||||
ap1.save()
|
||||
def test_overlap(self):
|
||||
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||
end_date = "2018-06-30", company = "Wind Power LLC")
|
||||
ap1.save()
|
||||
|
||||
ap2 = create_accounting_period(start_date = "2018-06-30",
|
||||
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
|
||||
self.assertRaises(OverlapError, ap2.save)
|
||||
ap2 = create_accounting_period(start_date = "2018-06-30",
|
||||
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
|
||||
self.assertRaises(OverlapError, ap2.save)
|
||||
|
||||
def test_accounting_period(self):
|
||||
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
|
||||
ap1.save()
|
||||
def test_accounting_period(self):
|
||||
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
|
||||
ap1.save()
|
||||
|
||||
doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
||||
doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
||||
|
||||
def tearDown(self):
|
||||
for d in frappe.get_all("Accounting Period"):
|
||||
frappe.delete_doc("Accounting Period", d.name)
|
||||
def tearDown(self):
|
||||
for d in frappe.get_all("Accounting Period"):
|
||||
frappe.delete_doc("Accounting Period", d.name)
|
||||
|
||||
def create_accounting_period(**args):
|
||||
args = frappe._dict(args)
|
||||
args = frappe._dict(args)
|
||||
|
||||
accounting_period = frappe.new_doc("Accounting Period")
|
||||
accounting_period.start_date = args.start_date or nowdate()
|
||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.append("closed_documents", {
|
||||
"document_type": 'Sales Invoice', "closed": 1
|
||||
})
|
||||
accounting_period = frappe.new_doc("Accounting Period")
|
||||
accounting_period.start_date = args.start_date or nowdate()
|
||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.append("closed_documents", {
|
||||
"document_type": 'Sales Invoice', "closed": 1
|
||||
})
|
||||
|
||||
return accounting_period
|
||||
return accounting_period
|
||||
|
||||
@@ -42,10 +42,9 @@ let add_fields_to_mapping_table = function (frm) {
|
||||
});
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
|
||||
frm.doc.name).options = options;
|
||||
|
||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
||||
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
||||
'bank_transaction_field', 'options', options
|
||||
);
|
||||
};
|
||||
|
||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
|
||||
@@ -12,6 +12,7 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class BankClearance(Document):
|
||||
@frappe.whitelist()
|
||||
def get_payment_entries(self):
|
||||
if not (self.from_date and self.to_date):
|
||||
frappe.throw(_("From Date and To Date are Mandatory"))
|
||||
@@ -108,6 +109,7 @@ class BankClearance(Document):
|
||||
row.update(d)
|
||||
self.total_amount += flt(amount)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_clearance_date(self):
|
||||
clearance_date_updated = False
|
||||
for d in self.get('payment_entries'):
|
||||
|
||||
@@ -532,43 +532,4 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
</table>
|
||||
`);
|
||||
},
|
||||
|
||||
show_missing_link_values(frm, missing_link_values) {
|
||||
let can_be_created_automatically = missing_link_values.every(
|
||||
(d) => d.has_one_mandatory_field
|
||||
);
|
||||
|
||||
let html = missing_link_values
|
||||
.map((d) => {
|
||||
let doctype = d.doctype;
|
||||
let values = d.missing_values;
|
||||
return `
|
||||
<h5>${doctype}</h5>
|
||||
<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
if (can_be_created_automatically) {
|
||||
// prettier-ignore
|
||||
let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
|
||||
frappe.confirm(message + html, () => {
|
||||
frm.call("create_missing_link_values", {
|
||||
missing_link_values,
|
||||
}).then((r) => {
|
||||
let records = r.message;
|
||||
frappe.msgprint(__(
|
||||
"Created {0} records successfully.", [
|
||||
records.length,
|
||||
]
|
||||
));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(
|
||||
// prettier-ignore
|
||||
__('The following records needs to be created before we can import your file.') + html
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -175,22 +175,24 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "deposit",
|
||||
"oldfieldname": "debit",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Deposit"
|
||||
"label": "Deposit",
|
||||
"oldfieldname": "debit",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "withdrawal",
|
||||
"oldfieldname": "credit",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Withdrawal"
|
||||
"label": "Withdrawal",
|
||||
"oldfieldname": "credit",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-30 19:40:54.221070",
|
||||
"modified": "2021-04-14 17:31:58.963529",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,6 +57,7 @@ class CForm(Document):
|
||||
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
||||
frappe.db.set(self, 'total_invoiced_amount', total)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoice_details(self, invoice_no):
|
||||
""" Pull details from invoices for referrence """
|
||||
if invoice_no:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ class CostCenter(NestedSet):
|
||||
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
|
||||
frappe.bold(self.parent_cost_center)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_group_to_ledger(self):
|
||||
if self.check_if_child_exists():
|
||||
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
||||
@@ -60,6 +61,7 @@ class CostCenter(NestedSet):
|
||||
self.save()
|
||||
return 1
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_ledger_to_group(self):
|
||||
if cint(self.enable_distributed_cost_center):
|
||||
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
|
||||
|
||||
@@ -27,6 +27,7 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_accounts_data(self, account=None):
|
||||
accounts = []
|
||||
self.validate_mandatory()
|
||||
@@ -95,6 +96,7 @@ class ExchangeRateRevaluation(Document):
|
||||
message = _("No outstanding invoices found")
|
||||
frappe.msgprint(message)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_jv_entry(self):
|
||||
if self.total_gain_loss == 0:
|
||||
return
|
||||
|
||||
@@ -12,6 +12,7 @@ from frappe.model.document import Document
|
||||
class FiscalYearIncorrectDate(frappe.ValidationError): pass
|
||||
|
||||
class FiscalYear(Document):
|
||||
@frappe.whitelist()
|
||||
def set_as_default(self):
|
||||
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
@@ -54,7 +55,7 @@ class FiscalYear(Document):
|
||||
def on_update(self):
|
||||
check_duplicate_fiscal_year(self)
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
|
||||
def on_trash(self):
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
if global_defaults.current_fiscal_year == self.name:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_disbursement_entry(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = 'Journal Entry'
|
||||
@@ -174,6 +175,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
return je
|
||||
|
||||
@frappe.whitelist()
|
||||
def close_loan(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = 'Journal Entry'
|
||||
|
||||
@@ -327,18 +327,16 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
},
|
||||
|
||||
setup_balance_formatter: function() {
|
||||
var me = this;
|
||||
$.each(["balance", "party_balance"], function(i, field) {
|
||||
var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name);
|
||||
df.formatter = function(value, df, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(df, doc);
|
||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||
return "<div style='text-align: right'>"
|
||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||
+ " " + dr_or_cr
|
||||
+ "</div>";
|
||||
}
|
||||
})
|
||||
const formatter = function(value, df, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(df, doc);
|
||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||
return "<div style='text-align: right'>"
|
||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||
+ " " + dr_or_cr
|
||||
+ "</div>";
|
||||
};
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
|
||||
},
|
||||
|
||||
reference_name: function(doc, cdt, cdn) {
|
||||
@@ -431,15 +429,6 @@ cur_frm.cscript.validate = function(doc,cdt,cdn) {
|
||||
cur_frm.cscript.update_totals(doc);
|
||||
}
|
||||
|
||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
if(doc.select_print_heading){
|
||||
// print heading
|
||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
||||
}
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Journal Entry");
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Journal Entry Account", {
|
||||
party: function(frm, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
@@ -511,8 +500,11 @@ $.extend(erpnext.journal_entry, {
|
||||
};
|
||||
|
||||
$.each(field_label_map, function (fieldname, label) {
|
||||
var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name);
|
||||
df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label;
|
||||
frm.fields_dict.accounts.grid.update_docfield_property(
|
||||
fieldname,
|
||||
'label',
|
||||
frm.doc.multi_currency ? (label + " in Account Currency") : label
|
||||
);
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -564,6 +564,7 @@ class JournalEntry(AccountsController):
|
||||
if gl_map:
|
||||
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance(self):
|
||||
if not self.get('accounts'):
|
||||
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-24 14:06:54.833738",
|
||||
"modified": "2020-06-26 14:06:54.833738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.utils import (flt, add_months)
|
||||
from frappe.model.document import Document
|
||||
|
||||
class MonthlyDistribution(Document):
|
||||
@frappe.whitelist()
|
||||
def get_months(self):
|
||||
month_list = ['January','February','March','April','May','June','July','August','September',
|
||||
'October','November','December']
|
||||
|
||||
@@ -167,6 +167,7 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
return invoice
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_invoices(self):
|
||||
self.validate_company()
|
||||
invoices = self.get_invoices()
|
||||
|
||||
@@ -6,10 +6,12 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
test_dependencies = ["Customer", "Supplier"]
|
||||
from frappe.cache_manager import clear_doctype_cache
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
||||
|
||||
test_dependencies = ["Customer", "Supplier"]
|
||||
|
||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||
@@ -24,22 +26,25 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
|
||||
def test_opening_sales_invoice_creation(self):
|
||||
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
||||
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||
try:
|
||||
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||
|
||||
self.assertEqual(len(invoices), 2)
|
||||
expected_value = {
|
||||
"keys": ["customer", "outstanding_amount", "status"],
|
||||
0: ["_Test Customer", 300, "Overdue"],
|
||||
1: ["_Test Customer 1", 250, "Overdue"],
|
||||
}
|
||||
self.check_expected_values(invoices, expected_value)
|
||||
self.assertEqual(len(invoices), 2)
|
||||
expected_value = {
|
||||
"keys": ["customer", "outstanding_amount", "status"],
|
||||
0: ["_Test Customer", 300, "Overdue"],
|
||||
1: ["_Test Customer 1", 250, "Overdue"],
|
||||
}
|
||||
self.check_expected_values(invoices, expected_value)
|
||||
|
||||
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||
|
||||
# Check if update stock is not enabled
|
||||
self.assertEqual(si.update_stock, 0)
|
||||
# Check if update stock is not enabled
|
||||
self.assertEqual(si.update_stock, 0)
|
||||
|
||||
property_setter.delete()
|
||||
finally:
|
||||
property_setter.delete()
|
||||
clear_doctype_cache("Sales Invoice")
|
||||
|
||||
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
||||
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
||||
@@ -143,4 +148,4 @@ def make_customer(customer=None):
|
||||
customer.insert(ignore_permissions=True)
|
||||
return customer.name
|
||||
else:
|
||||
return frappe.db.exists("Customer", customer_name)
|
||||
return frappe.db.exists("Customer", customer_name)
|
||||
|
||||
@@ -605,12 +605,22 @@ frappe.ui.form.on('Payment Entry', {
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {"company": frm.doc.company}
|
||||
}
|
||||
}
|
||||
},
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
|
||||
];
|
||||
|
||||
frappe.prompt(fields, function(filters){
|
||||
frappe.flags.allocate_payment_amount = true;
|
||||
frm.events.validate_filters_data(frm, filters);
|
||||
frm.doc.cost_center = filters.cost_center;
|
||||
frm.events.get_outstanding_documents(frm, filters);
|
||||
}, __("Filters"), __("Get Outstanding Documents"));
|
||||
},
|
||||
@@ -627,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, " ")])
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -682,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)
|
||||
@@ -764,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 {
|
||||
@@ -781,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;
|
||||
@@ -792,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));
|
||||
}
|
||||
@@ -1066,11 +1086,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance);
|
||||
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
|
||||
frm.set_value("party_balance", r.message.party_balance);
|
||||
},
|
||||
() => {
|
||||
if(frm.doc.payment_type != "Internal") {
|
||||
frm.clear_table("references");
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -234,8 +234,9 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
});
|
||||
|
||||
if (invoices) {
|
||||
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
|
||||
me.frm.doc.name).options = "\n" + invoices.join("\n");
|
||||
this.frm.fields_dict.payment.grid.update_docfield_property(
|
||||
'invoice_number', 'options', "\n" + invoices.join("\n")
|
||||
);
|
||||
|
||||
$.each(me.frm.doc.payments || [], function(i, p) {
|
||||
if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
||||
|
||||
@@ -11,6 +11,7 @@ from erpnext.accounts.utils import (get_outstanding_invoices,
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||
|
||||
class PaymentReconciliation(Document):
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
self.get_nonreconciled_payment_entries()
|
||||
self.get_invoice_entries()
|
||||
@@ -147,6 +148,7 @@ class PaymentReconciliation(Document):
|
||||
ent.currency = e.get('currency')
|
||||
ent.outstanding_amount = e.get('outstanding_amount')
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile(self, args):
|
||||
for e in self.get('payments'):
|
||||
e.invoice_type = None
|
||||
@@ -197,6 +199,7 @@ class PaymentReconciliation(Document):
|
||||
'difference_account': row.difference_account
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_difference_amount(self, child_row):
|
||||
if child_row.get("reference_type") != 'Payment Entry': return
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
self.validate_pos_closing()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
|
||||
def validate_pos_closing(self):
|
||||
user = frappe.db.sql("""
|
||||
SELECT name FROM `tabPOS Closing Entry`
|
||||
@@ -37,12 +37,12 @@ class POSClosingEntry(StatusUpdater):
|
||||
bold_user = frappe.bold(self.user)
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||
|
||||
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_transactions:
|
||||
invalid_row = {'idx': d.idx}
|
||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||
if pos_invoice.consolidated_invoice:
|
||||
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||
@@ -68,14 +68,15 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_reconciliation_details(self):
|
||||
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||
{"data": self, "currency": currency})
|
||||
|
||||
|
||||
def on_submit(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
unconsolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
|
||||
@@ -5,12 +5,21 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
class TestPOSClosingEntry(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Make stock available for POS Sales
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def test_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
@@ -41,9 +50,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
self.assertEqual(pcv_doc.total_quantity, 2)
|
||||
self.assertEqual(pcv_doc.net_total, 6700)
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def test_cancelling_of_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
@@ -84,8 +90,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
self.assertEqual(si_doc.docstatus, 2)
|
||||
self.assertEqual(pos_inv1.status, 'Paid')
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def init_user_and_profile(**args):
|
||||
user = 'test@example.com'
|
||||
@@ -103,4 +107,4 @@ def init_user_and_profile(**args):
|
||||
|
||||
pos_profile.save()
|
||||
|
||||
return test_user, pos_profile
|
||||
return test_user, pos_profile
|
||||
|
||||
@@ -57,7 +57,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
|
||||
def before_cancel(self):
|
||||
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
|
||||
pos_closing_entry = frappe.get_all(
|
||||
@@ -108,7 +108,6 @@ class POSInvoice(SalesInvoice):
|
||||
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||
if d.batch_no:
|
||||
filters["batch_no"] = d.batch_no
|
||||
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
@@ -221,7 +220,7 @@ class POSInvoice(SalesInvoice):
|
||||
base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
|
||||
if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
|
||||
self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
|
||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||
self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount)
|
||||
|
||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
@@ -355,6 +354,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
return profile
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
@@ -377,12 +377,20 @@ class POSInvoice(SalesInvoice):
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset_mode_of_payments(self):
|
||||
if self.pos_profile:
|
||||
pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
|
||||
update_multi_mode_option(self, pos_profile)
|
||||
self.paid_amount = 0
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
|
||||
for pay in self.payments:
|
||||
if not pay.account:
|
||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_request(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone":
|
||||
@@ -400,7 +408,7 @@ class POSInvoice(SalesInvoice):
|
||||
pay_req.request_phone_payment()
|
||||
|
||||
return pay_req
|
||||
|
||||
|
||||
def get_new_payment_request(self, mop):
|
||||
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
|
||||
"payment_account": mop.account,
|
||||
|
||||
@@ -9,8 +9,20 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
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")
|
||||
|
||||
if frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0)
|
||||
|
||||
def test_timestamp_change(self):
|
||||
w = create_pos_invoice(do_not_save=1)
|
||||
w.docstatus = 0
|
||||
@@ -370,7 +382,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 3470)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
@@ -412,7 +423,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 840)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_merging_with_validate_selling_price(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
@@ -421,10 +431,12 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||
|
||||
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||
item = "Test Selling Price Validation"
|
||||
make_item(item, {"is_stock_item": 1})
|
||||
make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
@@ -438,7 +450,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
})
|
||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
|
||||
pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
||||
})
|
||||
@@ -457,8 +469,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv2.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 400)
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
|
||||
|
||||
def create_pos_invoice(**args):
|
||||
args = frappe._dict(args)
|
||||
@@ -508,4 +518,4 @@ def create_pos_invoice(**args):
|
||||
else:
|
||||
pos_inv.payment_schedule = []
|
||||
|
||||
return pos_inv
|
||||
return pos_inv
|
||||
|
||||
@@ -12,6 +12,7 @@ from frappe.utils.background_jobs import enqueue
|
||||
from frappe.model.mapper import map_doc, map_child_doc
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
import json
|
||||
|
||||
from six import iteritems
|
||||
|
||||
@@ -78,8 +79,11 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
||||
|
||||
sales_invoice.is_consolidated = 1
|
||||
sales_invoice.set_posting_time = 1
|
||||
sales_invoice.posting_date = getdate(self.posting_date)
|
||||
sales_invoice.save()
|
||||
sales_invoice.submit()
|
||||
|
||||
self.consolidated_invoice = sales_invoice.name
|
||||
|
||||
return sales_invoice.name
|
||||
@@ -91,10 +95,13 @@ class POSInvoiceMergeLog(Document):
|
||||
credit_note = self.merge_pos_invoice_into(credit_note, data)
|
||||
|
||||
credit_note.is_consolidated = 1
|
||||
credit_note.set_posting_time = 1
|
||||
credit_note.posting_date = getdate(self.posting_date)
|
||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||
# credit_note.return_against = self.consolidated_invoice
|
||||
credit_note.save()
|
||||
credit_note.submit()
|
||||
|
||||
self.consolidated_credit_note = credit_note.name
|
||||
|
||||
return credit_note.name
|
||||
@@ -131,12 +138,14 @@ class POSInvoiceMergeLog(Document):
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
update_item_wise_tax_detail(t, tax)
|
||||
found = True
|
||||
if not found:
|
||||
tax.charge_type = 'Actual'
|
||||
tax.included_in_print_rate = 0
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||
taxes.append(tax)
|
||||
|
||||
for payment in doc.get('payments'):
|
||||
@@ -168,11 +177,9 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||
sales_invoice.customer = self.customer
|
||||
sales_invoice.is_pos = 1
|
||||
# date can be pos closing date?
|
||||
sales_invoice.posting_date = getdate(nowdate())
|
||||
|
||||
return sales_invoice
|
||||
|
||||
|
||||
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
||||
for doc in invoice_docs:
|
||||
doc.load_from_db()
|
||||
@@ -187,6 +194,26 @@ class POSInvoiceMergeLog(Document):
|
||||
si.flags.ignore_validate = True
|
||||
si.cancel()
|
||||
|
||||
def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
|
||||
consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
|
||||
tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
|
||||
|
||||
if not consolidated_tax_detail:
|
||||
consolidated_tax_detail = {}
|
||||
|
||||
for item_code, tax_data in tax_row_detail.items():
|
||||
if consolidated_tax_detail.get(item_code):
|
||||
consolidated_tax_data = consolidated_tax_detail.get(item_code)
|
||||
consolidated_tax_detail.update({
|
||||
item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
|
||||
})
|
||||
else:
|
||||
consolidated_tax_detail.update({
|
||||
item_code: [tax_data[0], tax_data[1]]
|
||||
})
|
||||
|
||||
consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
|
||||
|
||||
def get_all_unconsolidated_invoices():
|
||||
filters = {
|
||||
'consolidated_invoice': [ 'in', [ '', None ]],
|
||||
@@ -214,7 +241,7 @@ def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
||||
|
||||
if len(invoices) >= 5 and closing_entry:
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
|
||||
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
||||
else:
|
||||
create_merge_logs(invoice_by_customer, closing_entry)
|
||||
|
||||
@@ -227,21 +254,21 @@ def unconsolidate_pos_invoices(closing_entry):
|
||||
|
||||
if len(merge_logs) >= 5:
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
|
||||
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
||||
else:
|
||||
cancel_merge_logs(merge_logs, closing_entry)
|
||||
|
||||
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
||||
for customer, invoices in iteritems(invoice_by_customer):
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(nowdate())
|
||||
merge_log.posting_date = getdate(closing_entry.get('posting_date'))
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
||||
|
||||
merge_log.set('pos_invoices', invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.update_opening_entry()
|
||||
@@ -256,7 +283,7 @@ def cancel_merge_logs(merge_logs, closing_entry={}):
|
||||
closing_entry.set_status(update=True, status='Cancelled')
|
||||
closing_entry.update_opening_entry(for_cancel=True)
|
||||
|
||||
def enqueue_job(job, invoice_by_customer, closing_entry):
|
||||
def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
|
||||
check_scheduler_status()
|
||||
|
||||
job_name = closing_entry.get("name")
|
||||
@@ -269,6 +296,7 @@ def enqueue_job(job, invoice_by_customer, closing_entry):
|
||||
job_name=job_name,
|
||||
closing_entry=closing_entry,
|
||||
invoice_by_customer=invoice_by_customer,
|
||||
merge_logs=merge_logs,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import json
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
@@ -14,85 +15,136 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
def test_consolidated_invoice_creation(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_credit_note_creation(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
||||
})
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
||||
})
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_invoice_item_taxes(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
try:
|
||||
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
|
||||
inv.append("taxes", {
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 9
|
||||
})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
inv2.get('items')[0].item_code = '_Test Item 2'
|
||||
inv2.append("taxes", {
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 5
|
||||
})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
inv.load_from_db()
|
||||
|
||||
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||
item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
|
||||
|
||||
tax_rate, amount = item_wise_tax_detail.get('_Test Item')
|
||||
self.assertEqual(tax_rate, 9)
|
||||
self.assertEqual(amount, 9)
|
||||
|
||||
tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
|
||||
self.assertEqual(tax_rate2, 5)
|
||||
self.assertEqual(amount2, 5)
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
|
||||
@@ -62,14 +62,15 @@ class POSProfile(Document):
|
||||
|
||||
if len(default_mode) > 1:
|
||||
frappe.throw(_("You can only select one mode of payment as default"))
|
||||
|
||||
|
||||
invalid_modes = []
|
||||
for d in self.payments:
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account",
|
||||
"Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company},
|
||||
"default_account"
|
||||
)
|
||||
|
||||
if not account:
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||
|
||||
|
||||
@@ -92,11 +92,21 @@ def make_pos_profile(**args):
|
||||
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
|
||||
})
|
||||
|
||||
payments = [{
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
|
||||
company = args.company or "_Test Company"
|
||||
default_account = args.income_account or "Sales - _TC"
|
||||
|
||||
if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}):
|
||||
mode_of_payment.append("accounts", {
|
||||
"company": company,
|
||||
"default_account": default_account
|
||||
})
|
||||
mode_of_payment.save()
|
||||
|
||||
pos_profile.append("payments", {
|
||||
'mode_of_payment': 'Cash',
|
||||
'default': 1
|
||||
}]
|
||||
pos_profile.set("payments", payments)
|
||||
})
|
||||
|
||||
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
|
||||
pos_profile.insert()
|
||||
|
||||
@@ -16,8 +16,11 @@ frappe.ui.form.on('POS Settings', {
|
||||
}
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
|
||||
frm.fields_dict.invoice_fields.grid.update_docfield_property(
|
||||
'fieldname', 'options', [""].concat(fields)
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -44,6 +44,14 @@
|
||||
"column_break_21",
|
||||
"min_amt",
|
||||
"max_amt",
|
||||
"product_discount_scheme_section",
|
||||
"same_item",
|
||||
"free_item",
|
||||
"free_qty",
|
||||
"free_item_rate",
|
||||
"column_break_42",
|
||||
"free_item_uom",
|
||||
"is_recursive",
|
||||
"section_break_23",
|
||||
"valid_from",
|
||||
"valid_upto",
|
||||
@@ -62,13 +70,6 @@
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"for_price_list",
|
||||
"product_discount_scheme_section",
|
||||
"same_item",
|
||||
"free_item",
|
||||
"free_qty",
|
||||
"column_break_51",
|
||||
"free_item_uom",
|
||||
"free_item_rate",
|
||||
"section_break_13",
|
||||
"threshold_percentage",
|
||||
"priority",
|
||||
@@ -458,10 +459,6 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_51",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "free_item_uom",
|
||||
"fieldtype": "Link",
|
||||
@@ -552,19 +549,33 @@
|
||||
"fieldname": "promotional_scheme",
|
||||
"fieldtype": "Link",
|
||||
"label": "Promotional Scheme",
|
||||
"options": "Promotional Scheme"
|
||||
"no_copy": 1,
|
||||
"options": "Promotional Scheme",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Condition"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_42",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
|
||||
"fieldname": "is_recursive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recursive"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-01 23:18:38.717613",
|
||||
"modified": "2021-03-06 22:01:24.840422",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
"doctype": args.doctype,
|
||||
"has_margin": False,
|
||||
"name": args.name,
|
||||
"free_item_data": [],
|
||||
"parent": args.parent,
|
||||
"parenttype": args.parenttype,
|
||||
"child_docname": args.get('child_docname')
|
||||
|
||||
@@ -328,6 +328,21 @@ class TestPricingRule(unittest.TestCase):
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_with_margin_and_discount_amount(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10,
|
||||
rate_or_discount="Discount Amount", discount_amount=110)
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.payment_schedule = []
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
self.assertEquals(item.rate_with_margin, 1100)
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_for_product_discount_on_same_item(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
test_record = {
|
||||
@@ -560,6 +575,7 @@ def make_pricing_rule(**args):
|
||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||
"condition": args.condition or '',
|
||||
"priority": 1,
|
||||
"discount_amount": args.discount_amount or 0.0,
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||
})
|
||||
|
||||
|
||||
@@ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
|
||||
|
||||
if items and doc.get("items"):
|
||||
for row in doc.get('items'):
|
||||
if row.get(apply_on) not in items: continue
|
||||
if (row.get(apply_on) or args.get(apply_on)) not in items: continue
|
||||
|
||||
if pr_doc.mixed_conditions:
|
||||
amt = args.get('qty') * args.get("price_list_rate")
|
||||
@@ -471,7 +471,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
|
||||
if not d.get(pr_field): continue
|
||||
|
||||
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
|
||||
if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field):
|
||||
frappe.msgprint(_("User has not applied rule on the invoice {0}")
|
||||
.format(doc.name))
|
||||
else:
|
||||
@@ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
|
||||
doc.calculate_taxes_and_totals()
|
||||
elif d.price_or_product_discount == 'Product':
|
||||
item_details = frappe._dict({'parenttype': doc.doctype})
|
||||
item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
|
||||
get_product_discount_rule(d, item_details, doc=doc)
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
@@ -508,9 +508,16 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
frappe.throw(_("Free item not set in the pricing rule {0}")
|
||||
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
|
||||
|
||||
item_details.free_item_data = {
|
||||
qty = pricing_rule.free_qty or 1
|
||||
if pricing_rule.is_recursive:
|
||||
transaction_qty = args.get('qty') if args else doc.total_qty
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty
|
||||
|
||||
free_item_data_args = {
|
||||
'item_code': free_item,
|
||||
'qty': pricing_rule.free_qty or 1,
|
||||
'qty': qty,
|
||||
'pricing_rules': pricing_rule.name,
|
||||
'rate': pricing_rule.free_item_rate or 0,
|
||||
'price_list_rate': pricing_rule.free_item_rate or 0,
|
||||
'is_free_item': 1
|
||||
@@ -519,24 +526,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
|
||||
'description', 'stock_uom'], as_dict=1)
|
||||
|
||||
item_details.free_item_data.update(item_data)
|
||||
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
|
||||
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
|
||||
item_details.free_item_data['uom']).get("conversion_factor", 1)
|
||||
free_item_data_args.update(item_data)
|
||||
free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
|
||||
free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
|
||||
free_item_data_args['uom']).get("conversion_factor", 1)
|
||||
|
||||
if item_details.get("parenttype") == 'Purchase Order':
|
||||
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
|
||||
free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
|
||||
|
||||
if item_details.get("parenttype") == 'Sales Order':
|
||||
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
|
||||
free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
|
||||
|
||||
item_details.free_item_data.append(free_item_data_args)
|
||||
|
||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||
if pricing_rule_args.get('item_code'):
|
||||
items = [d.item_code for d in doc.items
|
||||
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
|
||||
if pricing_rule_args:
|
||||
items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
|
||||
|
||||
if not items:
|
||||
doc.append('items', pricing_rule_args)
|
||||
for args in pricing_rule_args:
|
||||
if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
|
||||
doc.append('items', args)
|
||||
|
||||
def get_pricing_rule_items(pr_doc):
|
||||
apply_on_data = []
|
||||
|
||||
@@ -38,22 +38,22 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
|
||||
{{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
frm.refresh_field('customers');
|
||||
}
|
||||
else{
|
||||
frappe.msgprint('No Customers found with selected options.');
|
||||
frappe.throw('No Customers found with selected options.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +126,11 @@ def get_customers_based_on_sales_person(sales_person):
|
||||
sales_person_records = frappe._dict()
|
||||
for d in records:
|
||||
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
if sales_person_records.get('Customer'):
|
||||
return frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
||||
return customers
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_recipients_and_cc(customer, doc):
|
||||
recipients = []
|
||||
|
||||
@@ -9,19 +9,19 @@ from frappe.utils import cstr
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.model.document import Document
|
||||
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
|
||||
'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
|
||||
'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
|
||||
'supplier_group', 'company', 'currency']
|
||||
'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
|
||||
|
||||
other_fields = ['min_qty', 'max_qty', 'min_amt',
|
||||
'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description']
|
||||
|
||||
price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate',
|
||||
'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule']
|
||||
'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules']
|
||||
|
||||
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
|
||||
'free_item_rate', 'same_item']
|
||||
'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
|
||||
|
||||
class PromotionalScheme(Document):
|
||||
def validate(self):
|
||||
@@ -111,4 +111,4 @@ def get_args_for_pricing_rule(doc):
|
||||
for d in pricing_rule_fields:
|
||||
args[d] = doc.get(d)
|
||||
|
||||
return args
|
||||
return args
|
||||
|
||||
@@ -1,792 +1,181 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2019-03-24 14:48:59.649168",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"disable",
|
||||
"apply_multiple_pricing_rules",
|
||||
"column_break_2",
|
||||
"rule_description",
|
||||
"section_break_2",
|
||||
"min_qty",
|
||||
"max_qty",
|
||||
"column_break_3",
|
||||
"min_amount",
|
||||
"max_amount",
|
||||
"section_break_6",
|
||||
"rate_or_discount",
|
||||
"column_break_10",
|
||||
"rate",
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"section_break_11",
|
||||
"warehouse",
|
||||
"threshold_percentage",
|
||||
"validate_applied_rule",
|
||||
"column_break_14",
|
||||
"priority",
|
||||
"apply_discount_on_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "disable",
|
||||
"fieldtype": "Check",
|
||||
"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": "Disable",
|
||||
"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
|
||||
"label": "Disable"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rule_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": "Rule Description",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_2",
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "min_qty",
|
||||
"fieldtype": "Float",
|
||||
"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": "Min Qty",
|
||||
"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
|
||||
"label": "Min Qty"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "max_qty",
|
||||
"fieldtype": "Float",
|
||||
"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": "Max Qty",
|
||||
"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
|
||||
"label": "Max Qty"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "min_amount",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Min Amount",
|
||||
"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
|
||||
"label": "Min Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "",
|
||||
"fieldname": "max_amount",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Max Amount",
|
||||
"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
|
||||
"label": "Max Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"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,
|
||||
"label": "",
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Discount Percentage",
|
||||
"depends_on": "",
|
||||
"fieldname": "rate_or_discount",
|
||||
"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": "Discount Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount",
|
||||
"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
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "column_break_10",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Rate",
|
||||
"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
|
||||
"label": "Rate"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"",
|
||||
"fieldname": "discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Discount Amount",
|
||||
"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
|
||||
"label": "Discount Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"",
|
||||
"fieldname": "discount_percentage",
|
||||
"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": "Discount Percentage",
|
||||
"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
|
||||
"label": "Discount Percentage"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_11",
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "warehouse",
|
||||
"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": "Warehouse",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"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
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "threshold_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"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": "Threshold for Suggestion",
|
||||
"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
|
||||
"label": "Threshold for Suggestion"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "validate_applied_rule",
|
||||
"fieldtype": "Check",
|
||||
"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": "Validate Applied Rule",
|
||||
"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
|
||||
"label": "Validate Applied Rule"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "priority",
|
||||
"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": "Priority",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
|
||||
"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
|
||||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "priority",
|
||||
"fieldname": "apply_multiple_pricing_rules",
|
||||
"fieldtype": "Check",
|
||||
"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": "Apply Multiple Pricing Rules",
|
||||
"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
|
||||
"label": "Apply Multiple Pricing Rules"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
||||
"fieldname": "apply_discount_on_rate",
|
||||
"fieldtype": "Check",
|
||||
"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": "Apply Discount on Rate",
|
||||
"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
|
||||
"label": "Apply Discount on Rate"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-24 14:48:59.649168",
|
||||
"links": [],
|
||||
"modified": "2021-03-07 11:56:23.424137",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme Price Discount",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2019-03-24 14:48:59.649168",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"disable",
|
||||
"apply_multiple_pricing_rules",
|
||||
"column_break_2",
|
||||
"rule_description",
|
||||
"section_break_1",
|
||||
@@ -25,7 +27,7 @@
|
||||
"threshold_percentage",
|
||||
"column_break_15",
|
||||
"priority",
|
||||
"apply_multiple_pricing_rules"
|
||||
"is_recursive"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -152,10 +154,19 @@
|
||||
"fieldname": "apply_multiple_pricing_rules",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Multiple Pricing Rules"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
|
||||
"fieldname": "is_recursive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recursive"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-07-21 00:00:56.674284",
|
||||
"links": [],
|
||||
"modified": "2021-03-06 21:58:18.162346",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme Product Discount",
|
||||
|
||||
@@ -496,15 +496,6 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc,
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
if(doc.select_print_heading){
|
||||
// print heading
|
||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
||||
}
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Purchase Invoice");
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Purchase Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
@@ -524,7 +515,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,6 @@
|
||||
"write_off_cost_center",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"adjust_advance_taxes",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
@@ -1326,13 +1325,6 @@
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Taxes paid while advance payment will be adjusted against this invoice",
|
||||
"fieldname": "adjust_advance_taxes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Adjust Advance Taxes"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||
@@ -1378,7 +1370,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-09 21:12:30.422084",
|
||||
"modified": "2021-03-30 22:45:58.334107",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -898,7 +898,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entries = 1
|
||||
acc_settings.save()
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_expense_account = deferred_account
|
||||
item.save()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
|
||||
frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
|
||||
|
||||
// Provision in case onload event is added to sales_invoice.js in future
|
||||
if (globalOnload) {
|
||||
globalOnload(doclist);
|
||||
globalOnload(list_view);
|
||||
}
|
||||
|
||||
const action = () => {
|
||||
const selected_docs = doclist.get_checked_items();
|
||||
const docnames = doclist.get_checked_items(true);
|
||||
const selected_docs = list_view.get_checked_items();
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
for (let doc of selected_docs) {
|
||||
if (doc.docstatus !== 1) {
|
||||
@@ -19,7 +19,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': doclist.doctype,
|
||||
'dt': list_view.doctype,
|
||||
'dn': docnames
|
||||
},
|
||||
callback: function(r) {
|
||||
@@ -35,5 +35,140 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
});
|
||||
};
|
||||
|
||||
doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
|
||||
const generate_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
if (docnames && docnames.length) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
|
||||
args: { docnames },
|
||||
freeze: true,
|
||||
freeze_message: __('Generating E-Invoices...')
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
message: __('Please select at least one sales invoice to generate IRN'),
|
||||
title: __('No Invoice Selected'),
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const cancel_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
const fields = [
|
||||
{
|
||||
"label": "Reason",
|
||||
"fieldname": "reason",
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark",
|
||||
"fieldname": "remark",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}
|
||||
];
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Cancel IRN"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
|
||||
args: {
|
||||
doctype: list_view.doctype,
|
||||
docnames,
|
||||
reason: data.reason.split('-')[0],
|
||||
remark: data.remark
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Cancelling E-Invoices...'),
|
||||
});
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
|
||||
let einvoicing_enabled = false;
|
||||
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
|
||||
einvoicing_enabled = enabled;
|
||||
});
|
||||
|
||||
list_view.$result.on("change", "input[type=checkbox]", () => {
|
||||
if (einvoicing_enabled) {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
// show/hide e-invoicing actions when no sales invoices are checked
|
||||
if (docnames && docnames.length) {
|
||||
// prevent adding actions twice if e-invoicing action group already exists
|
||||
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
|
||||
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
|
||||
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
|
||||
}
|
||||
} else {
|
||||
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
|
||||
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices generated successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to generate IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to cancel IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// print heading
|
||||
cur_frm.pformat.print_heading = 'Invoice';
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %};
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
@@ -916,7 +913,7 @@ frappe.ui.form.on('Sales Invoice Timesheet', {
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message){
|
||||
data = r.message;
|
||||
let data = r.message;
|
||||
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
|
||||
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
|
||||
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"in_words",
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"get_advances",
|
||||
@@ -1109,6 +1110,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@@ -1120,6 +1122,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@@ -1168,6 +1171,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@@ -1180,6 +1184,7 @@
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@@ -1945,6 +1950,13 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "grand_total",
|
||||
"fieldname": "disable_rounded_total",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Rounded Total"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -1952,13 +1964,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-04-15 23:57:58.766651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -24,6 +24,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
|
||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||
|
||||
@@ -76,7 +77,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if not self.is_pos:
|
||||
self.so_dn_required()
|
||||
|
||||
|
||||
self.set_tax_withholding()
|
||||
|
||||
self.validate_proj_cust()
|
||||
@@ -211,6 +212,9 @@ class SalesInvoice(SellingController):
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
@@ -390,6 +394,7 @@ class SalesInvoice(SellingController):
|
||||
if validate_against_credit_limit:
|
||||
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
|
||||
@@ -729,6 +734,7 @@ class SalesInvoice(SellingController):
|
||||
else:
|
||||
self.calculate_billing_amount_for_timesheet()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_timesheet_data(self):
|
||||
self.set('timesheets', [])
|
||||
if self.project:
|
||||
@@ -1286,6 +1292,7 @@ class SalesInvoice(SellingController):
|
||||
break
|
||||
|
||||
# Healthcare
|
||||
@frappe.whitelist()
|
||||
def set_healthcare_services(self, checked_values):
|
||||
self.set("items", [])
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1166,10 +1166,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_create_so_with_margin(self):
|
||||
si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True)
|
||||
price_list_rate = 100
|
||||
price_list_rate = flt(100) * flt(si.plc_conversion_rate)
|
||||
si.items[0].price_list_rate = price_list_rate
|
||||
si.items[0].margin_type = 'Percentage'
|
||||
si.items[0].margin_rate_or_amount = 25
|
||||
si.items[0].discount_amount = 0.0
|
||||
si.items[0].discount_percentage = 0.0
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
|
||||
|
||||
@@ -1800,6 +1802,15 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.selling_price_list = "_Test Price List Rest of the World"
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||
|
||||
# Add stock to stores for succesful stock transfer
|
||||
make_stock_entry(
|
||||
target="Stores - TCP1",
|
||||
company = "_Test Company with perpetual inventory",
|
||||
qty=1,
|
||||
basic_rate=100
|
||||
)
|
||||
|
||||
add_taxes(si)
|
||||
si.save()
|
||||
|
||||
@@ -1868,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append('credentials', {
|
||||
'company': '_Test Company',
|
||||
'gstin': '27AAECE4835E1ZR',
|
||||
'username': 'test',
|
||||
'password': 'test'
|
||||
})
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
@@ -1879,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 0
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
@@ -2106,6 +2128,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",
|
||||
@@ -2269,4 +2292,4 @@ def add_taxes(doc):
|
||||
"cost_center": "Main - TCP1",
|
||||
"description": "Excise Duty",
|
||||
"rate": 12
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,5 +46,5 @@ def validate_disabled(doc):
|
||||
frappe.throw(_("Disabled template must not be default template"))
|
||||
|
||||
def validate_for_tax_category(doc):
|
||||
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
|
||||
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
|
||||
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -177,7 +177,7 @@ def cancel_invoices():
|
||||
|
||||
for d in purchase_invoices:
|
||||
frappe.get_doc('Purchase Invoice', d).cancel()
|
||||
|
||||
|
||||
for d in sales_invoices:
|
||||
frappe.get_doc('Sales Invoice', d).cancel()
|
||||
|
||||
@@ -229,7 +229,8 @@ def create_sales_invoice(**args):
|
||||
'qty': args.qty or 1,
|
||||
'rate': args.rate or 10000,
|
||||
'cost_center': 'Main - _TC',
|
||||
'expense_account': 'Cost of Goods Sold - _TC'
|
||||
'expense_account': 'Cost of Goods Sold - _TC',
|
||||
'warehouse': args.warehouse or '_Test Warehouse - _TC'
|
||||
}]
|
||||
})
|
||||
|
||||
@@ -353,4 +354,4 @@ def create_tax_with_holding_category():
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
}).insert()
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
|
||||
"from_date": start_date
|
||||
})
|
||||
|
||||
to_date = add_months(start_date, months_to_add)
|
||||
if i==0 and filter_based_on == 'Date Range':
|
||||
to_date = add_months(get_first_day(start_date), months_to_add)
|
||||
else:
|
||||
to_date = add_months(start_date, months_to_add)
|
||||
|
||||
start_date = to_date
|
||||
|
||||
# Subtract one day from to_date, as it may be first day in next fiscal year or month
|
||||
|
||||
@@ -406,9 +406,10 @@ def check_if_advance_entry_modified(args):
|
||||
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
||||
|
||||
def validate_allocated_amount(args):
|
||||
precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
|
||||
if args.get("allocated_amount") < 0:
|
||||
throw(_("Allocated amount cannot be negative"))
|
||||
elif args.get("allocated_amount") > args.get("unadjusted_amount"):
|
||||
elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
|
||||
throw(_("Allocated amount cannot be greater than unadjusted amount"))
|
||||
|
||||
def update_reference_in_journal_entry(d, jv_obj):
|
||||
|
||||
@@ -443,6 +443,16 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "GL Entry",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "UAE VAT 201",
|
||||
"link_to": "UAE VAT 201",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
|
||||
@@ -71,6 +71,7 @@ class CropCycle(Document):
|
||||
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def reload_linked_analysis(self):
|
||||
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
|
||||
required_fields = ['location', 'name', 'collection_datetime']
|
||||
@@ -87,6 +88,7 @@ class CropCycle(Document):
|
||||
frappe.publish_realtime("List of Linked Docs",
|
||||
output, user=frappe.session.user)
|
||||
|
||||
@frappe.whitelist()
|
||||
def append_to_child(self, obj_to_append):
|
||||
for doctype in obj_to_append:
|
||||
for doc_name in set(obj_to_append[doctype]):
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Fertilizer(Document):
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
|
||||
for doc in docs:
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.model.naming import make_autoname
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PlantAnalysis(Document):
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
|
||||
for doc in docs:
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SoilAnalysis(Document):
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
|
||||
for doc in docs:
|
||||
|
||||
@@ -13,6 +13,7 @@ class SoilTexture(Document):
|
||||
soil_edit_order = [2, 1, 0]
|
||||
soil_types = ['clay_composition', 'sand_composition', 'silt_composition']
|
||||
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'})
|
||||
for doc in docs:
|
||||
@@ -26,6 +27,7 @@ class SoilTexture(Document):
|
||||
if sum(self.get(soil_type) for soil_type in self.soil_types) != 100:
|
||||
frappe.throw(_('Soil compositions do not add up to 100'))
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_soil_edit(self, soil_type):
|
||||
self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1
|
||||
self.soil_type = self.get_soil_type()
|
||||
@@ -35,8 +37,8 @@ class SoilTexture(Document):
|
||||
if sum(self.soil_edit_order) < 5: return
|
||||
last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order))
|
||||
|
||||
# set composition of the last edited soil
|
||||
self.set( self.soil_types[last_edit_index],
|
||||
# set composition of the last edited soil
|
||||
self.set(self.soil_types[last_edit_index],
|
||||
100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index])))
|
||||
|
||||
# calculate soil type
|
||||
@@ -67,4 +69,4 @@ class SoilTexture(Document):
|
||||
elif (c >= 40 and sa <= 45 and si < 40):
|
||||
return 'Clay'
|
||||
else:
|
||||
return 'Select'
|
||||
return 'Select'
|
||||
|
||||
@@ -9,11 +9,13 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
class WaterAnalysis(Document):
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'})
|
||||
for doc in docs:
|
||||
self.append('water_analysis_criteria', {'title': str(doc.name)})
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_lab_result_date(self):
|
||||
if not self.result_datetime:
|
||||
self.result_datetime = self.laboratory_testing_datetime
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Weather(Document):
|
||||
@frappe.whitelist()
|
||||
def load_contents(self):
|
||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'})
|
||||
for doc in docs:
|
||||
|
||||
@@ -553,6 +553,7 @@ class Asset(AccountsController):
|
||||
make_gl_entries(gl_entries)
|
||||
self.db_set('booked_fixed_asset', 1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_depreciation_rate(self, args, on_validate=False):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"maintain_same_rate",
|
||||
"maintain_same_rate_action",
|
||||
"role_to_override_stop_action",
|
||||
"allow_multiple_items",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
@@ -89,6 +91,23 @@
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Stop",
|
||||
"depends_on": "maintain_same_rate",
|
||||
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
|
||||
"fieldname": "maintain_same_rate_action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action If Same Rate is Not Maintained",
|
||||
"mandatory_depends_on": "maintain_same_rate",
|
||||
"options": "Stop\nWarn"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
|
||||
"fieldname": "role_to_override_stop_action",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -96,7 +115,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-02 17:34:04.190677",
|
||||
"modified": "2021-04-04 20:01:44.087066",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -133,6 +133,7 @@ class PurchaseOrder(BuyingController):
|
||||
d.material_request_item, "schedule_date")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_last_purchase_rate(self):
|
||||
"""get last purchase rates for all items"""
|
||||
|
||||
@@ -252,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()
|
||||
|
||||
@@ -366,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"
|
||||
@@ -723,7 +778,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
||||
item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
"base_net_amount",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
"actual_qty",
|
||||
"company_total_stock",
|
||||
"material_request",
|
||||
"material_request_item",
|
||||
"sales_order",
|
||||
@@ -743,6 +745,22 @@
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Available Qty at Warehouse",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "company_total_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Available Qty at Company",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "discount_and_margin_section",
|
||||
@@ -791,7 +809,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-23 01:00:27.132705",
|
||||
"modified": "2021-03-22 11:46:12.357435",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -66,6 +66,7 @@ class RequestforQuotation(BuyingController):
|
||||
def on_cancel(self):
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_supplier_email_preview(self, supplier):
|
||||
"""Returns formatted email preview as string."""
|
||||
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
|
||||
|
||||
@@ -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",
|
||||
|
||||
7
erpnext/change_log/v13/v13.0.2.md
Normal file
7
erpnext/change_log/v13/v13.0.2.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Version 13.0.2 Release Notes
|
||||
|
||||
### Fixes
|
||||
- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231))
|
||||
- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306))
|
||||
- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314))
|
||||
- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295))
|
||||
471
erpnext/change_log/v13/v13_0_0.md
Normal file
471
erpnext/change_log/v13/v13_0_0.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# Version 13.0.0 Release Notes
|
||||
|
||||
### Accounting
|
||||
- [New and refreshed POS](https://github.com/frappe/erpnext/pull/20789)
|
||||
- [GST E-invoicing for India](https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing)
|
||||
- [Distributed Cost Center](https://docs.erpnext.com/docs/user/manual/en/accounts/distributed-cost-center)
|
||||
- [Process Bulk Statement Of Accounts](https://docs.erpnext.com/docs/user/manual/en/accounts/process-statement-of-accounts)
|
||||
- [More controlled deferred revenue booking](https://docs.erpnext.com/docs/user/manual/en/accounts/process-deferred-accounting)
|
||||
- [Dunning](https://docs.erpnext.com/docs/user/manual/en/accounts/dunning)
|
||||
- [Journal Entry Template](https://docs.erpnext.com/docs/user/manual/en/accounts/journal-entry-template)
|
||||
- [POS Register report](https://github.com/frappe/erpnext/pull/23313)
|
||||
- [UAE VAT 201 Report](https://github.com/frappe/erpnext/pull/23447)
|
||||
|
||||
|
||||
### Loan Management
|
||||
- [Loan Application](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-application)
|
||||
- [Loan](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan)
|
||||
- [Loan Security Pledge](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-security-pledge)
|
||||
- [Loan Disbursement](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-disbursement)
|
||||
- [Loan Repayment](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-repayment)
|
||||
- [Loan Interest Accrual](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-interest-accrual)
|
||||
- [Loan Write Off](https://docs.erpnext.com/docs/user/manual/en/loan-management/loan-write-off)
|
||||
|
||||
### Healthcare
|
||||
- [Refactored Healthcare Module](https://docs.erpnext.com/docs/user/manual/en/healthcare)
|
||||
- [Rehabilitation Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/exercise_type)
|
||||
- [Laboratory Module](https://docs.erpnext.com/docs/user/manual/en/healthcare/setup_laboratory)
|
||||
- [Patient Progress Page](https://github.com/frappe/erpnext/pull/22474)
|
||||
- [Inpatient Medication Order and Entry](https://docs.erpnext.com/docs/user/manual/en/healthcare/inpatient_medication_entry)
|
||||
- [Therapy Plan Template](https://docs.erpnext.com/docs/user/manual/en/healthcare/therapy_plan)
|
||||
- [Multi company support in Healthcare](https://github.com/frappe/erpnext/pull/21290)
|
||||
- [Inpatient Medication Orders Script Report](https://github.com/frappe/erpnext/pull/23984)
|
||||
- [Patient History Enhancements](https://github.com/frappe/erpnext/pull/24033)
|
||||
|
||||
|
||||
### Stock
|
||||
- [Putaway](https://docs.erpnext.com/docs/user/manual/en/stock/putaway-rule)
|
||||
- [More accurate stock valuation in case of back-dated stock transactions](https://github.com/frappe/erpnext/pull/24183)
|
||||
- [Repost item costing via background job](https://github.com/frappe/erpnext/pull/24183)
|
||||
- [Item valuation for internal stock transfers](https://github.com/frappe/erpnext/pull/24200)
|
||||
- [Multi currency in Landed Cost Voucher](https://github.com/frappe/erpnext/pull/24127)
|
||||
- [Formula based Quality Inspection](https://docs.erpnext.com/docs/user/manual/en/stock/quality-inspection)
|
||||
- [Value Based and Numeric Quality Inspection](https://github.com/frappe/erpnext/pull/24181)
|
||||
- [Shipment](https://github.com/frappe/erpnext/pull/22914)
|
||||
- [Return tracking in PR/DN](https://github.com/frappe/erpnext/pull/22859)
|
||||
|
||||
### Manufacturing
|
||||
- [Production forecasting using Exponential Smoothing method](https://docs.erpnext.com/docs/user/manual/en/manufacturing/reports/demand-driven-forecasting)
|
||||
- [BOM Template](https://docs.erpnext.com/docs/user/manual/en/manufacturing/bill-of-materials#34-bom-template)
|
||||
- [Downtime Entry](https://docs.erpnext.com/docs/user/manual/en/manufacturing/downtime-entry)
|
||||
- [Quality Inspection on Job Card](https://github.com/frappe/erpnext/pull/23964)
|
||||
- New Reports
|
||||
- Production Planning Report ([#21763](https://github.com/frappe/erpnext/pull/21763))
|
||||
- BOM Operations Time ([#21763](https://github.com/frappe/erpnext/pull/21763))
|
||||
- Work Order Summary ([#21430](https://github.com/frappe/erpnext/pull/21430))
|
||||
- Job card Summary ([#21430](https://github.com/frappe/erpnext/pull/21430))
|
||||
- Downtime Analysis ([#21430](https://github.com/frappe/erpnext/pull/21430))
|
||||
- Quality Inspection ([#21430](https://github.com/frappe/erpnext/pull/21430))
|
||||
|
||||
### HR
|
||||
- [Leave policy assignment](https://github.com/frappe/erpnext/pull/23112)
|
||||
- [In and Out time in attendance](https://github.com/frappe/erpnext/pull/21547)
|
||||
- [Shift management](https://docs.erpnext.com/docs/user/manual/en/human-resources/shift-management)
|
||||
- [Recruitment analytics](https://github.com/frappe/erpnext/pull/21732)
|
||||
- [Bulk Mark Attendance](https://github.com/frappe/erpnext/pull/20062)
|
||||
- [Leave type with partial payment](https://github.com/frappe/erpnext/pull/23173)
|
||||
- New and enhanced reports
|
||||
- Employee Analytics ([#21705](https://github.com/frappe/erpnext/pull/21705))
|
||||
- Employee Leave Balance ([#20754](https://github.com/frappe/erpnext/pull/20754))
|
||||
- Employee Leave Balance Summary ([#20754](https://github.com/frappe/erpnext/pull/20754))
|
||||
|
||||
### Payroll
|
||||
- [Multi-currency payroll](https://github.com/frappe/erpnext/pull/23519)
|
||||
- [Payroll based on attendance](https://github.com/frappe/erpnext/pull/21258)
|
||||
- [Payroll based on employee cost center](https://github.com/frappe/erpnext/pull/21609)
|
||||
- [Recurring Additional Salary](https://github.com/frappe/erpnext/pull/20936)
|
||||
- [Compute Year to Date for Salary Slip components](https://github.com/frappe/erpnext/pull/24362)
|
||||
- New Reports
|
||||
- Income Tax Deductions
|
||||
- Professional Tax Deductions
|
||||
- Provident Fund Deductions
|
||||
- Total Salary Payments Based on Payment Mode
|
||||
- Salary Payments via ECS
|
||||
|
||||
### CRM
|
||||
- [Social Media Post](https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post)
|
||||
- [Make Quotation against Blanket Order](https://docs.erpnext.com/docs/user/manual/en/selling/blanket-order)
|
||||
- [Calendar View for Opportunity](https://github.com/frappe/erpnext/pull/21280)
|
||||
|
||||
### Selling
|
||||
- [Batch wise item pricing](https://github.com/frappe/erpnext/pull/24470)
|
||||
- [Refreshed shopping cart](https://github.com/frappe/erpnext/pull/22617)
|
||||
- [Territory-wise Sales Report](https://github.com/frappe/erpnext/pull/20428)
|
||||
|
||||
#### Buying
|
||||
- [Multi UOM support in Request for Quotation](https://github.com/frappe/erpnext/pull/22249)
|
||||
- [Provision to make RFQ against Opportunity](https://github.com/frappe/erpnext/pull/22765)
|
||||
- [Item Rate in Stock UOM in purchase cycle](https://github.com/frappe/erpnext/pull/24315)
|
||||
- New Reports
|
||||
- Requested Items To Order ([#21611](https://github.com/frappe/erpnext/pull/21611))
|
||||
- Purchase Order Analysis ([#21611](https://github.com/frappe/erpnext/pull/21611))
|
||||
- Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323))
|
||||
|
||||
### Project
|
||||
- [Project template with dependent tasks](https://github.com/frappe/erpnext/pull/24092)
|
||||
- [Project Summary Report](https://github.com/frappe/erpnext/pull/21587)
|
||||
|
||||
### Support
|
||||
- [Help Articles on support portal](https://github.com/frappe/erpnext/pull/22194)
|
||||
- [Issue Metrics and SLA Enhancements](https://github.com/frappe/erpnext/pull/21617)
|
||||
- [Issue Summary Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports)
|
||||
- [Issue Analytics Script Report](https://docs.erpnext.com/docs/user/manual/en/support/support_reports)
|
||||
|
||||
### Non-Profits
|
||||
- [80G Certificates and Donations](https://docs.erpnext.com/docs/user/manual/en/non_profit/tax_exemption_80g_certificate)
|
||||
|
||||
#### Integrations
|
||||
- [Woocommerce Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/woocommerce_integration)
|
||||
- [Taxjar Integration](https://github.com/frappe/erpnext/pull/21047)
|
||||
- [M-pesa Integration](https://docs.erpnext.com/docs/user/manual/en/erpnext_integration/mpesa-integration)
|
||||
- [Telephony feature using Twillio](https://github.com/frappe/erpnext/pull/24032)
|
||||
- [Voice Call Settings](https://github.com/frappe/erpnext/pull/24126)
|
||||
|
||||
|
||||
#### Other Enhancements and Fixes
|
||||
- Accounting Dimensions in Budget Variance Report ([#19973](https://github.com/frappe/erpnext/pull/19973))
|
||||
- "Sync Now" option in Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602))
|
||||
- Custom Fields in POS ([#19876](https://github.com/frappe/erpnext/pull/19876))
|
||||
- [Inter Warehouse Stock Transfer in Purchase Receipt](https://docs.erpnext.com/docs/user/manual/en/stock/articles/material-transfer-from-delivery-note)
|
||||
- [Accounts Payable Report based on Payment Terms](https://docs.erpnext.com/docs/user/manual/en/accounts/accounting-reports)
|
||||
- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912))
|
||||
- Territory tree in Customer Acquisition and Loyalty report ([#21668](https://github.com/frappe/erpnext/pull/21668))
|
||||
- Allow Purchase Invoice Creation Without Purchase Order Checkbox in Supplier ([#20864](https://github.com/frappe/erpnext/pull/20864))
|
||||
- Gross Profit In Quotation ([#21795](https://github.com/frappe/erpnext/pull/21795))
|
||||
- Notify credit controller users for credit limit extension via Email ([#22213](https://github.com/frappe/erpnext/pull/22213))
|
||||
- Run MRP at parent level in the production plan and make material transfer based upon materials availability ([#21545](https://github.com/frappe/erpnext/pull/21545))
|
||||
- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675))
|
||||
- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867))
|
||||
- Consider Holiday List in Student Leave Application and Attendance ([#23388](https://github.com/frappe/erpnext/pull/23388))
|
||||
- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201))
|
||||
- Sales order status filter added for production plan ([#23805](https://github.com/frappe/erpnext/pull/23805))
|
||||
- Monthly attendance sheet report group by Department, Designation, Employee Grade and Branch ([#21331](https://github.com/frappe/erpnext/pull/21331))
|
||||
- Upload Attendance template now have pre-filled holiday status ([#20947](https://github.com/frappe/erpnext/pull/20947))
|
||||
- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398))
|
||||
|
||||
<details>
|
||||
<summary>More</summary>
|
||||
|
||||
- Fetch Items from BOM in Stock Entry([#19498](https://github.com/frappe/erpnext/pull/19498))
|
||||
- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557))
|
||||
- Close Production Plan ([#23728](https://github.com/frappe/erpnext/pull/23728))
|
||||
- Button to create Stock Entry for Drug Shortage ([#24012](https://github.com/frappe/erpnext/pull/24012))
|
||||
- Added column cost center in Accounts Receivable report ([#23835](https://github.com/frappe/erpnext/pull/23835))
|
||||
- Added jinja templating in Contract Template ([#24046](https://github.com/frappe/erpnext/pull/24046))
|
||||
- Make account number length configurable ([#23845](https://github.com/frappe/erpnext/pull/23845))
|
||||
- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614))
|
||||
- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014))
|
||||
- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445))
|
||||
- [Tax Category in POS Profile](https://docs.erpnext.com/docs/user/manual/en/accounts/pos-profile)
|
||||
- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170))
|
||||
- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281))
|
||||
- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355))
|
||||
- Autofill Supplier pop-up when only 1 Supplier in RFQ ([#22512](https://github.com/frappe/erpnext/pull/22512))
|
||||
- Accounting entries for service item in Purchase receipt ([#22223](https://github.com/frappe/erpnext/pull/22223))
|
||||
- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309))
|
||||
- Added all companies option in employee tree to view employee across all companies ([#22573](https://github.com/frappe/erpnext/pull/22573))
|
||||
- Email Group Option In Email Campaign ([#22731](https://github.com/frappe/erpnext/pull/22731))
|
||||
- Stock Report Enhancements ([#21727](https://github.com/frappe/erpnext/pull/21727))
|
||||
- Added range for age in stock ageing ([#22622](https://github.com/frappe/erpnext/pull/22622))
|
||||
- Report Summary in Financial Statement([#20876](https://github.com/frappe/erpnext/pull/20876))
|
||||
- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641))
|
||||
- Nested Set filtering for Accounting Dimension
|
||||
- Add/Remove Items from submitted Sales/Purchase Order
|
||||
- Provision to edit Item Details from Marketplace
|
||||
- Scan Barcode in Purchase Receipt
|
||||
- Disable Rounded Totals Checkbox for Salary Slips in HR Settings
|
||||
|
||||
- Renamed Loan Management to Loan on Desk Page ([#21877](https://github.com/frappe/erpnext/pull/21877))
|
||||
- Added Expense Approver field in Employee master ([#22244](https://github.com/frappe/erpnext/pull/22244))
|
||||
- Bill all hours by default on Timesheet ([#22155](https://github.com/frappe/erpnext/pull/22155))
|
||||
- Unable to cancel employee advance ([#22374](https://github.com/frappe/erpnext/pull/22374))
|
||||
- Status error in purchase invoice ([#22351](https://github.com/frappe/erpnext/pull/22351))
|
||||
- Item-wise sales and purchase register export ([#22184](https://github.com/frappe/erpnext/pull/22184))
|
||||
- Billing address in for Purchase documents ([#22233](https://github.com/frappe/erpnext/pull/22233))
|
||||
- Handle canceled entries in financial statements ([#22231](https://github.com/frappe/erpnext/pull/22231))
|
||||
- Default period start date and period end date for financial statements ([#22011](https://github.com/frappe/erpnext/pull/22011))
|
||||
- Update Packed Items via Update Items in Sales Order ([#22392](https://github.com/frappe/erpnext/pull/22392))
|
||||
- Hide delete company transactions button if not system manager ([#21839](https://github.com/frappe/erpnext/pull/21839))
|
||||
- Skipping total row for tree-view reports ([#22350](https://github.com/frappe/erpnext/pull/22350))
|
||||
- Cancelled entries in tds payable monthly report ([#22131](https://github.com/frappe/erpnext/pull/22131))
|
||||
- Inter-company Invoice currency for multicurrency transactions ([#21984](https://github.com/frappe/erpnext/pull/21984))
|
||||
- Filter batches based on item and warehouse in Pick List (develop) ([#21780](https://github.com/frappe/erpnext/pull/21780))
|
||||
- Set cost center in Expense Claim child based on parent (if missing) ([#22175](https://github.com/frappe/erpnext/pull/22175))
|
||||
- Item wise backdated stock entry posting for immutable ledger ([#22366](https://github.com/frappe/erpnext/pull/22366))
|
||||
- Shopping cart UI fixes ([#22137](https://github.com/frappe/erpnext/pull/22137))
|
||||
- Filter Leave Type based on allocation for a particular employee ([#22050](https://github.com/frappe/erpnext/pull/22050))
|
||||
- Party validation for inter-warehouse transaction ([#22186](https://github.com/frappe/erpnext/pull/22186))
|
||||
- Manufacturing dashboard and work order summary chart ([#21946](https://github.com/frappe/erpnext/pull/21946))
|
||||
- IP Admission and Discharge, Minor fixes ([#21817](https://github.com/frappe/erpnext/pull/21817))
|
||||
- Validation of Purchase Order against Material Request missing ([#22192](https://github.com/frappe/erpnext/pull/22192))
|
||||
- Staffing Plan validation ([#22379](https://github.com/frappe/erpnext/pull/22379))
|
||||
- Do not allow backdated stock transactions in previous fiscal year ([#21967](https://github.com/frappe/erpnext/pull/21967))
|
||||
- Employee Advance Return not working ([#21812](https://github.com/frappe/erpnext/pull/21812))
|
||||
- Added card for reports on education desk ([#21853](https://github.com/frappe/erpnext/pull/21853))
|
||||
- Refactored project summary report ([#21943](https://github.com/frappe/erpnext/pull/21943))
|
||||
- Revenue and Customer Count only in date range in Customer Acquitition Report ([#22210](https://github.com/frappe/erpnext/pull/22210))
|
||||
- Alternative item not working for subcontract ([#22386](https://github.com/frappe/erpnext/pull/22386))
|
||||
- Unable to create batched Item ([#22393](https://github.com/frappe/erpnext/pull/22393))
|
||||
- Filters for the manufacturing reports ([#21960](https://github.com/frappe/erpnext/pull/21960))
|
||||
- Raw material warehouse in Production Planning Report ([#21982](https://github.com/frappe/erpnext/pull/21982))
|
||||
- Allowed LWP leave types to select in Leave Application even if there is no allocation against them ([#22197](https://github.com/frappe/erpnext/pull/22197))
|
||||
- Report not working on parameter Grade ([#21951](https://github.com/frappe/erpnext/pull/21951))
|
||||
- Allow to enter Relieving date if employee status is Left ([#22242](https://github.com/frappe/erpnext/pull/22242))
|
||||
- Resetting lost reason in opportunity and quotation ([#22378](https://github.com/frappe/erpnext/pull/22378))
|
||||
- Filtering issues in opening invoice creation tool ([#21969](https://github.com/frappe/erpnext/pull/21969))
|
||||
- Set default reference Id for "On Previous Row Amount" and "On Previous Row Total" ([#22346](https://github.com/frappe/erpnext/pull/22346))
|
||||
- UX date range field separated in from and to date fields. ([#21765](https://github.com/frappe/erpnext/pull/21765))
|
||||
- Enable show_configure_button when shopping cart is enabled ([#22468](https://github.com/frappe/erpnext/pull/22468))
|
||||
- Setup status indicators for Job Offer and Job Applicant (develop) ([#22445](https://github.com/frappe/erpnext/pull/22445))
|
||||
- Item-wise sales history report ([#22783](https://github.com/frappe/erpnext/pull/22783))
|
||||
- Setting filter for project in kanban board ([#22717](https://github.com/frappe/erpnext/pull/22717))
|
||||
- Dashboard For Timesheet ([#22750](https://github.com/frappe/erpnext/pull/22750))
|
||||
- Handle custom statuses for the pause SLA configuration ([#22349](https://github.com/frappe/erpnext/pull/22349))
|
||||
- Quality Feedback and Template ([#22571](https://github.com/frappe/erpnext/pull/22571))
|
||||
- Unable to change link from new lead to existing customer ([#22787](https://github.com/frappe/erpnext/pull/22787))
|
||||
- Move Issue List actions under 'Actions' dropdown (ux) ([#22710](https://github.com/frappe/erpnext/pull/22710))
|
||||
- Cost center should only show option of selected company ([#22598](https://github.com/frappe/erpnext/pull/22598))
|
||||
- Serial No Rename does not affect Stock Ledger Entry ([#22746](https://github.com/frappe/erpnext/pull/22746))
|
||||
- Descriptions not copied while creating Fees from Fee Structure ([#22792](https://github.com/frappe/erpnext/pull/22792))
|
||||
- Company filter for cost_center and expense_account in all sales and purchase transactions ([#22478](https://github.com/frappe/erpnext/pull/22478))
|
||||
- Arrangements of filters for reports accounts payable & receivable ([#22636](https://github.com/frappe/erpnext/pull/22636))
|
||||
- Update the project after task deletion so that the % completed shows correct value ([#22591](https://github.com/frappe/erpnext/pull/22591))
|
||||
- Block Invalid Serial No updates in Maintenance Schedule ([#22665](https://github.com/frappe/erpnext/pull/22665))
|
||||
- Fetch item price in sales invoice based on it's validity ([#22563](https://github.com/frappe/erpnext/pull/22563))
|
||||
- Add view ledger button for cancelled docs ([#22432](https://github.com/frappe/erpnext/pull/22432))
|
||||
- Allow creating SLA documents even if SLA tracking is not enabled ([#22608](https://github.com/frappe/erpnext/pull/22608))
|
||||
- Quotation list view blank if quotation_to field not set as a standard filter ([#22672](https://github.com/frappe/erpnext/pull/22672))
|
||||
- Salary deductions report fixes ([#22397](https://github.com/frappe/erpnext/pull/22397))
|
||||
22727))
|
||||
- Incorrect delivered qty in Supplier-Wise Sales Analytics ([#22631](https://github.com/frappe/erpnext/pull/22631))
|
||||
- Moved parent warehouse to top section also added a section break ([#22708](https://github.com/frappe/erpnext/pull/22708))
|
||||
- Skip Progress and Completed by fields on Task Duplication ([#22565](https://github.com/frappe/erpnext/pull/22565))
|
||||
- Incorrect stock after merging the items ([#22526](https://github.com/frappe/erpnext/pull/22526))
|
||||
- Letter head not found in opening invoice creation tool ([#22488](https://github.com/frappe/erpnext/pull/22488))
|
||||
- Cannot cancel asset and asset movement ([#22441](https://github.com/frappe/erpnext/pull/22441))
|
||||
- Fetch project-related info in Timesheet ([#22423](https://github.com/frappe/erpnext/pull/22423))
|
||||
- Currency symbol not showing as per company currency in stock balance report ([#22724](https://github.com/frappe/erpnext/pull/22724))
|
||||
- Add default cost center in payment reconciliation JV ([#22614](https://github.com/frappe/erpnext/pull/22614))
|
||||
- Stock Reconciliation Invalid Quantity for Batched Item ([#22726](https://github.com/frappe/erpnext/pull/22726))
|
||||
- Project link not set in accounts other than profit and loss accounts ([#22051](https://github.com/frappe/erpnext/pull/22051))
|
||||
- Buying price for non stock item in gross profit report ([#22616](https://github.com/frappe/erpnext/pull/22616))
|
||||
- Multi currency payment reconciliation ([#22738](https://github.com/frappe/erpnext/pull/22738))
|
||||
- Cannot cancel assets with repair pending ([#22440](https://github.com/frappe/erpnext/pull/22440))
|
||||
- Reset homepage to home after unchecking products page ([#22736](https://github.com/frappe/erpnext/pull/22736))
|
||||
- Generic Message in previous doc validation for buying and selling ([#22546](https://github.com/frappe/erpnext/pull/22546))
|
||||
- Expense claim outstanding while making payment entry ([#22735](https://github.com/frappe/erpnext/pull/22735))
|
||||
- Take parent cost center for child if no cost center at child in expense claim ([#22496](https://github.com/frappe/erpnext/pull/22496))
|
||||
- Consider company fiscal year for getting balance ([#22577](https://github.com/frappe/erpnext/pull/22577))
|
||||
- Pick List empty table and Serial-Batch items handling ([#22426](https://github.com/frappe/erpnext/pull/22426))
|
||||
- Show total row in print format of financial statement ([#22693](https://github.com/frappe/erpnext/pull/22693))
|
||||
- Set Root as Parent if no parent in new tree view node ([#22497](https://github.com/frappe/erpnext/pull/22497))
|
||||
- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725))
|
||||
- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001))
|
||||
- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217))
|
||||
- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669))
|
||||
- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256))
|
||||
- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333))
|
||||
- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580))
|
||||
- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541))
|
||||
- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755))
|
||||
- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302))
|
||||
- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165))
|
||||
- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772))
|
||||
- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880))
|
||||
- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407))
|
||||
- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072))
|
||||
- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718))
|
||||
- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288))
|
||||
- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
|
||||
- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548))
|
||||
- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145))
|
||||
- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918))
|
||||
- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597))
|
||||
- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245))
|
||||
- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361))
|
||||
- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704))
|
||||
- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161))
|
||||
- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369))
|
||||
- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195))
|
||||
- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419))
|
||||
- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953))
|
||||
- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110))
|
||||
- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059))
|
||||
- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007))
|
||||
- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066))
|
||||
- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814))
|
||||
- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076))
|
||||
- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434))
|
||||
- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009))
|
||||
- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958))
|
||||
- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483))
|
||||
- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539))
|
||||
- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151))
|
||||
- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476))
|
||||
- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911))
|
||||
- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950))
|
||||
- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943))
|
||||
- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282))
|
||||
- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714))
|
||||
- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205))
|
||||
- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623))
|
||||
- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264))
|
||||
- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676))
|
||||
- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988))
|
||||
- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436))
|
||||
- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357))
|
||||
- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515))
|
||||
- Loan disbursement amount validation ([#24000](https://github.com/frappe/erpnext/pull/24000))
|
||||
- Making company address read-only in delivery note ([#23890](https://github.com/frappe/erpnext/pull/23890))
|
||||
- BOM stock report color showing always red ([#23994](https://github.com/frappe/erpnext/pull/23994))
|
||||
- Added filter for customer field in Issue ([#24051](https://github.com/frappe/erpnext/pull/24051))
|
||||
- Added project link in timesheet form ([#23764](https://github.com/frappe/erpnext/pull/23764))
|
||||
- Update integrations desk page ([#23767](https://github.com/frappe/erpnext/pull/23767))
|
||||
- Place of supply change on address change ([#23941](https://github.com/frappe/erpnext/pull/23941))
|
||||
- TDS calculation, skip invoices with "Apply Tax Withholding Amount" has disabled ([#23672](https://github.com/frappe/erpnext/pull/23672))
|
||||
- Auto fetch serial nos with modified conversion factor ([#23854](https://github.com/frappe/erpnext/pull/23854))
|
||||
- Default cost center in item master not set in stock entry ([#23877](https://github.com/frappe/erpnext/pull/23877))
|
||||
- Incorrect de-link serial no and batch ([#23947](https://github.com/frappe/erpnext/pull/23947))
|
||||
- Accounting for internal transfer invoices within same company ([#24021](https://github.com/frappe/erpnext/pull/24021))
|
||||
- Multiple pricing rule with margin type as Percentage is not working ([#24205](https://github.com/frappe/erpnext/pull/24205))
|
||||
- Added Purchase Order to Global Search ([#24055](https://github.com/frappe/erpnext/pull/24055))
|
||||
- Cannot expand row in update items dialog ([#23839](https://github.com/frappe/erpnext/pull/23839))
|
||||
- Maintain stock can't be changed it there is product bundle ([#23989](https://github.com/frappe/erpnext/pull/23989))
|
||||
- SO to PO Mapping Issue ([#23820](https://github.com/frappe/erpnext/pull/23820))
|
||||
- Asset with value zero doesn't show up in fixed asset register ([#24091](https://github.com/frappe/erpnext/pull/24091))
|
||||
- Cannot save customer email & phone ([#23797](https://github.com/frappe/erpnext/pull/23797))
|
||||
- Incorrect balance value in stock balance report ([#24048](https://github.com/frappe/erpnext/pull/24048))
|
||||
- Payment Terms not fetched in Purchase Invoice from Purchase Receipt ([#23735](https://github.com/frappe/erpnext/pull/23735))
|
||||
- Fix for LMS Sign Up link ([#23743](https://github.com/frappe/erpnext/pull/23743))
|
||||
- Incorrect stock quantity if 'Allow Multiple Material Consumption… ([#24116](https://github.com/frappe/erpnext/pull/24116))
|
||||
- Added wrong absent days calculation in salary slip ([#23897](https://github.com/frappe/erpnext/pull/23897))
|
||||
- Purchase receipt to purchase invoice bill date mapping ([#23967](https://github.com/frappe/erpnext/pull/23967))
|
||||
- Overriding po ([#24022](https://github.com/frappe/erpnext/pull/24022))
|
||||
- Do not cancel reference document on Quality Inspection cancellation ([#24198](https://github.com/frappe/erpnext/pull/24198))
|
||||
- Get formatted value in 'taxes' print template ([#24035](https://github.com/frappe/erpnext/pull/24035))
|
||||
- Don't overrule Item Price via Pricing Rule Rate if 0 ([#23636](https://github.com/frappe/erpnext/pull/23636))
|
||||
- Job card error handling for operations field ([#23991](https://github.com/frappe/erpnext/pull/23991))
|
||||
- Validation for journal entry with 0 debit and credit values ([#23975](https://github.com/frappe/erpnext/pull/23975))
|
||||
- Check if customer exists in product listing ([#24030](https://github.com/frappe/erpnext/pull/24030))
|
||||
- Asset finance book posting date fix ([#23778](https://github.com/frappe/erpnext/pull/23778))
|
||||
- Same source and target tables in Status Updater's update query ([#24110](https://github.com/frappe/erpnext/pull/24110))
|
||||
- Asset finance book depreciation posting date fix ([#23833](https://github.com/frappe/erpnext/pull/23833))
|
||||
- Ignore exception during leave ledger creation from patch ([#24005](https://github.com/frappe/erpnext/pull/24005))
|
||||
- Added link of bank reconciliation and clearance in accounting desk page ([#23850](https://github.com/frappe/erpnext/pull/23850))
|
||||
- Sales invoice add button from sales order dashboard ([#24077](https://github.com/frappe/erpnext/pull/24077))
|
||||
- Incorrect calculation for consumed qty for subcontract item ([#23257](https://github.com/frappe/erpnext/pull/23257))
|
||||
- Incorrect required_qty in Production Planning Report ([#24074](https://github.com/frappe/erpnext/pull/24074))
|
||||
- Email digest user not found ([#23949](https://github.com/frappe/erpnext/pull/23949))
|
||||
- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24115](https://github.com/frappe/erpnext/pull/24115))
|
||||
- Added TDS Payable account number and an error message ([#24065](https://github.com/frappe/erpnext/pull/24065))
|
||||
- Override field_map for job card gantt ([#24155](https://github.com/frappe/erpnext/pull/24155))
|
||||
- Old shopify order syncing date ([#23990](https://github.com/frappe/erpnext/pull/23990))
|
||||
- Shipping chanrges not sync in erpnext from shopify ([#24114](https://github.com/frappe/erpnext/pull/24114))
|
||||
- GSTR B2C report ([#24039](https://github.com/frappe/erpnext/pull/24039))
|
||||
- Ignore cancelled entries in stock balance report ([#23757](https://github.com/frappe/erpnext/pull/23757))
|
||||
- Stock ageing report not working ([#23923](https://github.com/frappe/erpnext/pull/23923))
|
||||
- Incorrect assign to in Maintenance Schedule ([#23831](https://github.com/frappe/erpnext/pull/23831))
|
||||
- Improve UX of DATEV report ([#23892](https://github.com/frappe/erpnext/pull/23892))
|
||||
- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
|
||||
- dDouble exception in payroll ([#24078](https://github.com/frappe/erpnext/pull/24078))
|
||||
- Make asset dashboard charts public ([#23751](https://github.com/frappe/erpnext/pull/23751))
|
||||
- Don't copy terms and discount from SO to PO ([#23903](https://github.com/frappe/erpnext/pull/23903))
|
||||
- Ignore doctypes on company transaction delete ([#23864](https://github.com/frappe/erpnext/pull/23864))
|
||||
- Error handling in Upload Attendance ([#23907](https://github.com/frappe/erpnext/pull/23907))
|
||||
- Tax template update on customer address change ([#24160](https://github.com/frappe/erpnext/pull/24160))
|
||||
- Not able to save bom ([#23910](https://github.com/frappe/erpnext/pull/23910))
|
||||
- Enable Allow Auto Repeat for standard doctypes having auto_repeat field ([#23776](https://github.com/frappe/erpnext/pull/23776))
|
||||
- Place of Supply fix in Sales Invoices ([#23785](https://github.com/frappe/erpnext/pull/23785))
|
||||
- Opening invoices in GSTR-1 report ([#24117](https://github.com/frappe/erpnext/pull/24117))
|
||||
- Partial serial no return issue ([#24208](https://github.com/frappe/erpnext/pull/24208))
|
||||
- Import taxjar globally in the taxjar_integration module ([#24027](https://github.com/frappe/erpnext/pull/24027))
|
||||
- Payroll attendance error ([#23887](https://github.com/frappe/erpnext/pull/23887))
|
||||
- Loan application link on creating loan ([#23937](https://github.com/frappe/erpnext/pull/23937))
|
||||
- POS item search includes non stock items ([#23914](https://github.com/frappe/erpnext/pull/23914))
|
||||
- Paid amount in Sales Invoice POS return resets to 0 ([#24057](https://github.com/frappe/erpnext/pull/24057))
|
||||
- Fiscal year can be shorter than 12 months ([#23838](https://github.com/frappe/erpnext/pull/23838))
|
||||
- Loan repayment type option remove ([#23582](https://github.com/frappe/erpnext/pull/23582))
|
||||
- Item wise tax calculation ([#23744](https://github.com/frappe/erpnext/pull/23744))
|
||||
- Enabling track changes for stock settings ([#23982](https://github.com/frappe/erpnext/pull/23982))
|
||||
- Added link of bank reconciliation and clearance in accounting desk page ([#23809](https://github.com/frappe/erpnext/pull/23809))
|
||||
- Location data on Asset to use command(make_demo) ([#23825](https://github.com/frappe/erpnext/pull/23825))
|
||||
- Handle Account and Item None not found in Opening Invoice Creation Tool ([#23559](https://github.com/frappe/erpnext/pull/23559))
|
||||
- Multiple subcontracting issues ([#23662](https://github.com/frappe/erpnext/pull/23662))
|
||||
- Sequence id override with workstation column ([#23810](https://github.com/frappe/erpnext/pull/23810))
|
||||
- Leave policy dashboard fix and roles ([#24170](https://github.com/frappe/erpnext/pull/24170))
|
||||
- Scan barcode does not update barcode item field in sales order ([#24090](https://github.com/frappe/erpnext/pull/24090))
|
||||
- Item price duplicate checking ([#23408](https://github.com/frappe/erpnext/pull/23408))
|
||||
- Tax template update on supplier change for India ([#24060](https://github.com/frappe/erpnext/pull/24060))
|
||||
- Consumed qty logic for subcontracted raw materials ([#23314](https://github.com/frappe/erpnext/pull/23314))
|
||||
- Finance book not getting added in journal Entry of asset value adjustment ([#24100](https://github.com/frappe/erpnext/pull/24100))
|
||||
- Set proper state code in ewaybill JSON when GST category is SEZ ([#23953](https://github.com/frappe/erpnext/pull/23953))
|
||||
- Copying po no when mapping doc ([#23729](https://github.com/frappe/erpnext/pull/23729))
|
||||
- Duplicate items validation for POS Invoice when allow multiple items is disabled ([#23896](https://github.com/frappe/erpnext/pull/23896))
|
||||
- Do not allow Company as accounting dimension ([#23749](https://github.com/frappe/erpnext/pull/23749))
|
||||
- Validation for duplicate Tax Category ([#23978](https://github.com/frappe/erpnext/pull/23978))
|
||||
- Therapy plan and session fixes ([#23817](https://github.com/frappe/erpnext/pull/23817))
|
||||
- Pricing rule with transaction not working for additional product ([#24053](https://github.com/frappe/erpnext/pull/24053))
|
||||
- Inpatient Medication Order and Entry fixes ([#23799](https://github.com/frappe/erpnext/pull/23799))
|
||||
- Avoid using SQL query to get fiscal year dates ([#24050](https://github.com/frappe/erpnext/pull/24050))
|
||||
- Auto Statewise gst tax template ([#23832](https://github.com/frappe/erpnext/pull/23832))
|
||||
- On save sequence id column override with workstation ([#23812](https://github.com/frappe/erpnext/pull/23812))
|
||||
- Multiple pricing rules are not working on selling side ([#22711](https://github.com/frappe/erpnext/pull/22711))
|
||||
- Salary slip popup error ([#24192](https://github.com/frappe/erpnext/pull/24192))
|
||||
- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204))
|
||||
- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424))
|
||||
- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119))
|
||||
- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339))
|
||||
- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191))
|
||||
- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338))
|
||||
- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346))
|
||||
- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019))
|
||||
- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358))
|
||||
- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324))
|
||||
- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437))
|
||||
- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252))
|
||||
- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226))
|
||||
- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329))
|
||||
- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130))
|
||||
- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474))
|
||||
- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146))
|
||||
- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164))
|
||||
- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375))
|
||||
- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207))
|
||||
- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376))
|
||||
- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349))
|
||||
- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296))
|
||||
- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265))
|
||||
- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397))
|
||||
- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272))
|
||||
- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402))
|
||||
- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517))
|
||||
- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220))
|
||||
- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354))
|
||||
- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294))
|
||||
- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063))
|
||||
- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356))
|
||||
- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378))
|
||||
- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072))
|
||||
- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332))
|
||||
- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515))
|
||||
- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322))
|
||||
- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197))
|
||||
- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297))
|
||||
- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944))
|
||||
- Fixed stock and account balance syncing ([#24644](https://github.com/frappe/erpnext/pull/24644))
|
||||
- Fixed incorrect stock ledger qty in the stock ledger report and bin ([#24649](https://github.com/frappe/erpnext/pull/24649))
|
||||
- Fixed Consolidated Financial Statement report ([#24580](https://github.com/frappe/erpnext/pull/24580))
|
||||
- Repost incompleted backdated transactions ([#24991](https://github.com/frappe/erpnext/pull/24991))
|
||||
- Unequal debit and credit issue on RCM Invoice ([#24838](https://github.com/frappe/erpnext/pull/24838))
|
||||
- Period list for exponential smoothing forecasting report ([#24983](https://github.com/frappe/erpnext/pull/24983))
|
||||
- POS Opening Entry with empty balance detail rows ([#24891](https://github.com/frappe/erpnext/pull/24891))
|
||||
- Use account_name only in consolidated report ([#24840](https://github.com/frappe/erpnext/pull/24840))
|
||||
- Validation of job card in stock entry ([#24882](https://github.com/frappe/erpnext/pull/24882))
|
||||
- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24918](https://github.com/frappe/erpnext/pull/24918))
|
||||
- TDS check getting checked after reload ([#24973](https://github.com/frappe/erpnext/pull/24973))
|
||||
- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
|
||||
- Allow zero valuation in stock reconciliation ([#24985](https://github.com/frappe/erpnext/pull/24985))
|
||||
- Simplified logic for additional salary ([#24907](https://github.com/frappe/erpnext/pull/24907))
|
||||
- Allow to select item code in batch naming ([#24825](https://github.com/frappe/erpnext/pull/24825))
|
||||
- Membership renewal validation (#24963) ([#24964](https://github.com/frappe/erpnext/pull/24964))
|
||||
</details>
|
||||
@@ -26,7 +26,8 @@ from erpnext.controllers.print_settings import set_print_templates_for_item_tabl
|
||||
|
||||
class AccountMissingError(frappe.ValidationError): pass
|
||||
|
||||
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
||||
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
|
||||
"pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -516,6 +517,7 @@ class AccountsController(TransactionBase):
|
||||
frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
|
||||
and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_shipping_rule(self):
|
||||
if self.shipping_rule:
|
||||
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
|
||||
@@ -536,6 +538,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
return {}
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_advances(self):
|
||||
"""Returns list of advances against Account, Party, Reference"""
|
||||
|
||||
@@ -656,6 +659,7 @@ class AccountsController(TransactionBase):
|
||||
'dr_or_cr': dr_or_cr,
|
||||
'unadjusted_amount': flt(d.advance_amount),
|
||||
'allocated_amount': flt(d.allocated_amount),
|
||||
'precision': d.precision('advance_amount'),
|
||||
'exchange_rate': (self.conversion_rate
|
||||
if self.party_account_currency != self.company_currency else 1),
|
||||
'grand_total': (self.base_grand_total
|
||||
@@ -920,7 +924,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]
|
||||
@@ -1235,18 +1240,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
|
||||
@@ -1258,6 +1269,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):
|
||||
"""
|
||||
@@ -1316,25 +1337,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]
|
||||
@@ -1343,23 +1402,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'):
|
||||
@@ -1394,7 +1447,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
)
|
||||
|
||||
def get_new_child_item(item_row):
|
||||
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
||||
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
||||
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||
|
||||
def validate_quantity(child_item, d):
|
||||
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.utils import flt,cint, cstr, getdate
|
||||
from six import iteritems
|
||||
from collections import OrderedDict
|
||||
from erpnext.accounts.party import get_party_details
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
|
||||
@@ -391,10 +392,12 @@ class BuyingController(StockController):
|
||||
|
||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
|
||||
|
||||
for batch_data in batches_qty:
|
||||
qty = batch_data['qty']
|
||||
raw_material.batch_no = batch_data['batch']
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
if qty > 0:
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
else:
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
|
||||
@@ -1056,7 +1059,7 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
|
||||
for batch_data in transferred_batches:
|
||||
key = ((batch_data.item_code, fg_item)
|
||||
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
|
||||
transferred_batch_qty_map.setdefault(key, {})
|
||||
transferred_batch_qty_map.setdefault(key, OrderedDict())
|
||||
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
|
||||
|
||||
return transferred_batch_qty_map
|
||||
@@ -1109,8 +1112,14 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty
|
||||
if available_qty >= required_qty:
|
||||
available_batches.append({'batch': batch, 'qty': required_qty})
|
||||
break
|
||||
else:
|
||||
elif available_qty != 0:
|
||||
available_batches.append({'batch': batch, 'qty': available_qty})
|
||||
required_qty -= available_qty
|
||||
|
||||
for row in available_batches:
|
||||
if backflushed_batches.get(row.get('batch'), 0) > 0:
|
||||
backflushed_batches[row.get('batch')] += row.get('qty')
|
||||
else:
|
||||
backflushed_batches[row.get('batch')] = row.get('qty')
|
||||
|
||||
return available_batches
|
||||
|
||||
@@ -325,7 +325,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
and status not in ("Stopped", "Closed") %(fcond)s
|
||||
and (
|
||||
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
|
||||
or `tabDelivery Note`.grand_total = 0
|
||||
or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
|
||||
or (
|
||||
`tabDelivery Note`.is_return = 1
|
||||
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.model.meta import get_field_precision
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from frappe.utils import flt, get_datetime, format_datetime
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
@@ -389,10 +390,24 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
|
||||
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
|
||||
item_row=None, voucher_detail_no=None, sle=None):
|
||||
if not return_against:
|
||||
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
|
||||
|
||||
if not return_against and voucher_type == 'Sales Invoice' and sle:
|
||||
return get_incoming_rate({
|
||||
"item_code": sle.item_code,
|
||||
"warehouse": sle.warehouse,
|
||||
"posting_date": sle.get('posting_date'),
|
||||
"posting_time": sle.get('posting_time'),
|
||||
"qty": sle.actual_qty,
|
||||
"serial_no": sle.get('serial_no'),
|
||||
"company": sle.company,
|
||||
"voucher_type": sle.voucher_type,
|
||||
"voucher_no": sle.voucher_no
|
||||
}, raise_error_if_no_rate=False)
|
||||
|
||||
return_against_item_field = get_return_against_item_fields(voucher_type)
|
||||
|
||||
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
|
||||
|
||||
@@ -144,7 +144,7 @@ class SellingController(StockController):
|
||||
|
||||
if sales_person.commission_rate:
|
||||
sales_person.incentives = flt(
|
||||
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
|
||||
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
|
||||
self.precision("incentives", sales_person))
|
||||
|
||||
total += sales_person.allocated_percentage
|
||||
@@ -311,14 +311,16 @@ class SellingController(StockController):
|
||||
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
for d in items:
|
||||
if not cint(self.get("is_return")):
|
||||
if not self.get("return_against"):
|
||||
# Get incoming rate based on original item cost based on valuation method
|
||||
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
|
||||
|
||||
d.incoming_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.get('posting_date') or self.get('transaction_date'),
|
||||
"posting_time": self.get('posting_time') or nowtime(),
|
||||
"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
|
||||
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
@@ -502,4 +504,4 @@ def set_default_income_account_for_item(obj):
|
||||
for d in obj.get("items"):
|
||||
if d.item_code:
|
||||
if getattr(d, "income_account", None):
|
||||
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
|
||||
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
|
||||
|
||||
@@ -406,8 +406,7 @@ class StockController(AccountsController):
|
||||
def set_rate_of_stock_uom(self):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
|
||||
for d in self.get("items"):
|
||||
if d.conversion_factor:
|
||||
d.stock_uom_rate = d.rate / d.conversion_factor
|
||||
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||
@@ -495,7 +494,7 @@ class StockController(AccountsController):
|
||||
"voucher_no": self.name,
|
||||
"company": self.company
|
||||
})
|
||||
if check_if_future_sle_exists(args):
|
||||
if future_sle_exists(args):
|
||||
create_repost_item_valuation_entry(args)
|
||||
elif not is_reposting_pending():
|
||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||
@@ -506,37 +505,42 @@ def is_reposting_pending():
|
||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||
|
||||
|
||||
def check_if_future_sle_exists(args):
|
||||
sl_entries = frappe.db.get_all("Stock Ledger Entry",
|
||||
def future_sle_exists(args):
|
||||
sl_entries = frappe.get_all("Stock Ledger Entry",
|
||||
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
|
||||
fields=["item_code", "warehouse"],
|
||||
order_by="creation asc")
|
||||
|
||||
distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries]))
|
||||
if not sl_entries:
|
||||
return
|
||||
|
||||
sle_exists = False
|
||||
for item_code, warehouse in distinct_item_warehouses:
|
||||
args.update({
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse
|
||||
})
|
||||
if get_sle(args):
|
||||
sle_exists = True
|
||||
break
|
||||
return sle_exists
|
||||
warehouse_items_map = {}
|
||||
for entry in sl_entries:
|
||||
if entry.warehouse not in warehouse_items_map:
|
||||
warehouse_items_map[entry.warehouse] = set()
|
||||
|
||||
warehouse_items_map[entry.warehouse].add(entry.item_code)
|
||||
|
||||
or_conditions = []
|
||||
for warehouse, items in warehouse_items_map.items():
|
||||
or_conditions.append(
|
||||
"warehouse = '{}' and item_code in ({})".format(
|
||||
warehouse,
|
||||
", ".join(frappe.db.escape(item) for item in items)
|
||||
)
|
||||
)
|
||||
|
||||
def get_sle(args):
|
||||
return frappe.db.sql("""
|
||||
select name
|
||||
from `tabStock Ledger Entry`
|
||||
where
|
||||
item_code=%(item_code)s
|
||||
and warehouse=%(warehouse)s
|
||||
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
|
||||
({})
|
||||
and timestamp(posting_date, posting_time)
|
||||
>= timestamp(%(posting_date)s, %(posting_time)s)
|
||||
and voucher_no != %(voucher_no)s
|
||||
and is_cancelled = 0
|
||||
limit 1
|
||||
""", args)
|
||||
""".format(" or ".join(or_conditions)), args)
|
||||
|
||||
def create_repost_item_valuation_entry(args):
|
||||
args = frappe._dict(args)
|
||||
@@ -554,4 +558,4 @@ def create_repost_item_valuation_entry(args):
|
||||
repost_entry.allow_zero_rate = args.allow_zero_rate
|
||||
repost_entry.flags.ignore_links = True
|
||||
repost_entry.save()
|
||||
repost_entry.submit()
|
||||
repost_entry.submit()
|
||||
|
||||
@@ -113,7 +113,12 @@ 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"))
|
||||
item.discount_amount = item.rate_with_margin - item.rate
|
||||
|
||||
if item.discount_amount and not item.discount_percentage:
|
||||
item.rate = item.rate_with_margin - 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:
|
||||
@@ -144,7 +149,9 @@ class calculate_taxes_and_totals(object):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
tax.item_wise_tax_detail = {}
|
||||
if not self.doc.get('is_consolidated'):
|
||||
tax.item_wise_tax_detail = {}
|
||||
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||
@@ -284,10 +291,13 @@ class calculate_taxes_and_totals(object):
|
||||
# set precision in the last item iteration
|
||||
if n == len(self.doc.get("items")) - 1:
|
||||
self.round_off_totals(tax)
|
||||
self._set_in_company_currency(tax,
|
||||
["tax_amount", "tax_amount_after_discount_amount"])
|
||||
|
||||
self.round_off_base_values(tax)
|
||||
self.set_cumulative_total(i, tax)
|
||||
|
||||
self._set_in_company_currency(tax,
|
||||
["total", "tax_amount", "tax_amount_after_discount_amount"])
|
||||
self._set_in_company_currency(tax, ["total"])
|
||||
|
||||
# adjust Discount Amount loss in last tax iteration
|
||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||
@@ -334,18 +344,11 @@ class calculate_taxes_and_totals(object):
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
if not self.doc.get("is_consolidated"):
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
return current_tax_amount
|
||||
|
||||
def get_final_current_tax_amount(self, tax, current_tax_amount):
|
||||
# Some countries need individual tax components to be rounded
|
||||
# Handeled via regional doctypess
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
current_tax_amount = round(current_tax_amount, 0)
|
||||
return current_tax_amount
|
||||
|
||||
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
||||
# store tax breakup for each item
|
||||
key = item.item_code or item.item_name
|
||||
@@ -356,10 +359,20 @@ class calculate_taxes_and_totals(object):
|
||||
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
|
||||
|
||||
def round_off_totals(self, tax):
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
tax.tax_amount = round(tax.tax_amount, 0)
|
||||
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
|
||||
|
||||
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
|
||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
||||
tax.precision("tax_amount"))
|
||||
|
||||
def round_off_base_values(self, tax):
|
||||
# Round off to nearest integer based on regional settings
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
tax.base_tax_amount = round(tax.base_tax_amount, 0)
|
||||
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
|
||||
|
||||
def manipulate_grand_total_for_inclusive_tax(self):
|
||||
# if fully inclusive taxes and diff
|
||||
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
||||
@@ -437,8 +450,9 @@ class calculate_taxes_and_totals(object):
|
||||
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
||||
|
||||
def _cleanup(self):
|
||||
for tax in self.doc.get("taxes"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
if not self.doc.get('is_consolidated'):
|
||||
for tax in self.doc.get("taxes"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
|
||||
def set_discount_amount(self):
|
||||
if self.doc.additional_discount_percentage:
|
||||
@@ -795,7 +809,7 @@ class init_landed_taxes_and_totals(object):
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if d.account_currency == company_currency:
|
||||
d.exchange_rate = 1
|
||||
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
|
||||
elif not d.exchange_rate:
|
||||
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
|
||||
account_currency=d.account_currency, company=self.doc.company)
|
||||
|
||||
@@ -805,4 +819,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"))
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
||||
|
||||
if not filters: filters = []
|
||||
|
||||
if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']:
|
||||
if doctype in ['Supplier Quotation', 'Purchase Invoice']:
|
||||
filters.append((doctype, 'docstatus', '<', 2))
|
||||
else:
|
||||
filters.append((doctype, 'docstatus', '=', 1))
|
||||
|
||||
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Lead Source', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:source_name",
|
||||
"creation": "2016-09-16 01:47:47.382372",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"source_name",
|
||||
"details"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "source_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Source Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Details"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-02-08 12:51:48.971517",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead Source",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LeadSource(Document):
|
||||
@@ -1,12 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Lead Source')
|
||||
|
||||
class TestLeadSource(unittest.TestCase):
|
||||
pass
|
||||
@@ -11,7 +11,8 @@ from frappe.utils.file_manager import get_file, get_file_path
|
||||
from six.moves.urllib.parse import urlencode
|
||||
|
||||
class LinkedInSettings(Document):
|
||||
def get_authorization_url(self):
|
||||
@frappe.whitelist()
|
||||
def get_authorization_url(self):
|
||||
params = urlencode({
|
||||
"response_type":"code",
|
||||
"client_id": self.consumer_key,
|
||||
@@ -35,7 +36,7 @@ class LinkedInSettings(Document):
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
|
||||
response = self.http_post(url=url, data=body, headers=headers)
|
||||
response = frappe.parse_json(response.content.decode())
|
||||
self.db_set("access_token", response["access_token"])
|
||||
|
||||
@@ -85,6 +85,7 @@ class Opportunity(TransactionBase):
|
||||
self.opportunity_from = "Lead"
|
||||
self.party_name = lead_name
|
||||
|
||||
@frappe.whitelist()
|
||||
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
||||
if not self.has_active_quotation():
|
||||
frappe.db.set(self, 'status', 'Lost')
|
||||
@@ -248,7 +249,6 @@ def make_quotation(source_name, target_doc=None):
|
||||
"doctype": "Quotation",
|
||||
"field_map": {
|
||||
"opportunity_from": "quotation_to",
|
||||
"opportunity_type": "order_type",
|
||||
"name": "enq_no",
|
||||
}
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user