Merge branch 'staging'
22
.travis.yml
@ -51,11 +51,17 @@ before_script:
|
||||
- bench start &
|
||||
- sleep 10
|
||||
|
||||
script:
|
||||
- set -e
|
||||
- bench run-tests
|
||||
- sleep 5
|
||||
- bench reinstall --yes
|
||||
- bench execute erpnext.setup.setup_wizard.utils.complete
|
||||
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
|
||||
- bench run-ui-tests --app erpnext
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script:
|
||||
- set -e
|
||||
- bench run-tests
|
||||
env: Server Side Test
|
||||
- # stage
|
||||
script:
|
||||
- bench --verbose run-setup-wizard-ui-test
|
||||
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
|
||||
- bench run-ui-tests --app erpnext
|
||||
env: Client Side Test
|
||||
|
||||
|
@ -4,7 +4,7 @@ import inspect
|
||||
import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = '8.11.6'
|
||||
__version__ = '9.0.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -5,7 +5,9 @@ QUnit.test("test account", function(assert) {
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Tree', 'Account'),
|
||||
() => frappe.timeout(3),
|
||||
() => frappe.click_button('Expand All'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Debtors'),
|
||||
() => frappe.click_button('Edit'),
|
||||
() => frappe.timeout(1),
|
||||
|
@ -36,7 +36,7 @@ class GLEntry(Document):
|
||||
validate_balance_type(self.account, adv_adj)
|
||||
|
||||
# Update outstanding amt on against voucher
|
||||
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice'] \
|
||||
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
|
||||
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
|
||||
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
||||
self.against_voucher)
|
||||
@ -196,7 +196,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
||||
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
|
||||
|
||||
# Update outstanding amt on against voucher
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
||||
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
||||
ref_doc.db_set('outstanding_amount', bal)
|
||||
ref_doc.set_status(update=True)
|
||||
|
@ -1337,6 +1337,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1382,7 +1443,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-06-13 14:29:09.794076",
|
||||
"modified": "2017-08-31 11:21:09.442695",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
@ -12,7 +12,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
setup: function(frm) {
|
||||
frm.set_query("paid_from", function() {
|
||||
var party_account_type = frm.doc.party_type=="Customer" ? "Receivable" : "Payable";
|
||||
var party_account_type = in_list(["Customer", "Student"], frm.doc.party_type) ?
|
||||
"Receivable" : "Payable";
|
||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : party_account_type;
|
||||
|
||||
@ -28,13 +29,14 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_query("party_type", function() {
|
||||
return{
|
||||
"filters": {
|
||||
"name": ["in",["Customer","Supplier", "Employee"]],
|
||||
"name": ["in",["Customer","Supplier", "Employee", "Student"]],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("paid_to", function() {
|
||||
var party_account_type = frm.doc.party_type=="Customer" ? "Receivable" : "Payable";
|
||||
var party_account_type = in_list(["Customer", "Student"], frm.doc.party_type) ?
|
||||
"Receivable" : "Payable";
|
||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : party_account_type;
|
||||
|
||||
@ -72,6 +74,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||
} else if (frm.doc.party_type=="Employee") {
|
||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||
} else if (frm.doc.party_type=="Student") {
|
||||
var doctypes = ["Fees"];
|
||||
} else {
|
||||
var doctypes = ["Journal Entry"];
|
||||
}
|
||||
@ -85,7 +89,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
child = locals[cdt][cdn];
|
||||
filters = {"docstatus": 1, "company": doc.company};
|
||||
party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||
'Purchase Order', 'Expense Claim'];
|
||||
'Purchase Order', 'Expense Claim', 'Fees'];
|
||||
|
||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||
@ -207,19 +211,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_value(field, null);
|
||||
});
|
||||
} else {
|
||||
if(!frm.doc.party)
|
||||
{
|
||||
if (frm.doc.payment_type=="Receive"){
|
||||
frm.set_value("party_type", "Customer");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frm.events.party(frm);
|
||||
if(frm.doc.party) {
|
||||
frm.events.party(frm);
|
||||
}
|
||||
|
||||
if(frm.doc.mode_of_payment)
|
||||
if(frm.doc.mode_of_payment) {
|
||||
frm.events.mode_of_payment(frm);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -254,6 +252,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
date: frm.doc.posting_date
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
console.log(r, rt);
|
||||
if(r.message) {
|
||||
if(frm.doc.payment_type == "Receive") {
|
||||
frm.set_value("paid_from", r.message.party_account);
|
||||
@ -502,6 +501,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
c.due_date = d.due_date
|
||||
c.total_amount = d.invoice_amount;
|
||||
c.outstanding_amount = d.outstanding_amount;
|
||||
c.bill_no = d.bill_no;
|
||||
|
||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim"], d.voucher_type)) {
|
||||
if(flt(d.outstanding_amount) > 0)
|
||||
total_positive_outstanding += flt(d.outstanding_amount);
|
||||
@ -644,16 +645,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if(frm.doc.party) {
|
||||
var party_amount = frm.doc.payment_type=="Receive" ?
|
||||
frm.doc.paid_amount : frm.doc.received_amount;
|
||||
|
||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||
function(d) { return flt(d.amount) }));
|
||||
|
||||
if(frm.doc.total_allocated_amount < party_amount) {
|
||||
if(frm.doc.payment_type == "Receive") {
|
||||
unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
|
||||
} else {
|
||||
unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions);
|
||||
}
|
||||
unallocated_amount = party_amount - frm.doc.total_allocated_amount;
|
||||
}
|
||||
}
|
||||
frm.set_value("unallocated_amount", unallocated_amount);
|
||||
@ -672,11 +666,10 @@ frappe.ui.form.on('Payment Entry', {
|
||||
difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount);
|
||||
}
|
||||
|
||||
$.each(frm.doc.deductions || [], function(i, d) {
|
||||
if(d.amount) difference_amount -= flt(d.amount);
|
||||
})
|
||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||
function(d) { return flt(d.amount) }));
|
||||
|
||||
frm.set_value("difference_amount", difference_amount);
|
||||
frm.set_value("difference_amount", difference_amount - total_deductions);
|
||||
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
},
|
||||
|
@ -1659,6 +1659,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1730,7 +1791,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-13 14:29:04.244537",
|
||||
"modified": "2017-08-31 11:20:37.578469",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
@ -100,8 +100,8 @@ class PaymentEntry(AccountsController):
|
||||
if not self.party:
|
||||
frappe.throw(_("Party is mandatory"))
|
||||
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party,
|
||||
self.party_type.lower() + "_name")
|
||||
_party_name = "title" if self.party_type == "Student" else self.party_type.lower() + "_name"
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
|
||||
if self.party:
|
||||
if not self.party_balance:
|
||||
@ -149,7 +149,7 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
|
||||
|
||||
if self.party_account:
|
||||
party_account_type = "Receivable" if self.party_type=="Customer" else "Payable"
|
||||
party_account_type = "Receivable" if self.party_type in ("Customer", "Student") else "Payable"
|
||||
self.validate_account_type(self.party_account, [party_account_type])
|
||||
|
||||
def validate_bank_accounts(self):
|
||||
@ -182,7 +182,9 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
||||
|
||||
def validate_reference_documents(self):
|
||||
if self.party_type == "Customer":
|
||||
if self.party_type == "Student":
|
||||
valid_reference_doctypes = ("Fees")
|
||||
elif self.party_type == "Customer":
|
||||
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry")
|
||||
elif self.party_type == "Supplier":
|
||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||
@ -209,17 +211,19 @@ class PaymentEntry(AccountsController):
|
||||
else:
|
||||
self.validate_journal_entry()
|
||||
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
|
||||
if self.party_type=="Customer":
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
|
||||
if self.party_type == "Customer":
|
||||
ref_party_account = ref_doc.debit_to
|
||||
elif self.party_type == "Student":
|
||||
ref_party_account = ref_doc.receivable_account
|
||||
elif self.party_type=="Supplier":
|
||||
ref_party_account = ref_doc.credit_to
|
||||
elif self.party_type=="Employee":
|
||||
ref_party_account = ref_doc.payable_account
|
||||
|
||||
if ref_party_account != self.party_account:
|
||||
frappe.throw(_("{0} {1} is associated with {2}, but Party Account is {3}")
|
||||
.format(d.reference_doctype, d.reference_name, ref_party_account, self.party_account))
|
||||
frappe.throw(_("{0} {1} is associated with {2}, but Party Account is {3}")
|
||||
.format(d.reference_doctype, d.reference_name, ref_party_account, self.party_account))
|
||||
|
||||
if ref_doc.docstatus != 1:
|
||||
frappe.throw(_("{0} {1} must be submitted")
|
||||
@ -281,13 +285,8 @@ class PaymentEntry(AccountsController):
|
||||
if self.party:
|
||||
party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
|
||||
|
||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
||||
|
||||
if self.total_allocated_amount < party_amount:
|
||||
if self.payment_type == "Receive":
|
||||
self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions)
|
||||
else:
|
||||
self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions)
|
||||
self.unallocated_amount = party_amount - self.total_allocated_amount
|
||||
|
||||
def set_difference_amount(self):
|
||||
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
||||
@ -302,11 +301,10 @@ class PaymentEntry(AccountsController):
|
||||
else:
|
||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
||||
|
||||
for d in self.get("deductions"):
|
||||
if d.amount:
|
||||
self.difference_amount -= flt(d.amount)
|
||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
||||
|
||||
self.difference_amount = flt(self.difference_amount, self.precision("difference_amount"))
|
||||
self.difference_amount = flt(self.difference_amount - total_deductions,
|
||||
self.precision("difference_amount"))
|
||||
|
||||
def clear_unallocated_reference_document_rows(self):
|
||||
self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]}))
|
||||
@ -404,7 +402,7 @@ class PaymentEntry(AccountsController):
|
||||
"account_currency": self.party_account_currency
|
||||
})
|
||||
|
||||
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
|
||||
dr_or_cr = "credit" if self.party_type in ["Customer", "Student"] else "debit"
|
||||
|
||||
for d in self.get("references"):
|
||||
gle = party_gl_dict.copy()
|
||||
@ -489,9 +487,14 @@ class PaymentEntry(AccountsController):
|
||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
||||
update_reimbursed_amount(doc)
|
||||
|
||||
def on_recurring(self, reference_doc, subscription_doc):
|
||||
self.reference_no = reference_doc.name
|
||||
self.reference_date = nowdate()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_reference_documents(args):
|
||||
args = json.loads(args)
|
||||
if isinstance(args, basestring):
|
||||
args = json.loads(args)
|
||||
|
||||
party_account_currency = get_account_currency(args.get("party_account"))
|
||||
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
|
||||
@ -499,10 +502,12 @@ def get_outstanding_reference_documents(args):
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
total_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
|
||||
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
||||
args.get("party"), args.get("party_account"), total_field)
|
||||
negative_outstanding_invoices = []
|
||||
if (args.get("party_type") != "Student"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
||||
args.get("party"), args.get("party_account"), total_field)
|
||||
|
||||
# Get positive outstanding sales /purchase invoices
|
||||
# Get positive outstanding sales /purchase invoices/ Fees
|
||||
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
||||
args.get("party_account"))
|
||||
|
||||
@ -515,10 +520,14 @@ def get_outstanding_reference_documents(args):
|
||||
d["exchange_rate"] = get_exchange_rate(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"),
|
||||
party_account_currency, company_currency)
|
||||
orders_to_be_billed = []
|
||||
if (args.get("party_type") != "Student"):
|
||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
||||
args.get("party"), party_account_currency, company_currency)
|
||||
|
||||
return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||
|
||||
@ -633,7 +642,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
total_amount = outstanding_amount = exchange_rate = None
|
||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
|
||||
if reference_doctype != "Journal Entry":
|
||||
if reference_doctype == "Fees":
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
exchange_rate = 1
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if party_account_currency == ref_doc.company_currency:
|
||||
if ref_doc.doctype == "Expense Claim":
|
||||
total_amount = ref_doc.total_sanctioned_amount
|
||||
@ -676,19 +689,23 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
party_type = "Supplier"
|
||||
elif dt in ("Expense Claim"):
|
||||
party_type = "Employee"
|
||||
elif dt in ("Fees"):
|
||||
party_type = "Student"
|
||||
|
||||
# party account
|
||||
if dt == "Sales Invoice":
|
||||
party_account = doc.debit_to
|
||||
elif dt == "Purchase Invoice":
|
||||
party_account = doc.credit_to
|
||||
elif dt == "Fees":
|
||||
party_account = doc.receivable_account
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
|
||||
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
||||
|
||||
# payment type
|
||||
if (dt == "Sales Order" or (dt=="Sales Invoice" and doc.outstanding_amount > 0)) \
|
||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \
|
||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
@ -704,6 +721,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
elif dt in ("Expense Claim"):
|
||||
grand_total = doc.total_sanctioned_amount
|
||||
outstanding_amount = doc.total_sanctioned_amount - doc.total_amount_reimbursed
|
||||
elif dt == "Fees":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
else:
|
||||
total_field = "base_grand_total" if party_account_currency == doc.company_currency else "grand_total"
|
||||
grand_total = flt(doc.get(total_field))
|
||||
@ -745,6 +765,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
pe.append("references", {
|
||||
"reference_doctype": dt,
|
||||
"reference_name": dn,
|
||||
"bill_no": doc.get("bill_no"),
|
||||
"due_date": doc.get("due_date"),
|
||||
"total_amount": grand_total,
|
||||
"outstanding_amount": outstanding_amount,
|
||||
|
@ -267,3 +267,65 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
return frappe.db.sql("""select account, debit, credit, against_voucher
|
||||
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
|
||||
order by account asc""", voucher_no, as_dict=1)
|
||||
|
||||
def test_payment_entry_write_off_difference(self):
|
||||
si = create_sales_invoice()
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
pe.received_amount = pe.paid_amount = 110
|
||||
pe.insert()
|
||||
|
||||
self.assertEqual(pe.unallocated_amount, 10)
|
||||
|
||||
pe.received_amount = pe.paid_amount = 95
|
||||
pe.append("deductions", {
|
||||
"account": "_Test Write Off - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": 5
|
||||
})
|
||||
pe.save()
|
||||
|
||||
self.assertEqual(pe.unallocated_amount, 0)
|
||||
self.assertEqual(pe.difference_amount, 0)
|
||||
|
||||
pe.submit()
|
||||
|
||||
expected_gle = dict((d[0], d) for d in [
|
||||
["Debtors - _TC", 0, 100, si.name],
|
||||
["_Test Cash - _TC", 95, 0, None],
|
||||
["_Test Write Off - _TC", 5, 0, None]
|
||||
])
|
||||
|
||||
self.validate_gl_entries(pe.name, expected_gle)
|
||||
|
||||
def test_payment_entry_exchange_gain_loss(self):
|
||||
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
pe.target_exchange_rate = 55
|
||||
|
||||
pe.append("deductions", {
|
||||
"account": "_Test Exchange Gain/Loss - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": -500
|
||||
})
|
||||
pe.save()
|
||||
|
||||
self.assertEqual(pe.unallocated_amount, 0)
|
||||
self.assertEqual(pe.difference_amount, 0)
|
||||
|
||||
pe.submit()
|
||||
|
||||
expected_gle = dict((d[0], d) for d in [
|
||||
["_Test Receivable USD - _TC", 0, 5000, si.name],
|
||||
["_Test Bank USD - _TC", 5500, 0, None],
|
||||
["_Test Exchange Gain/Loss - _TC", 0, 500, None],
|
||||
])
|
||||
|
||||
self.validate_gl_entries(pe.name, expected_gle)
|
||||
|
||||
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
|
||||
self.assertEqual(outstanding_amount, 0)
|
||||
|
@ -0,0 +1,55 @@
|
||||
QUnit.module('Payment Entry');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(6);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'item_code': 'Test Product 1'},
|
||||
{'qty': 1},
|
||||
{'rate': 101},
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Payment'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
||||
'made payment entry');
|
||||
assert.equal(cur_frm.doc.party, 'Test Customer 1',
|
||||
'customer set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_amount, 101,
|
||||
'paid amount set in payment entry');
|
||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
|
||||
'amount allocated against sales invoice');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value('paid_amount', 100),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.model.set_value("Payment Entry Reference", cur_frm.doc.references[0].name,
|
||||
"allocated_amount", 101);
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Write Off Difference Amount'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
||||
assert.equal(cur_frm.doc.deductions[0].amount, 1, 'Write off amount = 1');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -25,5 +25,4 @@ QUnit.test("test payment entry", function(assert) {
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,67 @@
|
||||
QUnit.module('Payment Entry');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(8);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{company: 'For Testing'},
|
||||
{currency: 'INR'},
|
||||
{selling_price_list: '_Test Price List'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 1},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1.5),
|
||||
() => frappe.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Payment'),
|
||||
() => frappe.timeout(2),
|
||||
() => cur_frm.set_value("paid_to", "_Test Cash - FT"),
|
||||
() => frappe.timeout(0.5),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
|
||||
assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_from, 'Debtors - FT', 'customer account set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
|
||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
|
||||
'amount allocated against sales invoice');
|
||||
},
|
||||
() => cur_frm.set_value('paid_amount', 95),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.model.set_value("Payment Entry Reference",
|
||||
cur_frm.doc.references[0].name, "allocated_amount", 100);
|
||||
},
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
|
||||
},
|
||||
() => {
|
||||
frappe.db.set_value("Company", "For Testing", "write_off_account", "_Test Write Off - FT");
|
||||
frappe.timeout(1);
|
||||
frappe.db.set_value("Company", "For Testing",
|
||||
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - FT");
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Write Off Difference Amount'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
||||
assert.equal(cur_frm.doc.deductions[0].amount, 5, 'Write off amount = 5');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
@ -12,6 +13,7 @@
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -42,6 +44,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -72,6 +75,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -101,6 +105,38 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "bill_no",
|
||||
"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": "Supplier Invoice No",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -129,6 +165,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -158,6 +195,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -187,6 +225,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -216,10 +255,12 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
|
||||
"fieldname": "exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
@ -245,17 +286,17 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-02-17 16:47:17.156256",
|
||||
"modified": "2017-09-04 17:37:01.192312",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
@ -35,7 +35,6 @@ class PaymentRequest(Document):
|
||||
|
||||
def on_submit(self):
|
||||
send_mail = True
|
||||
self.make_communication_entry()
|
||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||
|
||||
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
|
||||
@ -45,6 +44,7 @@ class PaymentRequest(Document):
|
||||
if send_mail:
|
||||
self.set_payment_request_url()
|
||||
self.send_email()
|
||||
self.make_communication_entry()
|
||||
|
||||
def on_cancel(self):
|
||||
self.check_if_payment_entry_exists()
|
||||
@ -69,8 +69,11 @@ class PaymentRequest(Document):
|
||||
self.db_set('status', 'Initiated')
|
||||
|
||||
def get_payment_url(self):
|
||||
data = frappe.db.get_value(self.reference_doctype, self.reference_name,
|
||||
["company", "customer_name"], as_dict=1)
|
||||
if self.reference_doctype != "Fees":
|
||||
data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1)
|
||||
else:
|
||||
data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["student_name"], as_dict=1)
|
||||
data.update({"company": frappe.defaults.get_defaults().company})
|
||||
|
||||
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
@ -277,6 +280,9 @@ def get_amount(ref_doc, dt):
|
||||
else:
|
||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||
|
||||
if dt == "Fees":
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
if grand_total > 0 :
|
||||
return grand_total
|
||||
|
||||
|
@ -8,10 +8,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
|
||||
});
|
||||
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
@ -27,6 +23,27 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Profile', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("online_print_format", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Print Format', 'doc_type', '=', 'Sales Invoice'],
|
||||
['Print Format', 'print_format_type', '!=', 'Js'],
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
|
||||
});
|
||||
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
|
||||
is_online = r && cint(r.is_online)
|
||||
frm.toggle_display('offline_pos_section', !is_online);
|
||||
frm.toggle_display('print_format_for_online', is_online);
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.company) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
|
@ -631,8 +631,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Point of Sale",
|
||||
"fieldname": "print_format",
|
||||
"fieldname": "print_format_for_online",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -641,7 +640,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Print Format",
|
||||
"label": "Print Format for Online",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
@ -822,7 +821,7 @@
|
||||
"columns": 0,
|
||||
"fieldname": "apply_discount",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
@ -836,7 +835,7 @@
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -851,7 +850,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Grand Total",
|
||||
"depends_on": "apply_discount",
|
||||
"depends_on": "",
|
||||
"fieldname": "apply_discount_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -883,7 +882,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "customer_details",
|
||||
"fieldname": "offline_pos_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -892,7 +891,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "New Customer Details",
|
||||
"label": "Offline POS Section",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -969,6 +968,38 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Point of Sale",
|
||||
"fieldname": "print_format",
|
||||
"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": "Print Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1291,7 +1322,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-28 03:40:03.253088",
|
||||
"modified": "2017-09-01 15:55:14.890452",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
8
erpnext/accounts/doctype/pos_settings/pos_settings.js
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('POS Settings', {
|
||||
refresh: function() {
|
||||
|
||||
}
|
||||
});
|
133
erpnext/accounts/doctype/pos_settings/pos_settings.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-08-28 16:46:41.732676",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "use_pos_in_offline_mode",
|
||||
"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": "Use POS in Offline Mode",
|
||||
"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,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-11 13:57:28.787023",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Sales User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"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
|
||||
}
|
9
erpnext/accounts/doctype/pos_settings/pos_settings.py
Normal file
@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSettings(Document):
|
||||
pass
|
23
erpnext/accounts/doctype/pos_settings/test_pos_settings.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: POS Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new POS Settings
|
||||
() => frappe.tests.make('POS Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestPOSSettings(unittest.TestCase):
|
||||
pass
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
@ -12,6 +13,7 @@
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -40,6 +42,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -69,6 +72,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -99,6 +103,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -129,6 +134,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -159,6 +165,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -189,6 +196,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -217,6 +225,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -247,6 +256,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -275,6 +285,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -303,6 +314,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -331,6 +343,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -359,6 +372,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -387,6 +401,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -417,6 +432,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -447,6 +463,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -477,6 +494,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -507,6 +525,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -537,6 +556,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -567,6 +587,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -597,6 +618,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -627,6 +649,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -655,6 +678,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -683,6 +707,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -711,6 +736,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -739,6 +765,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -767,6 +794,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -796,6 +824,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -824,6 +853,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -851,6 +881,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -880,6 +911,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -910,6 +942,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -941,6 +974,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -969,6 +1003,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1000,6 +1035,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1028,6 +1064,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1058,6 +1095,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1085,13 +1123,14 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.price_or_discount==\"Price\"",
|
||||
"fieldname": "price",
|
||||
"fieldtype": "Float",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@ -1114,6 +1153,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1143,6 +1183,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1173,6 +1214,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1202,6 +1244,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -1230,18 +1273,18 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-02-17 16:21:28.446208",
|
||||
"modified": "2017-08-31 16:34:41.614743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -46,6 +46,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
cur_frm.add_custom_button(__('Return / Debit Note'),
|
||||
this.make_debit_note, __("Make"));
|
||||
}
|
||||
|
||||
if(!doc.subscription) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __("Make"))
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.docstatus===0) {
|
||||
@ -343,6 +349,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
'Payment Entry': 'Payment'
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
$.each(["warehouse", "rejected_warehouse"], function(i, field) {
|
||||
frm.set_query(field, "items", function() {
|
||||
@ -370,5 +377,5 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
@ -2072,6 +2072,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"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": "Rounding Adjustment (Company Currency)",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2166,6 +2197,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rounding_adjustment",
|
||||
"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": "Rounding Adjustment",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -3348,6 +3410,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -3358,7 +3481,7 @@
|
||||
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
|
||||
"fieldname": "recurring_invoice",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
@ -3797,7 +3920,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-07-19 13:53:48.673757",
|
||||
"modified": "2017-09-19 11:22:47.074420",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -15,6 +15,7 @@ from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.buying.utils import check_for_closed_status
|
||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@ -353,6 +354,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.make_payment_gl_entries(gl_entries)
|
||||
self.make_write_off_gl_entry(gl_entries)
|
||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||
|
||||
return gl_entries
|
||||
|
||||
@ -584,6 +586,21 @@ class PurchaseInvoice(BuyingController):
|
||||
})
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
if self.rounding_adjustment:
|
||||
round_off_account, round_off_cost_center = \
|
||||
get_round_off_account_and_cost_center(self.company)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": round_off_account,
|
||||
"against": self.supplier,
|
||||
"debit_in_account_currency": self.rounding_adjustment,
|
||||
"debit": self.base_rounding_adjustment,
|
||||
"cost_center": round_off_cost_center,
|
||||
}
|
||||
))
|
||||
|
||||
def on_cancel(self):
|
||||
self.check_for_closed_status()
|
||||
|
||||
@ -667,7 +684,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if account_type != 'Fixed Asset':
|
||||
frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx))
|
||||
|
||||
def on_recurring(self, reference_doc):
|
||||
def on_recurring(self, reference_doc, subscription_doc):
|
||||
self.due_date = None
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -8,7 +8,8 @@ def get_data():
|
||||
'Payment Entry': 'reference_name',
|
||||
'Payment Request': 'reference_name',
|
||||
'Landed Cost Voucher': 'receipt_document',
|
||||
'Purchase Invoice': 'return_against'
|
||||
'Purchase Invoice': 'return_against',
|
||||
'Subscription': 'reference_document'
|
||||
},
|
||||
'internal_links': {
|
||||
'Purchase Order': ['items', 'purchase_order'],
|
||||
@ -27,5 +28,9 @@ def get_data():
|
||||
'label': _('Returns'),
|
||||
'items': ['Purchase Invoice']
|
||||
},
|
||||
{
|
||||
'label': _('Subscription'),
|
||||
'items': ['Subscription']
|
||||
},
|
||||
]
|
||||
}
|
@ -256,10 +256,6 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_type='Purchase Invoice' and reference_name=%s""", pi.name))
|
||||
|
||||
def test_recurring_invoice(self):
|
||||
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
|
||||
test_recurring_document(self, test_records)
|
||||
|
||||
def test_total_purchase_cost_for_project(self):
|
||||
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
||||
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
|
||||
|
@ -86,7 +86,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this.make_payment_request, __("Make"));
|
||||
}
|
||||
|
||||
|
||||
if(!doc.subscription) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __("Make"))
|
||||
}
|
||||
}
|
||||
|
||||
// Show buttons only when pos view is active
|
||||
|
@ -1670,36 +1670,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "net_total",
|
||||
"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": "Net Total",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1731,6 +1701,36 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "net_total",
|
||||
"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": "Net Total",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2337,6 +2337,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"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": "Rounding Adjustment (Company Currency)",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2463,6 +2494,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rounding_adjustment",
|
||||
"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": "Rounding Adjustment",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -4175,6 +4237,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -4185,7 +4308,7 @@
|
||||
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
|
||||
"fieldname": "recurring_invoice",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
@ -4688,7 +4811,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-07-07 13:05:37.469682",
|
||||
"modified": "2017-09-19 11:23:08.675028",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -20,6 +20,7 @@ from erpnext.accounts.doctype.asset.depreciation \
|
||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@ -107,7 +108,7 @@ class SalesInvoice(SellingController):
|
||||
def on_submit(self):
|
||||
self.validate_pos_paid_amount()
|
||||
|
||||
if not self.recurring_id:
|
||||
if not self.subscription:
|
||||
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
|
||||
self.company, self.base_grand_total, self)
|
||||
|
||||
@ -313,7 +314,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
|
||||
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account',
|
||||
'write_off_account', 'write_off_cost_center'):
|
||||
'write_off_account', 'write_off_cost_center', 'apply_discount_on'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
self.set(fieldname, pos.get(fieldname))
|
||||
|
||||
@ -625,6 +626,7 @@ class SalesInvoice(SellingController):
|
||||
self.make_gle_for_change_amount(gl_entries)
|
||||
|
||||
self.make_write_off_gl_entry(gl_entries)
|
||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||
|
||||
return gl_entries
|
||||
|
||||
@ -784,6 +786,21 @@ class SalesInvoice(SellingController):
|
||||
}, write_off_account_currency)
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
if self.rounding_adjustment:
|
||||
round_off_account, round_off_cost_center = \
|
||||
get_round_off_account_and_cost_center(self.company)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": round_off_account,
|
||||
"against": self.customer,
|
||||
"credit_in_account_currency": self.rounding_adjustment,
|
||||
"credit": self.base_rounding_adjustment,
|
||||
"cost_center": round_off_cost_center,
|
||||
}
|
||||
))
|
||||
|
||||
def update_billing_status_in_dn(self, update_modified=True):
|
||||
updated_delivery_notes = []
|
||||
for d in self.get("items"):
|
||||
@ -799,7 +816,7 @@ class SalesInvoice(SellingController):
|
||||
for dn in set(updated_delivery_notes):
|
||||
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
|
||||
|
||||
def on_recurring(self, reference_doc):
|
||||
def on_recurring(self, reference_doc, subscription_doc):
|
||||
for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"):
|
||||
self.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
|
@ -8,7 +8,8 @@ def get_data():
|
||||
'Journal Entry': 'reference_name',
|
||||
'Payment Entry': 'reference_name',
|
||||
'Payment Request': 'reference_name',
|
||||
'Sales Invoice': 'return_against'
|
||||
'Sales Invoice': 'return_against',
|
||||
'Subscription': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
@ -26,5 +27,9 @@ def get_data():
|
||||
'label': _('Returns'),
|
||||
'items': ['Sales Invoice']
|
||||
},
|
||||
{
|
||||
'label': _('Subscription'),
|
||||
'items': ['Subscription']
|
||||
},
|
||||
]
|
||||
}
|
@ -215,12 +215,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
# with inclusive tax and additional discount
|
||||
self.assertEquals(si.net_total, 4298.24)
|
||||
self.assertEquals(si.net_total, 4298.25)
|
||||
self.assertEquals(si.grand_total, 4900.00)
|
||||
|
||||
def test_sales_invoice_discount_amount(self):
|
||||
si = frappe.copy_doc(test_records[3])
|
||||
si.discount_amount = 104.95
|
||||
si.discount_amount = 104.94
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Previous Row Amount",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
@ -285,7 +285,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
|
||||
"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
|
||||
"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
|
||||
"_Test Account Service Tax - _TC": [-18.03, -16.86, 1500]
|
||||
"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01]
|
||||
}
|
||||
|
||||
for d in si.get("taxes"):
|
||||
@ -294,10 +294,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.assertEquals(si.base_grand_total, 1500)
|
||||
self.assertEquals(si.grand_total, 1500)
|
||||
self.assertEquals(si.rounding_adjustment, -0.01)
|
||||
|
||||
def test_discount_amount_gl_entry(self):
|
||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||
si = frappe.copy_doc(test_records[3])
|
||||
si.discount_amount = 104.95
|
||||
si.discount_amount = 104.94
|
||||
si.append("taxes", {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"charge_type": "On Previous Row Amount",
|
||||
@ -327,7 +329,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
|
||||
[test_records[3]["taxes"][6]["account_head"], 0.0, 100],
|
||||
[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
|
||||
["_Test Account Service Tax - _TC", 16.86, 0.0]
|
||||
["_Test Account Service Tax - _TC", 16.85, 0.0],
|
||||
["Round Off - _TC", 0.01, 0.0]
|
||||
])
|
||||
|
||||
for gle in gl_entries:
|
||||
@ -423,13 +426,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
expected_values = {
|
||||
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
|
||||
"base_price_list_rate", "base_rate", "base_amount", "net_rate", "net_amount"],
|
||||
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.98],
|
||||
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 750],
|
||||
"_Test Item Home Desktop 100": [62.5, 0, 62.5, 625.0, 62.5, 62.5, 625.0, 50, 499.97600115194473],
|
||||
"_Test Item Home Desktop 200": [190.66, 0, 190.66, 953.3, 190.66, 190.66, 953.3, 150, 749.9968530500239],
|
||||
}
|
||||
|
||||
# check if children are saved
|
||||
self.assertEquals(len(si.get("items")),
|
||||
len(expected_values)-1)
|
||||
self.assertEquals(len(si.get("items")), len(expected_values)-1)
|
||||
|
||||
# check if item values are calculated
|
||||
for d in si.get("items"):
|
||||
@ -437,28 +439,28 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
||||
|
||||
# check net total
|
||||
self.assertEquals(si.base_net_total, 1249.98)
|
||||
self.assertEquals(si.net_total, 1249.97)
|
||||
self.assertEquals(si.total, 1578.3)
|
||||
|
||||
# check tax calculation
|
||||
expected_values = {
|
||||
"keys": ["tax_amount", "total"],
|
||||
"_Test Account Excise Duty - _TC": [140, 1389.98],
|
||||
"_Test Account Education Cess - _TC": [2.8, 1392.78],
|
||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.18],
|
||||
"_Test Account CST - _TC": [27.88, 1422.06],
|
||||
"_Test Account VAT - _TC": [156.25, 1578.31],
|
||||
"_Test Account Customs Duty - _TC": [125, 1703.31],
|
||||
"_Test Account Shipping Charges - _TC": [100, 1803.31],
|
||||
"_Test Account Discount - _TC": [-180.33, 1622.98]
|
||||
"_Test Account Excise Duty - _TC": [140, 1389.97],
|
||||
"_Test Account Education Cess - _TC": [2.8, 1392.77],
|
||||
"_Test Account S&H Education Cess - _TC": [1.4, 1394.17],
|
||||
"_Test Account CST - _TC": [27.88, 1422.05],
|
||||
"_Test Account VAT - _TC": [156.25, 1578.30],
|
||||
"_Test Account Customs Duty - _TC": [125, 1703.30],
|
||||
"_Test Account Shipping Charges - _TC": [100, 1803.30],
|
||||
"_Test Account Discount - _TC": [-180.33, 1622.97]
|
||||
}
|
||||
|
||||
for d in si.get("taxes"):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||
|
||||
self.assertEquals(si.base_grand_total, 1622.98)
|
||||
self.assertEquals(si.grand_total, 1622.98)
|
||||
self.assertEquals(si.base_grand_total, 1622.97)
|
||||
self.assertEquals(si.grand_total, 1622.97)
|
||||
|
||||
def test_sales_invoice_calculation_export_currency_with_tax_inclusive_price(self):
|
||||
# prepare
|
||||
@ -486,7 +488,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"base_rate": 2500,
|
||||
"base_amount": 25000,
|
||||
"net_rate": 40,
|
||||
"net_amount": 399.98,
|
||||
"net_amount": 399.9808009215558,
|
||||
"base_net_rate": 2000,
|
||||
"base_net_amount": 19999
|
||||
},
|
||||
@ -500,7 +502,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"base_rate": 7500,
|
||||
"base_amount": 37500,
|
||||
"net_rate": 118.01,
|
||||
"net_amount": 590.05,
|
||||
"net_amount": 590.0531205155963,
|
||||
"base_net_rate": 5900.5,
|
||||
"base_net_amount": 29502.5
|
||||
}
|
||||
@ -536,8 +538,11 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||
|
||||
self.assertEquals(si.base_grand_total, 60794.5)
|
||||
self.assertEquals(si.grand_total, 1215.89)
|
||||
self.assertEquals(si.base_grand_total, 60795)
|
||||
self.assertEquals(si.grand_total, 1215.90)
|
||||
self.assertEquals(si.rounding_adjustment, 0.01)
|
||||
self.assertEquals(si.base_rounding_adjustment, 0.50)
|
||||
|
||||
|
||||
def test_outstanding(self):
|
||||
w = self.make()
|
||||
@ -809,10 +814,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
|
||||
where reference_name=%s""", si.name))
|
||||
|
||||
def test_recurring_invoice(self):
|
||||
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
|
||||
test_recurring_document(self, test_records)
|
||||
|
||||
def test_serialized(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@ -1167,8 +1168,15 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(flt(si.outstanding_amount), flt(si.grand_total + si.total_advance, si.precision("outstanding_amount")))
|
||||
|
||||
def test_multiple_uom_in_selling(self):
|
||||
si = frappe.copy_doc(test_records[1])
|
||||
frappe.db.sql("""delete from `tabItem Price`
|
||||
where price_list='_Test Price List' and item_code='_Test Item'""")
|
||||
item_price = frappe.new_doc("Item Price")
|
||||
item_price.price_list = "_Test Price List"
|
||||
item_price.item_code = "_Test Item"
|
||||
item_price.price_list_rate = 100
|
||||
item_price.insert()
|
||||
|
||||
si = frappe.copy_doc(test_records[1])
|
||||
si.items[0].uom = "_Test UOM 1"
|
||||
si.items[0].conversion_factor = None
|
||||
si.items[0].price_list_rate = None
|
||||
@ -1283,6 +1291,40 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
current_month_sales = frappe.db.get_value("Company", "_Test Company", "total_monthly_sales")
|
||||
self.assertEqual(current_month_sales, existing_current_month_sales)
|
||||
|
||||
def test_rounding_adjustment(self):
|
||||
si = create_sales_invoice(rate=24900, do_not_save=True)
|
||||
for tax in ["Tax 1", "Tax2"]:
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"description": tax,
|
||||
"rate": 14,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"included_in_print_rate": 1
|
||||
})
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.net_total, 19453.13)
|
||||
self.assertEqual(si.grand_total, 24900)
|
||||
self.assertEqual(si.total_taxes_and_charges, 5446.88)
|
||||
self.assertEqual(si.rounding_adjustment, -0.01)
|
||||
|
||||
expected_values = dict((d[0], d) for d in [
|
||||
[si.debit_to, 24900, 0.0],
|
||||
["_Test Account Service Tax - _TC", 0.0, 5446.88],
|
||||
["Sales - _TC", 0.0, 19453.13],
|
||||
["Round Off - _TC", 0.01, 0.0]
|
||||
])
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
|
||||
order by account asc""", si.name, as_dict=1)
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.account)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
si = frappe.new_doc("Sales Invoice")
|
||||
args = frappe._dict(args)
|
||||
|
0
erpnext/accounts/doctype/subscription/__init__.py
Normal file
68
erpnext/accounts/doctype/subscription/subscription.js
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Subscription', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": 1
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['print_format'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": frm.doc.reference_doctype
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus == 1) {
|
||||
let label = __('View {0}', [frm.doc.reference_doctype]);
|
||||
frm.add_custom_button(__(label),
|
||||
function() {
|
||||
frappe.route_options = {
|
||||
"subscription": frm.doc.name,
|
||||
};
|
||||
frappe.set_route("List", frm.doc.reference_doctype);
|
||||
}
|
||||
);
|
||||
|
||||
if(frm.doc.status != 'Stopped') {
|
||||
frm.add_custom_button(__("Stop"),
|
||||
function() {
|
||||
frm.events.stop_resume_subscription(frm, "Stopped");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(frm.doc.status == 'Stopped') {
|
||||
frm.add_custom_button(__("Resume"),
|
||||
function() {
|
||||
frm.events.stop_resume_subscription(frm, "Resumed");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stop_resume_subscription: function(frm, status) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.subscription.subscription.stop_resume_subscription",
|
||||
args: {
|
||||
subscription: frm.doc.name,
|
||||
status: status
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("status", r.message);
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
771
erpnext/accounts/doctype/subscription/subscription.json
Normal file
@ -0,0 +1,771 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2017-07-18 17:50:43.967266",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_1",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "naming_series",
|
||||
"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": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "SUB-",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"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": "Reference Doctype",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_document",
|
||||
"fieldtype": "Dynamic 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": "Reference Document",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "reference_doctype",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"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": "Disabled",
|
||||
"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": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "submit_on_creation",
|
||||
"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": "Submit on Creation",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Start Date",
|
||||
"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": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "End Date",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "next_schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Next Schedule Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "frequency_detail",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "frequency",
|
||||
"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": "Frequency",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_12",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: in_list([\"Monthly\", \"Quarterly\", \"Yearly\"], doc.frequency)",
|
||||
"fieldname": "repeat_on_day",
|
||||
"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": "Repeat on Day",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "notification",
|
||||
"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": "Notification",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "notify_by_email",
|
||||
"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": "Notify by Email",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_17",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "recipients",
|
||||
"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": "Recipients",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "print_format",
|
||||
"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": "Print Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_16",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"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": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nDraft\nStopped\nSubmitted\nCancelled\nCompleted",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"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": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-14 12:09:38.471458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription",
|
||||
"name_case": "",
|
||||
"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": 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": 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": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "reference_document",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "reference_document",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
228
erpnext/accounts/doctype/subscription/subscription.py
Normal file
@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import calendar
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today
|
||||
from frappe.model.document import Document
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
class Subscription(Document):
|
||||
def validate(self):
|
||||
self.update_status()
|
||||
self.validate_dates()
|
||||
self.validate_next_schedule_date()
|
||||
self.validate_email_id()
|
||||
|
||||
def before_submit(self):
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_subscription_id()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.validate_dates()
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
|
||||
frappe.throw(_("End date must be greater than start date"))
|
||||
|
||||
def validate_next_schedule_date(self):
|
||||
if self.repeat_on_day and self.next_schedule_date:
|
||||
next_date = getdate(self.next_schedule_date)
|
||||
if next_date.day != self.repeat_on_day:
|
||||
# if the repeat day is the last day of the month (31)
|
||||
# and the current month does not have as many days,
|
||||
# then the last day of the current month is a valid date
|
||||
lastday = calendar.monthrange(next_date.year, next_date.month)[1]
|
||||
if self.repeat_on_day < lastday:
|
||||
|
||||
# the specified day of the month is not same as the day specified
|
||||
# or the last day of the month
|
||||
frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.notify_by_email:
|
||||
if self.recipients:
|
||||
email_list = split_emails(self.recipients.replace("\n", ""))
|
||||
|
||||
from frappe.utils import validate_email_add
|
||||
for email in email_list:
|
||||
if not validate_email_add(email):
|
||||
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
|
||||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def set_next_schedule_date(self):
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date,
|
||||
self.frequency, self.repeat_on_day)
|
||||
|
||||
def update_subscription_id(self):
|
||||
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
if not doc.meta.get_field('subscription'):
|
||||
frappe.throw(_("Add custom field Subscription Id in the doctype {0}").format(self.reference_doctype))
|
||||
|
||||
doc.db_set('subscription', self.name)
|
||||
|
||||
def update_status(self, status=None):
|
||||
self.status = {
|
||||
'0': 'Draft',
|
||||
'1': 'Submitted',
|
||||
'2': 'Cancelled'
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
if status and status != 'Resumed':
|
||||
self.status = status
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day):
|
||||
mcount = month_map.get(frequency)
|
||||
if mcount:
|
||||
next_date = get_next_date(start_date, mcount, repeat_on_day)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(start_date, days)
|
||||
return next_date
|
||||
|
||||
def make_subscription_entry(date=None):
|
||||
date = date or today()
|
||||
for data in get_subscription_entries(date):
|
||||
schedule_date = getdate(data.next_schedule_date)
|
||||
while schedule_date <= getdate(today()):
|
||||
create_documents(data, schedule_date)
|
||||
schedule_date = get_next_schedule_date(schedule_date,
|
||||
data.frequency, data.repeat_on_day)
|
||||
|
||||
if schedule_date and not frappe.db.get_value('Subscription', data.name, 'disabled'):
|
||||
frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date)
|
||||
|
||||
def get_subscription_entries(date):
|
||||
return frappe.db.sql(""" select * from `tabSubscription`
|
||||
where docstatus = 1 and next_schedule_date <=%s
|
||||
and reference_document is not null and reference_document != ''
|
||||
and next_schedule_date <= ifnull(end_date, '2199-12-31')
|
||||
and ifnull(disabled, 0) = 0 and status != 'Stopped' """, (date), as_dict=1)
|
||||
|
||||
def create_documents(data, schedule_date):
|
||||
try:
|
||||
doc = make_new_document(data, schedule_date)
|
||||
if data.notify_by_email and data.recipients:
|
||||
print_format = data.print_format or "Standard"
|
||||
send_notification(doc, print_format, data.recipients)
|
||||
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.db.begin()
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
disabled_subscription(data)
|
||||
frappe.db.commit()
|
||||
if data.reference_document and not frappe.flags.in_test:
|
||||
notify_error_to_user(data)
|
||||
|
||||
def disabled_subscription(data):
|
||||
subscription = frappe.get_doc('Subscription', data.name)
|
||||
subscription.db_set('disabled', 1)
|
||||
|
||||
def notify_error_to_user(data):
|
||||
party = ''
|
||||
party_type = ''
|
||||
|
||||
if data.reference_doctype in ['Sales Order', 'Sales Invoice', 'Delivery Note']:
|
||||
party_type = 'customer'
|
||||
elif data.reference_doctype in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
party_type = 'supplier'
|
||||
|
||||
if party_type:
|
||||
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
|
||||
|
||||
notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
|
||||
|
||||
def make_new_document(args, schedule_date):
|
||||
doc = frappe.get_doc(args.reference_doctype, args.reference_document)
|
||||
new_doc = frappe.copy_doc(doc, ignore_no_copy=False)
|
||||
update_doc(new_doc, doc , args, schedule_date)
|
||||
new_doc.insert(ignore_permissions=True)
|
||||
|
||||
if args.submit_on_creation:
|
||||
new_doc.submit()
|
||||
|
||||
return new_doc
|
||||
|
||||
def update_doc(new_document, reference_doc, args, schedule_date):
|
||||
new_document.docstatus = 0
|
||||
if new_document.meta.get_field('set_posting_time'):
|
||||
new_document.set('set_posting_time', 1)
|
||||
|
||||
if new_document.meta.get_field('subscription'):
|
||||
new_document.set('subscription', args.name)
|
||||
|
||||
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
|
||||
for data in new_document.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
new_document.set(data.fieldname, schedule_date)
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
|
||||
return dt
|
||||
|
||||
def send_notification(new_rv, print_format='Standard', recipients=None):
|
||||
"""Notify concerned persons about recurring document generation"""
|
||||
print_format = print_format
|
||||
|
||||
frappe.sendmail(recipients,
|
||||
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
|
||||
|
||||
def notify_errors(doc, doctype, party, owner, name):
|
||||
recipients = get_system_managers(only_name=True)
|
||||
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
|
||||
subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)),
|
||||
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
|
||||
"type": _(doctype),
|
||||
"name": doc,
|
||||
"party": party or "",
|
||||
"subscription": name
|
||||
}))
|
||||
|
||||
assign_task_to_owner(name, "Recurring Documents Failed", recipients)
|
||||
|
||||
def assign_task_to_owner(name, msg, users):
|
||||
for d in users:
|
||||
args = {
|
||||
'doctype' : 'Subscription',
|
||||
'assign_to' : d,
|
||||
'name' : name,
|
||||
'description' : msg,
|
||||
'priority' : 'High'
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_subscription(doctype, docname):
|
||||
doc = frappe.new_doc('Subscription')
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_document = docname
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def stop_resume_subscription(subscription, status):
|
||||
doc = frappe.get_doc('Subscription', subscription)
|
||||
frappe.msgprint(_("Subscription has been {0}").format(status))
|
||||
if status == 'Resumed':
|
||||
doc.next_schedule_date = get_next_schedule_date(today(),
|
||||
doc.frequency, doc.repeat_on_day)
|
||||
|
||||
doc.update_status(status)
|
||||
doc.save()
|
||||
|
||||
return doc.status
|
16
erpnext/accounts/doctype/subscription/subscription_list.js
Normal file
@ -0,0 +1,16 @@
|
||||
frappe.listview_settings['Subscription'] = {
|
||||
add_fields: ["next_schedule_date"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.disabled) {
|
||||
return [__("Disabled"), "red"];
|
||||
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') {
|
||||
return [__("Active"), "green"];
|
||||
} else if(doc.docstatus === 0) {
|
||||
return [__("Draft"), "red", "docstatus,=,0"];
|
||||
} else if(doc.status === 'Stopped') {
|
||||
return [__("Stopped"), "red"];
|
||||
} else {
|
||||
return [__("Expired"), "darkgrey"];
|
||||
}
|
||||
}
|
||||
};
|
32
erpnext/accounts/doctype/subscription/test_subscription.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Subscription", function (assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
// insert a new Subscription
|
||||
() => {
|
||||
return frappe.tests.make("Subscription", [
|
||||
{reference_doctype: 'Sales Invoice'},
|
||||
{reference_document: 'SINV-00004'},
|
||||
{start_date: frappe.datetime.month_start()},
|
||||
{end_date: frappe.datetime.month_end()},
|
||||
{frequency: 'Weekly'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.savesubmit(),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Yes'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.frequency.includes("Weekly"), "Set frequency Weekly");
|
||||
assert.ok(cur_frm.doc.reference_doctype.includes("Sales Invoice"), "Set base doctype Sales Invoice");
|
||||
assert.equal(cur_frm.doc.docstatus, 1, "Submitted subscription");
|
||||
assert.equal(cur_frm.doc.next_schedule_date,
|
||||
frappe.datetime.add_days(frappe.datetime.get_today(), 7), "Set schedule date");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
93
erpnext/accounts/doctype/subscription/test_subscription.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import today, add_days, getdate
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.accounts.report.financial_statements import get_months
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.accounts.doctype.subscription.subscription import make_subscription_entry
|
||||
|
||||
class TestSubscription(unittest.TestCase):
|
||||
def test_daily_subscription(self):
|
||||
qo = frappe.copy_doc(quotation_records[0])
|
||||
qo.submit()
|
||||
|
||||
doc = make_subscription(reference_document=qo.name)
|
||||
self.assertEquals(doc.next_schedule_date, today())
|
||||
make_subscription_entry()
|
||||
frappe.db.commit()
|
||||
|
||||
quotation = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
self.assertEquals(quotation.subscription, doc.name)
|
||||
|
||||
new_quotation = frappe.db.get_value('Quotation',
|
||||
{'subscription': doc.name, 'name': ('!=', quotation.name)}, 'name')
|
||||
|
||||
new_quotation = frappe.get_doc('Quotation', new_quotation)
|
||||
|
||||
for fieldname in ['customer', 'company', 'order_type', 'total', 'grand_total']:
|
||||
self.assertEquals(quotation.get(fieldname), new_quotation.get(fieldname))
|
||||
|
||||
for fieldname in ['item_code', 'qty', 'rate', 'amount']:
|
||||
self.assertEquals(quotation.items[0].get(fieldname),
|
||||
new_quotation.items[0].get(fieldname))
|
||||
|
||||
def test_monthly_subscription_for_so(self):
|
||||
current_fiscal_year = get_fiscal_year(today(), as_dict=True)
|
||||
start_date = current_fiscal_year.year_start_date
|
||||
end_date = current_fiscal_year.year_end_date
|
||||
|
||||
for doctype in ['Sales Order', 'Sales Invoice']:
|
||||
if doctype == 'Sales Invoice':
|
||||
docname = create_sales_invoice(posting_date=start_date)
|
||||
else:
|
||||
docname = make_sales_order()
|
||||
|
||||
self.monthly_subscription(doctype, docname.name, start_date, end_date)
|
||||
|
||||
def monthly_subscription(self, doctype, docname, start_date, end_date):
|
||||
doc = make_subscription(reference_doctype=doctype, frequency = 'Monthly',
|
||||
reference_document = docname, start_date=start_date, end_date=end_date)
|
||||
|
||||
doc.disabled = 1
|
||||
doc.save()
|
||||
frappe.db.commit()
|
||||
|
||||
make_subscription_entry()
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name})
|
||||
self.assertEquals(len(docnames), 1)
|
||||
|
||||
doc = frappe.get_doc('Subscription', doc.name)
|
||||
doc.disabled = 0
|
||||
doc.save()
|
||||
|
||||
months = get_months(getdate(start_date), getdate(today()))
|
||||
make_subscription_entry()
|
||||
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name})
|
||||
self.assertEquals(len(docnames), months)
|
||||
|
||||
quotation_records = frappe.get_test_records('Quotation')
|
||||
|
||||
def make_subscription(**args):
|
||||
args = frappe._dict(args)
|
||||
doc = frappe.get_doc({
|
||||
'doctype': 'Subscription',
|
||||
'reference_doctype': args.reference_doctype or 'Quotation',
|
||||
'reference_document': args.reference_document or \
|
||||
frappe.db.get_value('Quotation', {'docstatus': 1}, 'name'),
|
||||
'frequency': args.frequency or 'Daily',
|
||||
'start_date': args.start_date or add_days(today(), -1),
|
||||
'end_date': args.end_date or add_days(today(), 1),
|
||||
'submit_on_creation': args.submit_on_creation or 0
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
if not args.do_not_submit:
|
||||
doc.submit()
|
||||
|
||||
return doc
|
@ -135,7 +135,8 @@ def get_tax_template(posting_date, args):
|
||||
for key, value in args.iteritems():
|
||||
if key=="use_for_shopping_cart":
|
||||
conditions.append("use_for_shopping_cart = {0}".format(1 if value else 0))
|
||||
if key == 'customer_group' and value:
|
||||
if key == 'customer_group':
|
||||
if not value: value = _("All Customer Groups")
|
||||
customer_group_condition = get_customer_group_condition(value)
|
||||
conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition))
|
||||
else:
|
||||
|
@ -39,7 +39,7 @@ class TestTaxRule(unittest.TestCase):
|
||||
sales_tax_template = "_Test Sales Taxes and Charges Template", priority = 1, from_date = "2015-01-01")
|
||||
tax_rule1.save()
|
||||
|
||||
self.assertEquals(get_tax_template("2015-01-01", {"customer_group" : "Commercial"}),
|
||||
self.assertEquals(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}),
|
||||
"_Test Sales Taxes and Charges Template")
|
||||
|
||||
def test_conflict_with_overlapping_dates(self):
|
||||
|
@ -136,14 +136,7 @@ def round_off_debit_credit(gl_map):
|
||||
make_round_off_gle(gl_map, debit_credit_diff)
|
||||
|
||||
def make_round_off_gle(gl_map, debit_credit_diff):
|
||||
round_off_account, round_off_cost_center = frappe.db.get_value("Company", gl_map[0].company,
|
||||
["round_off_account", "round_off_cost_center"]) or [None, None]
|
||||
if not round_off_account:
|
||||
frappe.throw(_("Please mention Round Off Account in Company"))
|
||||
|
||||
if not round_off_cost_center:
|
||||
frappe.throw(_("Please mention Round Off Cost Center in Company"))
|
||||
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(gl_map[0].company)
|
||||
|
||||
round_off_gle = frappe._dict()
|
||||
for k in ["voucher_type", "voucher_no", "company",
|
||||
@ -165,6 +158,17 @@ def make_round_off_gle(gl_map, debit_credit_diff):
|
||||
|
||||
gl_map.append(round_off_gle)
|
||||
|
||||
def get_round_off_account_and_cost_center(company):
|
||||
round_off_account, round_off_cost_center = frappe.db.get_value("Company", company,
|
||||
["round_off_account", "round_off_cost_center"]) or [None, None]
|
||||
if not round_off_account:
|
||||
frappe.throw(_("Please mention Round Off Account in Company"))
|
||||
|
||||
if not round_off_cost_center:
|
||||
frappe.throw(_("Please mention Round Off Cost Center in Company"))
|
||||
|
||||
return round_off_account, round_off_cost_center
|
||||
|
||||
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
adv_adj=False, update_outstanding="Yes"):
|
||||
|
||||
|
@ -8,7 +8,16 @@ frappe.pages['pos'].on_page_load = function (wrapper) {
|
||||
single_column: true
|
||||
});
|
||||
|
||||
wrapper.pos = new erpnext.pos.PointOfSale(wrapper)
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
|
||||
if (r && r.use_pos_in_offline_mode && cint(r.use_pos_in_offline_mode)) {
|
||||
// offline
|
||||
wrapper.pos = new erpnext.pos.PointOfSale(wrapper);
|
||||
cur_pos = wrapper.pos;
|
||||
} else {
|
||||
// online
|
||||
frappe.set_route('point-of-sale');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.pages['pos'].refresh = function (wrapper) {
|
||||
|
@ -1,16 +1,15 @@
|
||||
QUnit.test("test:POS Profile", function(assert) {
|
||||
assert.expect(1);
|
||||
QUnit.test("test:Sales Invoice", function(assert) {
|
||||
assert.expect(3);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make("POS Profile", [
|
||||
{naming_series: "SINV"},
|
||||
{company: "Test Company"},
|
||||
{country: "India"},
|
||||
{currency: "INR"},
|
||||
{write_off_account: "Write Off - TC"},
|
||||
{write_off_cost_center: "Main - TC"},
|
||||
{write_off_account: "Write Off - FT"},
|
||||
{write_off_cost_center: "Main - FT"},
|
||||
{payments: [
|
||||
[
|
||||
{"default": 1},
|
||||
@ -24,19 +23,10 @@ QUnit.test("test:POS Profile", function(assert) {
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("test:Sales Invoice", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
return frappe.tests.make("Sales Invoice", [
|
||||
{customer: "Test Customer 2"},
|
||||
{company: "Test Company"},
|
||||
{is_pos: 1},
|
||||
{posting_date: frappe.datetime.get_today()},
|
||||
{due_date: frappe.datetime.get_today()},
|
||||
@ -44,7 +34,7 @@ QUnit.test("test:Sales Invoice", function(assert) {
|
||||
[
|
||||
{"item_code": "Test Product 1"},
|
||||
{"qty": 5},
|
||||
{"warehouse":'Stores - TC'}
|
||||
{"warehouse":'Stores - FT'}
|
||||
]]
|
||||
}
|
||||
]);
|
||||
|
@ -142,10 +142,16 @@ def get_data(company, root_type, balance_must_be, period_list, filters=None,
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def calculate_values(accounts_by_name, gl_entries_by_account, period_list, accumulated_values, ignore_accumulated_values_for_fy):
|
||||
for entries in gl_entries_by_account.values():
|
||||
for entry in entries:
|
||||
d = accounts_by_name.get(entry.account)
|
||||
if not d:
|
||||
frappe.msgprint(
|
||||
_("Could not retrieve information for {0}.".format(entry.account)), title="Error",
|
||||
raise_exception=1
|
||||
)
|
||||
for period in period_list:
|
||||
# check if posting date is within the period
|
||||
|
||||
|
@ -17,7 +17,6 @@ def execute(filters=None):
|
||||
gross_profit_data = GrossProfitGenerator(filters)
|
||||
|
||||
data = []
|
||||
source = gross_profit_data.grouped_data if filters.get("group_by") != "Invoice" else gross_profit_data.data
|
||||
|
||||
group_wise_columns = frappe._dict({
|
||||
"invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
|
||||
@ -45,7 +44,7 @@ def execute(filters=None):
|
||||
|
||||
columns = get_columns(group_wise_columns, filters)
|
||||
|
||||
for src in source:
|
||||
for src in gross_profit_data.grouped_data:
|
||||
row = []
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
row.append(src.get(col))
|
||||
@ -103,6 +102,7 @@ class GrossProfitGenerator(object):
|
||||
self.load_stock_ledger_entries()
|
||||
self.load_product_bundle()
|
||||
self.load_non_stock_items()
|
||||
self.get_returned_invoice_items()
|
||||
self.process()
|
||||
|
||||
def process(self):
|
||||
@ -143,40 +143,68 @@ class GrossProfitGenerator(object):
|
||||
row.gross_profit_percent = 0.0
|
||||
|
||||
# add to grouped
|
||||
if self.filters.group_by != "Invoice":
|
||||
self.grouped.setdefault(row.get(scrub(self.filters.group_by)), []).append(row)
|
||||
|
||||
self.data.append(row)
|
||||
self.grouped.setdefault(row.get(scrub(self.filters.group_by)), []).append(row)
|
||||
|
||||
if self.grouped:
|
||||
self.get_average_rate_based_on_group_by()
|
||||
else:
|
||||
self.grouped_data = []
|
||||
|
||||
def get_average_rate_based_on_group_by(self):
|
||||
# sum buying / selling totals for group
|
||||
self.grouped_data = []
|
||||
for key in self.grouped.keys():
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if i==0:
|
||||
new_row = row
|
||||
else:
|
||||
new_row.qty += row.qty
|
||||
new_row.buying_amount += row.buying_amount
|
||||
new_row.base_amount += row.base_amount
|
||||
if self.filters.get("group_by") != "Invoice":
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if i==0:
|
||||
new_row = row
|
||||
else:
|
||||
new_row.qty += row.qty
|
||||
new_row.buying_amount += row.buying_amount
|
||||
new_row.base_amount += row.base_amount
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
else:
|
||||
for i, row in enumerate(self.grouped[key]):
|
||||
if row.parent in self.returned_invoices \
|
||||
and row.item_code in self.returned_invoices[row.parent]:
|
||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||
for returned_item_row in returned_item_rows:
|
||||
row.qty += returned_item_row.qty
|
||||
row.base_amount += returned_item_row.base_amount
|
||||
row.buying_amount = row.qty * row.buying_rate
|
||||
if row.qty:
|
||||
row = self.set_average_rate(row)
|
||||
self.grouped_data.append(row)
|
||||
|
||||
new_row.gross_profit = new_row.base_amount - new_row.buying_amount
|
||||
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
|
||||
if new_row.base_amount else 0
|
||||
new_row.buying_rate = (new_row.buying_amount / new_row.qty) \
|
||||
if new_row.qty else 0
|
||||
new_row.base_rate = (new_row.base_amount / new_row.qty) \
|
||||
if new_row.qty else 0
|
||||
def set_average_rate(self, new_row):
|
||||
new_row.gross_profit = new_row.base_amount - new_row.buying_amount
|
||||
new_row.gross_profit_percent = ((new_row.gross_profit / new_row.base_amount) * 100.0) \
|
||||
if new_row.base_amount else 0
|
||||
new_row.buying_rate = (new_row.buying_amount / new_row.qty) if new_row.qty else 0
|
||||
new_row.base_rate = (new_row.base_amount / new_row.qty) if new_row.qty else 0
|
||||
return new_row
|
||||
|
||||
self.grouped_data.append(new_row)
|
||||
def get_returned_invoice_items(self):
|
||||
returned_invoices = frappe.db.sql("""
|
||||
select
|
||||
si.name, si_item.item_code, si_item.qty, si_item.base_amount, si.return_against
|
||||
from
|
||||
`tabSales Invoice` si, `tabSales Invoice Item` si_item
|
||||
where
|
||||
si.name = si_item.parent
|
||||
and si.docstatus = 1
|
||||
and si.is_return = 1
|
||||
""", as_dict=1)
|
||||
|
||||
self.returned_invoices = frappe._dict()
|
||||
for inv in returned_invoices:
|
||||
self.returned_invoices.setdefault(inv.return_against, frappe._dict())\
|
||||
.setdefault(inv.item_code, []).append(inv)
|
||||
|
||||
def skip_row(self, row, product_bundles):
|
||||
if self.filters.get("group_by") != "Invoice" and not row.get(scrub(self.filters.get("group_by"))):
|
||||
if self.filters.get("group_by") != "Invoice":
|
||||
if not row.get(scrub(self.filters.get("group_by"))):
|
||||
return True
|
||||
elif row.get("is_return") == 1:
|
||||
return True
|
||||
|
||||
def get_buying_amount_from_product_bundle(self, row, product_bundle):
|
||||
@ -268,20 +296,26 @@ class GrossProfitGenerator(object):
|
||||
sales_person_cols = ""
|
||||
sales_team_table = ""
|
||||
|
||||
self.si_list = frappe.db.sql("""select `tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time, `tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.territory,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.brand,
|
||||
`tabSales Invoice Item`.dn_detail, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, `tabSales Invoice Item`.name as "item_row"
|
||||
self.si_list = frappe.db.sql("""
|
||||
select
|
||||
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
|
||||
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
|
||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return
|
||||
{sales_person_cols}
|
||||
from
|
||||
`tabSales Invoice`
|
||||
inner join `tabSales Invoice Item` on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
||||
`tabSales Invoice` inner join `tabSales Invoice Item`
|
||||
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
||||
{sales_team_table}
|
||||
where
|
||||
`tabSales Invoice`.docstatus = 1 {conditions} {match_cond}
|
||||
`tabSales Invoice`.docstatus=1 {conditions} {match_cond}
|
||||
order by
|
||||
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc"""
|
||||
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
||||
|
@ -13,6 +13,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
'Stock Entry': 'Material to Supplier'
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
@ -20,8 +21,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
|
||||
frm.set_indicator_formatter('item_code',
|
||||
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||
@ -86,8 +86,13 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
|
||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __("Make"));
|
||||
}
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
|
||||
|
||||
if(!doc.subscription) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __("Make"))
|
||||
}
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2102,6 +2102,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"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": "Rounding Adjustment (Company Currency)",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2227,6 +2258,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rounding_adjustment",
|
||||
"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": "Rounding Adjustment",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2856,6 +2918,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2866,7 +2989,7 @@
|
||||
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
|
||||
"fieldname": "recurring_order",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
@ -3335,7 +3458,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-19 14:03:51.838328",
|
||||
"modified": "2017-09-19 11:22:30.190589",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
@ -5,7 +5,8 @@ def get_data():
|
||||
'fieldname': 'purchase_order',
|
||||
'non_standard_fieldnames': {
|
||||
'Journal Entry': 'reference_name',
|
||||
'Payment Entry': 'reference_name'
|
||||
'Payment Entry': 'reference_name',
|
||||
'Subscription': 'reference_document'
|
||||
},
|
||||
'internal_links': {
|
||||
'Material Request': ['items', 'material_request'],
|
||||
@ -23,11 +24,11 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('Reference'),
|
||||
'items': ['Material Request', 'Supplier Quotation', 'Project']
|
||||
'items': ['Material Request', 'Supplier Quotation', 'Project', 'Subscription']
|
||||
},
|
||||
{
|
||||
'label': _('Sub-contracting'),
|
||||
'items': ['Stock Entry']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
23
erpnext/buying/doctype/purchase_order/test_purchase_order.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Purchase Order", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Purchase Order', [
|
||||
// insert a new Purchase Order
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -65,7 +65,7 @@ QUnit.test("test: request_for_quotation", function(assert) {
|
||||
assert.ok(cur_frm.doc.docstatus == 1, "Quotation request submitted");
|
||||
},
|
||||
() => frappe.click_button('Send Supplier Emails'),
|
||||
() => frappe.timeout(3),
|
||||
() => frappe.timeout(4),
|
||||
() => {
|
||||
assert.ok($('div.modal.fade.in > div.modal-dialog > div > div.modal-body.ui-front > div.msgprint').text().includes("Email sent to supplier Test Supplier"), "Send emails working");
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
() => frappe.new_doc("Request for Quotation"),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value("transaction_date", "04-04-2017"),
|
||||
() => cur_frm.set_value("company", "_Test Company"),
|
||||
() => cur_frm.set_value("company", "For Testing"),
|
||||
// Add Suppliers
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||
@ -62,7 +62,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
},
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - _TC";
|
||||
cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - FT";
|
||||
},
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(1),
|
||||
@ -104,7 +104,7 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Make Supplier Quotation'),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value("company", "_Test Company"),
|
||||
() => cur_frm.set_value("company", "For Testing"),
|
||||
() => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99,
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Save'),
|
||||
|
@ -16,7 +16,7 @@ class Supplier(TransactionBase):
|
||||
|
||||
def onload(self):
|
||||
"""Load address and contacts in `__onload`"""
|
||||
load_address_and_contact(self, "supplier")
|
||||
load_address_and_contact(self)
|
||||
self.load_dashboard_info()
|
||||
|
||||
def load_dashboard_info(self):
|
||||
|
@ -13,8 +13,8 @@ QUnit.test("test: supplier", function(assert) {
|
||||
{credit_days_based_on: 'Fixed Days'},
|
||||
{accounts: [
|
||||
[
|
||||
{'company': "Test Company"},
|
||||
{'account': "Creditors - TC"}
|
||||
{'company': "For Testing"},
|
||||
{'account': "Creditors - FT"}
|
||||
]]
|
||||
}
|
||||
]);
|
||||
@ -68,7 +68,7 @@ QUnit.test("test: supplier", function(assert) {
|
||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Name correct");
|
||||
assert.ok(cur_frm.doc.supplier_type == 'Hardware', "Type correct");
|
||||
assert.ok(cur_frm.doc.default_currency == 'INR', "Currency correct");
|
||||
assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('Test Company'), " Account Head abbr correct");
|
||||
assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('For Testing'), " Account Head abbr correct");
|
||||
assert.ok($('.address-box:nth-child(3) p').text().includes('Shipping City 3'), "Address correct");
|
||||
assert.ok($('.col-sm-6+ .col-sm-6 .h6').text().includes('Contact 3'), "Contact correct");
|
||||
},
|
||||
|
@ -22,7 +22,9 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
|
||||
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
|
||||
__("Make"));
|
||||
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
|
||||
}, __("Make"))
|
||||
}
|
||||
else if (this.frm.doc.docstatus===0) {
|
||||
|
||||
|
@ -1676,6 +1676,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"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": "Rounding Adjustment (Company Currency",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1801,6 +1832,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rounding_adjustment",
|
||||
"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": "Rounding Adjustment",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2051,6 +2113,67 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription_section",
|
||||
"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": "Subscription Section",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subscription",
|
||||
"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": "Subscription",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Subscription",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -2247,7 +2370,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-07-19 13:51:18.929697",
|
||||
"modified": "2017-09-19 11:23:25.268924",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
@ -3,6 +3,9 @@ from frappe import _
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'supplier_quotation',
|
||||
'non_standard_fieldnames': {
|
||||
'Subscription': 'reference_document'
|
||||
},
|
||||
'internal_links': {
|
||||
'Material Request': ['items', 'material_request'],
|
||||
'Request for Quotation': ['items', 'request_for_quotation'],
|
||||
@ -17,6 +20,10 @@ def get_data():
|
||||
'label': _('Reference'),
|
||||
'items': ['Material Request', 'Request for Quotation', 'Project']
|
||||
},
|
||||
{
|
||||
'label': _('Subscription'),
|
||||
'items': ['Subscription']
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Supplier Quotation", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Supplier Quotation', [
|
||||
// insert a new Supplier Quotation
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -8,13 +8,13 @@ QUnit.test("test: supplier quotation with item wise discount", function(assert){
|
||||
() => {
|
||||
return frappe.tests.make('Supplier Quotation', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{company: 'Test Company'},
|
||||
{company: 'For Testing'},
|
||||
{items: [
|
||||
[
|
||||
{"item_code": 'Test Product 4'},
|
||||
{"qty": 5},
|
||||
{"uom": 'Unit'},
|
||||
{"warehouse": 'All Warehouses - TC'},
|
||||
{"warehouse": 'All Warehouses - FT'},
|
||||
{'discount_percentage': 10},
|
||||
]
|
||||
]}
|
||||
|
30
erpnext/change_log/v9/v9_0_0.md
Normal file
@ -0,0 +1,30 @@
|
||||
- POS - Online & Offline
|
||||
- Now user has an option to enable or disable Offline POS mode from POS Settings
|
||||
- Provision to select the Item's serial number from the dropdown while adding item in the cart
|
||||
- Indicator for stock availability in Online POS Mode.
|
||||
|
||||
#### Subscription
|
||||
- Setup recurring documents using **Subscription**
|
||||
- User can schedule the subscription for doctypes other than Sales Invoice, Purchase Invoice etc.
|
||||
|
||||
#### Healthcare Domain
|
||||
- Clinic / Practice Management
|
||||
- Patient
|
||||
- Physician, Physician scheduling
|
||||
- Appointment
|
||||
- Vital Signs
|
||||
- Consultation
|
||||
- Medical Code Standards
|
||||
- Patient Medical Record
|
||||
- Laboratory
|
||||
- Sample Collection
|
||||
- Lab Test
|
||||
- Patient Portal
|
||||
|
||||
#### School Fees Management
|
||||
- Fee Structure
|
||||
- Fee Schedule
|
||||
- Payment against Fees
|
||||
|
||||
#### Setup Wizard
|
||||
- Broken into 2 parts with a fresh looks
|
@ -32,6 +32,12 @@ def get_data():
|
||||
"label": _("POS"),
|
||||
"description": _("Point of Sale")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Subscription",
|
||||
"label": _("Subscription"),
|
||||
"description": _("To make recurring documents")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"name": "Accounts Receivable",
|
||||
|
@ -261,5 +261,12 @@ def get_data():
|
||||
"icon": "octicon octicon-mortar-board",
|
||||
"type": "module",
|
||||
"label": _("Schools")
|
||||
},
|
||||
{
|
||||
"module_name": "Healthcare",
|
||||
"color": "#FF888B",
|
||||
"icon": "octicon octicon-plus",
|
||||
"type": "module",
|
||||
"label": _("Healthcare")
|
||||
}
|
||||
]
|
||||
|
157
erpnext/config/healthcare.py
Normal file
@ -0,0 +1,157 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
|
||||
return [
|
||||
{
|
||||
"label": _("Consultation"),
|
||||
"icon": "icon-star",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Patient Appointment",
|
||||
"description": _("Patient Appointment"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Consultation",
|
||||
"label": _("Consultation"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Vital Signs",
|
||||
"label": _("Vital Signs"),
|
||||
"description": _("Record Patient Vitals"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "medical_record",
|
||||
"label": _("Patient Medical Record"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "appointment-analytic",
|
||||
"label": _("Appointment Analytics"),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Laboratory"),
|
||||
"icon": "icon-list",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Lab Test",
|
||||
"description": _("Results"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Sample Collection",
|
||||
"label": _("Sample Collection"),
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"name": "Lab Test Report",
|
||||
"is_query_report": True
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Masters"),
|
||||
"icon": "icon-list",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Patient",
|
||||
"label": _("Patient"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Physician",
|
||||
"label": "Physician",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Physician Schedule",
|
||||
"label": _("Physician Schedule"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Medical Code Standard",
|
||||
"label": _("Medical Code Standard"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Medical Code",
|
||||
"label": _("Medical Code"),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Setup"),
|
||||
"icon": "icon-cog",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Healthcare Settings",
|
||||
"label": _("Healthcare Settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Medical Department",
|
||||
"label": "Medical Department"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Appointment Type",
|
||||
"description": _("Appointment Type Master"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Prescription Dosage",
|
||||
"description": _("Prescription Dosage")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Prescription Duration",
|
||||
"description": _("Prescription Period")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Complaint",
|
||||
"description": _("Complaint")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Diagnosis",
|
||||
"description": _("Diagnosis")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Lab Test Sample",
|
||||
"description": _("Test Sample Master."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Lab Test UOM",
|
||||
"description": _("Lab Test UOM.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Antibiotic",
|
||||
"description": _("Antibiotic.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Sensitivity",
|
||||
"description": _("Sensitivity Naming.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Lab Test Template",
|
||||
"description": _("Lab Test Configurations.")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -154,6 +154,10 @@ def get_data():
|
||||
"type": "doctype",
|
||||
"name": "Fees"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Fee Schedule"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Fee Structure"
|
||||
|
@ -8,7 +8,6 @@ from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
@ -53,13 +52,6 @@ class AccountsController(TransactionBase):
|
||||
self.validate_party()
|
||||
self.validate_currency()
|
||||
|
||||
if self.meta.get_field("is_recurring"):
|
||||
if self.amended_from and self.recurring_id == self.amended_from:
|
||||
self.recurring_id = None
|
||||
if not self.get("__islocal"):
|
||||
validate_recurring_document(self)
|
||||
convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
|
||||
|
||||
if self.doctype == 'Purchase Invoice':
|
||||
self.validate_paid_amount()
|
||||
|
||||
@ -84,11 +76,6 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
frappe.db.set(self,'paid_amount',0)
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if self.meta.get_field("is_recurring"):
|
||||
validate_recurring_document(self)
|
||||
convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
if frappe.flags.in_test:
|
||||
for fieldname in ["posting_date","transaction_date"]:
|
||||
|
@ -1,230 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import calendar
|
||||
import frappe.utils
|
||||
import frappe.defaults
|
||||
|
||||
from frappe.utils import cint, cstr, getdate, nowdate, \
|
||||
get_first_day, get_last_day, split_emails
|
||||
|
||||
from frappe import _, msgprint, throw
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
date_field_map = {
|
||||
"Sales Order": "transaction_date",
|
||||
"Sales Invoice": "posting_date",
|
||||
"Purchase Order": "transaction_date",
|
||||
"Purchase Invoice": "posting_date"
|
||||
}
|
||||
|
||||
def create_recurring_documents():
|
||||
manage_recurring_documents("Sales Order")
|
||||
manage_recurring_documents("Sales Invoice")
|
||||
manage_recurring_documents("Purchase Order")
|
||||
manage_recurring_documents("Purchase Invoice")
|
||||
|
||||
def manage_recurring_documents(doctype, next_date=None, commit=True):
|
||||
"""
|
||||
Create recurring documents on specific date by copying the original one
|
||||
and notify the concerned people
|
||||
"""
|
||||
next_date = next_date or nowdate()
|
||||
|
||||
date_field = date_field_map[doctype]
|
||||
|
||||
condition = " and ifnull(status, '') != 'Closed'" if doctype in ("Sales Order", "Purchase Order") else ""
|
||||
|
||||
recurring_documents = frappe.db.sql("""select name, recurring_id
|
||||
from `tab{0}` where is_recurring=1
|
||||
and (docstatus=1 or docstatus=0) and next_date=%s
|
||||
and next_date <= ifnull(end_date, '2199-12-31') {1}""".format(doctype, condition), next_date)
|
||||
|
||||
exception_list = []
|
||||
for ref_document, recurring_id in recurring_documents:
|
||||
if not frappe.db.sql("""select name from `tab%s`
|
||||
where %s=%s and recurring_id=%s and (docstatus=1 or docstatus=0)"""
|
||||
% (doctype, date_field, '%s', '%s'), (next_date, recurring_id)):
|
||||
try:
|
||||
reference_doc = frappe.get_doc(doctype, ref_document)
|
||||
new_doc = make_new_document(reference_doc, date_field, next_date)
|
||||
if reference_doc.notify_by_email:
|
||||
send_notification(new_doc)
|
||||
if commit:
|
||||
frappe.db.commit()
|
||||
except:
|
||||
if commit:
|
||||
frappe.db.rollback()
|
||||
|
||||
frappe.db.begin()
|
||||
frappe.db.sql("update `tab%s` \
|
||||
set is_recurring = 0 where name = %s" % (doctype, '%s'),
|
||||
(ref_document))
|
||||
notify_errors(ref_document, doctype, reference_doc.get("customer") or reference_doc.get("supplier"),
|
||||
reference_doc.owner)
|
||||
frappe.db.commit()
|
||||
|
||||
exception_list.append(frappe.get_traceback())
|
||||
finally:
|
||||
if commit:
|
||||
frappe.db.begin()
|
||||
|
||||
if exception_list:
|
||||
exception_message = "\n\n".join([cstr(d) for d in exception_list])
|
||||
frappe.throw(exception_message)
|
||||
|
||||
def make_new_document(reference_doc, date_field, posting_date):
|
||||
new_document = frappe.copy_doc(reference_doc, ignore_no_copy=False)
|
||||
mcount = month_map[reference_doc.recurring_type]
|
||||
|
||||
from_date = get_next_date(reference_doc.from_date, mcount)
|
||||
|
||||
# get last day of the month to maintain period if the from date is first day of its own month
|
||||
# and to date is the last day of its own month
|
||||
if (cstr(get_first_day(reference_doc.from_date)) == cstr(reference_doc.from_date)) and \
|
||||
(cstr(get_last_day(reference_doc.to_date)) == cstr(reference_doc.to_date)):
|
||||
to_date = get_last_day(get_next_date(reference_doc.to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(reference_doc.to_date, mcount)
|
||||
|
||||
new_document.update({
|
||||
date_field: posting_date,
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"next_date": get_next_date(reference_doc.next_date, mcount,cint(reference_doc.repeat_on_day_of_month))
|
||||
})
|
||||
|
||||
if new_document.meta.get_field('set_posting_time'):
|
||||
new_document.set('set_posting_time', 1)
|
||||
|
||||
# copy document fields
|
||||
for fieldname in ("owner", "recurring_type", "repeat_on_day_of_month",
|
||||
"recurring_id", "notification_email_address", "is_recurring", "end_date",
|
||||
"title", "naming_series", "select_print_heading", "ignore_pricing_rule",
|
||||
"posting_time", "remarks", 'submit_on_creation'):
|
||||
if new_document.meta.get_field(fieldname):
|
||||
new_document.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
# copy item fields
|
||||
for i, item in enumerate(new_document.items):
|
||||
for fieldname in ("page_break",):
|
||||
item.set(fieldname, reference_doc.items[i].get(fieldname))
|
||||
|
||||
new_document.run_method("on_recurring", reference_doc=reference_doc)
|
||||
|
||||
if reference_doc.submit_on_creation:
|
||||
new_document.insert()
|
||||
new_document.submit()
|
||||
else:
|
||||
new_document.docstatus=0
|
||||
new_document.insert()
|
||||
|
||||
return new_document
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
|
||||
return dt
|
||||
|
||||
def send_notification(new_rv):
|
||||
"""Notify concerned persons about recurring document generation"""
|
||||
|
||||
frappe.sendmail(new_rv.notification_email_address,
|
||||
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=new_rv.recurring_print_format)])
|
||||
|
||||
def notify_errors(doc, doctype, party, owner):
|
||||
from frappe.utils.user import get_system_managers
|
||||
recipients = get_system_managers(only_name=True)
|
||||
|
||||
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
|
||||
subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc),
|
||||
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
|
||||
"type": doctype,
|
||||
"name": doc,
|
||||
"party": party
|
||||
}))
|
||||
|
||||
assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients)
|
||||
|
||||
def assign_task_to_owner(doc, doctype, msg, users):
|
||||
for d in users:
|
||||
from frappe.desk.form import assign_to
|
||||
args = {
|
||||
'assign_to' : d,
|
||||
'doctype' : doctype,
|
||||
'name' : doc,
|
||||
'description' : msg,
|
||||
'priority' : 'High'
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
def validate_recurring_document(doc):
|
||||
if doc.is_recurring:
|
||||
validate_notification_email_id(doc)
|
||||
if not doc.recurring_type:
|
||||
frappe.throw(_("Please select {0}").format(doc.meta.get_label("recurring_type")))
|
||||
|
||||
elif not (doc.from_date and doc.to_date):
|
||||
frappe.throw(_("Period From and Period To dates mandatory for recurring {0}").format(doc.doctype))
|
||||
|
||||
def validate_recurring_next_date(doc):
|
||||
posting_date = doc.get("posting_date") or doc.get("transaction_date")
|
||||
if getdate(posting_date) > getdate(doc.next_date):
|
||||
frappe.throw(_("Next Date must be greater than Posting Date"))
|
||||
|
||||
next_date = getdate(doc.next_date)
|
||||
if next_date.day != doc.repeat_on_day_of_month:
|
||||
|
||||
# if the repeat day is the last day of the month (31)
|
||||
# and the current month does not have as many days,
|
||||
# then the last day of the current month is a valid date
|
||||
lastday = calendar.monthrange(next_date.year, next_date.month)[1]
|
||||
if doc.repeat_on_day_of_month < lastday:
|
||||
|
||||
# the specified day of the month is not same as the day specified
|
||||
# or the last day of the month
|
||||
frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
|
||||
|
||||
def convert_to_recurring(doc, posting_date):
|
||||
if doc.is_recurring:
|
||||
if not doc.recurring_id:
|
||||
doc.db_set("recurring_id", doc.name)
|
||||
|
||||
set_next_date(doc, posting_date)
|
||||
|
||||
if doc.next_date:
|
||||
validate_recurring_next_date(doc)
|
||||
|
||||
elif doc.recurring_id:
|
||||
doc.db_set("recurring_id", None)
|
||||
|
||||
def validate_notification_email_id(doc):
|
||||
if doc.notify_by_email:
|
||||
if doc.notification_email_address:
|
||||
email_list = split_emails(doc.notification_email_address.replace("\n", ""))
|
||||
|
||||
from frappe.utils import validate_email_add
|
||||
for email in email_list:
|
||||
if not validate_email_add(email):
|
||||
throw(_("{0} is an invalid email address in 'Notification \
|
||||
Email Address'").format(email))
|
||||
|
||||
else:
|
||||
frappe.throw(_("'Notification Email Addresses' not specified for recurring %s") \
|
||||
% doc.doctype)
|
||||
|
||||
def set_next_date(doc, posting_date):
|
||||
""" Set next date on which recurring document will be created"""
|
||||
if not doc.repeat_on_day_of_month:
|
||||
msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1)
|
||||
|
||||
next_date = get_next_date(posting_date, month_map[doc.recurring_type],
|
||||
cint(doc.repeat_on_day_of_month))
|
||||
|
||||
doc.db_set('next_date', next_date)
|
||||
|
||||
msgprint(_("Next Recurring {0} will be created on {1}").format(doc.doctype, next_date))
|
@ -121,9 +121,10 @@ class calculate_taxes_and_totals(object):
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
|
||||
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty:
|
||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction), item.precision("net_amount"))
|
||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction))
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
|
||||
item.discount_percentage = flt(item.discount_percentage,
|
||||
item.precision("discount_percentage"))
|
||||
|
||||
self._set_in_company_currency(item, ["net_rate", "net_amount"])
|
||||
|
||||
@ -173,6 +174,7 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"])
|
||||
|
||||
def calculate_taxes(self):
|
||||
self.doc.rounding_adjustment = 0
|
||||
# maintain actual tax rate based on idx
|
||||
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
|
||||
@ -222,7 +224,9 @@ class calculate_taxes_and_totals(object):
|
||||
# adjust Discount Amount loss in last tax iteration
|
||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||
and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
|
||||
self.adjust_discount_amount_loss(tax)
|
||||
self.doc.rounding_adjustment = flt(self.doc.grand_total
|
||||
- flt(self.doc.discount_amount) - tax.total,
|
||||
self.doc.precision("rounding_adjustment"))
|
||||
|
||||
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
||||
# if just for valuation, do not add the tax amount in total
|
||||
@ -277,36 +281,26 @@ class calculate_taxes_and_totals(object):
|
||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
||||
tax.precision("tax_amount"))
|
||||
|
||||
def adjust_discount_amount_loss(self, tax):
|
||||
discount_amount_loss = self.doc.grand_total - flt(self.doc.discount_amount) - tax.total
|
||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount +
|
||||
discount_amount_loss, tax.precision("tax_amount"))
|
||||
tax.total = flt(tax.total + discount_amount_loss, tax.precision("total"))
|
||||
|
||||
self._set_in_company_currency(tax, ["total", "tax_amount_after_discount_amount"])
|
||||
|
||||
def manipulate_grand_total_for_inclusive_tax(self):
|
||||
# if fully inclusive taxes and diff
|
||||
if self.doc.get("taxes") and all(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
||||
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
||||
last_tax = self.doc.get("taxes")[-1]
|
||||
diff = self.doc.total - flt(last_tax.total, self.doc.precision("grand_total"))
|
||||
|
||||
if diff and abs(diff) <= (2.0 / 10**last_tax.precision("tax_amount")):
|
||||
last_tax.tax_amount += diff
|
||||
last_tax.tax_amount_after_discount_amount += diff
|
||||
last_tax.total += diff
|
||||
|
||||
self._set_in_company_currency(last_tax,
|
||||
["total", "tax_amount", "tax_amount_after_discount_amount"])
|
||||
non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
|
||||
for d in self.doc.get("taxes") if not d.included_in_print_rate])
|
||||
diff = self.doc.total + non_inclusive_tax_amount \
|
||||
- flt(last_tax.total, last_tax.precision("total"))
|
||||
if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
|
||||
self.doc.rounding_adjustment = flt(flt(self.doc.rounding_adjustment) +
|
||||
flt(diff), self.doc.precision("rounding_adjustment"))
|
||||
|
||||
def calculate_totals(self):
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total
|
||||
if self.doc.get("taxes") else self.doc.net_total)
|
||||
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \
|
||||
if self.doc.get("taxes") else flt(self.doc.net_total)
|
||||
|
||||
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total,
|
||||
self.doc.precision("total_taxes_and_charges"))
|
||||
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
|
||||
- flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
|
||||
|
||||
self._set_in_company_currency(self.doc, ["total_taxes_and_charges"])
|
||||
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
|
||||
|
||||
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
|
||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
|
||||
@ -326,13 +320,22 @@ class calculate_taxes_and_totals(object):
|
||||
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
|
||||
else self.doc.base_net_total
|
||||
|
||||
self._set_in_company_currency(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
self._set_in_company_currency(self.doc,
|
||||
["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
|
||||
|
||||
self.set_rounded_total()
|
||||
|
||||
def set_rounded_total(self):
|
||||
if frappe.db.get_single_value("Global Defaults", "disable_rounded_total"):
|
||||
self.doc.rounded_total = self.doc.base_rounded_total = 0
|
||||
return
|
||||
|
||||
if self.doc.meta.get_field("rounded_total"):
|
||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
|
||||
self.doc.currency, self.doc.precision("rounded_total"))
|
||||
|
||||
if self.doc.meta.get_field("base_rounded_total"):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
|
||||
@ -525,7 +528,7 @@ def get_itemised_tax_breakup_html(doc):
|
||||
for tax in doc.taxes:
|
||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||
continue
|
||||
if tax.description not in tax_accounts:
|
||||
if tax.description not in tax_accounts and tax.tax_amount_after_discount_amount:
|
||||
tax_accounts.append(tax.description)
|
||||
|
||||
headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
|
||||
@ -565,26 +568,21 @@ def get_itemised_tax(taxes):
|
||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||
continue
|
||||
|
||||
tax_amount_precision = tax.precision("tax_amount")
|
||||
tax_rate_precision = tax.precision("rate")
|
||||
|
||||
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
|
||||
|
||||
for item_code, tax_data in item_tax_map.items():
|
||||
itemised_tax.setdefault(item_code, frappe._dict())
|
||||
if item_tax_map:
|
||||
for item_code, tax_data in item_tax_map.items():
|
||||
itemised_tax.setdefault(item_code, frappe._dict())
|
||||
|
||||
if isinstance(tax_data, list):
|
||||
precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision
|
||||
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data[0]),
|
||||
tax_amount=flt(tax_data[1])
|
||||
))
|
||||
else:
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data),
|
||||
tax_amount=0.0
|
||||
))
|
||||
if isinstance(tax_data, list):
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data[0]),
|
||||
tax_amount=flt(tax_data[1])
|
||||
))
|
||||
else:
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data),
|
||||
tax_amount=0.0
|
||||
))
|
||||
|
||||
return itemised_tax
|
||||
|
||||
|
@ -1,149 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import frappe.permissions
|
||||
from erpnext.controllers.recurring_document import date_field_map
|
||||
from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate, add_days
|
||||
|
||||
def test_recurring_document(obj, test_records):
|
||||
frappe.db.set_value("Print Settings", "Print Settings", "send_print_as_pdf", 1)
|
||||
today = nowdate()
|
||||
base_doc = frappe.copy_doc(test_records[0])
|
||||
|
||||
base_doc.update({
|
||||
"is_recurring": 1,
|
||||
"submit_on_create": 1,
|
||||
"recurring_type": "Monthly",
|
||||
"notification_email_address": "test@example.com, test1@example.com, test2@example.com",
|
||||
"repeat_on_day_of_month": getdate(today).day,
|
||||
"due_date": None,
|
||||
"from_date": get_first_day(today),
|
||||
"to_date": get_last_day(today)
|
||||
})
|
||||
|
||||
date_field = date_field_map[base_doc.doctype]
|
||||
base_doc.set(date_field, today)
|
||||
|
||||
if base_doc.doctype == "Sales Order":
|
||||
base_doc.set("delivery_date", add_days(today, 15))
|
||||
|
||||
# monthly
|
||||
doc1 = frappe.copy_doc(base_doc)
|
||||
doc1.insert()
|
||||
doc1.submit()
|
||||
_test_recurring_document(obj, doc1, date_field, True)
|
||||
|
||||
# monthly without a first and last day period
|
||||
if getdate(today).day != 1:
|
||||
doc2 = frappe.copy_doc(base_doc)
|
||||
doc2.update({
|
||||
"from_date": today,
|
||||
"to_date": add_to_date(today, days=30)
|
||||
})
|
||||
doc2.insert()
|
||||
doc2.submit()
|
||||
_test_recurring_document(obj, doc2, date_field, False)
|
||||
|
||||
# quarterly
|
||||
doc3 = frappe.copy_doc(base_doc)
|
||||
doc3.update({
|
||||
"recurring_type": "Quarterly",
|
||||
"from_date": get_first_day(today),
|
||||
"to_date": get_last_day(add_to_date(today, months=3))
|
||||
})
|
||||
doc3.insert()
|
||||
doc3.submit()
|
||||
_test_recurring_document(obj, doc3, date_field, True)
|
||||
|
||||
# quarterly without a first and last day period
|
||||
doc4 = frappe.copy_doc(base_doc)
|
||||
doc4.update({
|
||||
"recurring_type": "Quarterly",
|
||||
"from_date": today,
|
||||
"to_date": add_to_date(today, months=3)
|
||||
})
|
||||
doc4.insert()
|
||||
doc4.submit()
|
||||
_test_recurring_document(obj, doc4, date_field, False)
|
||||
|
||||
# yearly
|
||||
doc5 = frappe.copy_doc(base_doc)
|
||||
doc5.update({
|
||||
"recurring_type": "Yearly",
|
||||
"from_date": get_first_day(today),
|
||||
"to_date": get_last_day(add_to_date(today, years=1))
|
||||
})
|
||||
doc5.insert()
|
||||
doc5.submit()
|
||||
_test_recurring_document(obj, doc5, date_field, True)
|
||||
|
||||
# yearly without a first and last day period
|
||||
doc6 = frappe.copy_doc(base_doc)
|
||||
doc6.update({
|
||||
"recurring_type": "Yearly",
|
||||
"from_date": today,
|
||||
"to_date": add_to_date(today, years=1)
|
||||
})
|
||||
doc6.insert()
|
||||
doc6.submit()
|
||||
_test_recurring_document(obj, doc6, date_field, False)
|
||||
|
||||
# change date field but keep recurring day to be today
|
||||
doc7 = frappe.copy_doc(base_doc)
|
||||
doc7.update({
|
||||
date_field: today,
|
||||
})
|
||||
doc7.insert()
|
||||
doc7.submit()
|
||||
|
||||
# setting so that _test function works
|
||||
# doc7.set(date_field, today)
|
||||
_test_recurring_document(obj, doc7, date_field, True)
|
||||
|
||||
def _test_recurring_document(obj, base_doc, date_field, first_and_last_day):
|
||||
from frappe.utils import add_months, get_last_day
|
||||
from erpnext.controllers.recurring_document import manage_recurring_documents, \
|
||||
get_next_date
|
||||
|
||||
no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_doc.recurring_type]
|
||||
|
||||
def _test(i):
|
||||
obj.assertEquals(i+1, frappe.db.sql("""select count(*) from `tab%s`
|
||||
where recurring_id=%s and (docstatus=1 or docstatus=0)""" % (base_doc.doctype, '%s'),
|
||||
(base_doc.recurring_id))[0][0])
|
||||
|
||||
next_date = get_next_date(base_doc.get(date_field), no_of_months,
|
||||
base_doc.repeat_on_day_of_month)
|
||||
|
||||
manage_recurring_documents(base_doc.doctype, next_date=next_date, commit=False)
|
||||
|
||||
recurred_documents = frappe.db.sql("""select name from `tab%s`
|
||||
where recurring_id=%s and (docstatus=1 or docstatus=0) order by name desc"""
|
||||
% (base_doc.doctype, '%s'), (base_doc.recurring_id))
|
||||
|
||||
obj.assertEquals(i+2, len(recurred_documents))
|
||||
|
||||
new_doc = frappe.get_doc(base_doc.doctype, recurred_documents[0][0])
|
||||
|
||||
for fieldname in ["is_recurring", "recurring_type",
|
||||
"repeat_on_day_of_month", "notification_email_address"]:
|
||||
obj.assertEquals(base_doc.get(fieldname),
|
||||
new_doc.get(fieldname))
|
||||
|
||||
obj.assertEquals(new_doc.get(date_field), getdate(next_date))
|
||||
|
||||
obj.assertEquals(new_doc.from_date, getdate(add_months(base_doc.from_date, no_of_months)))
|
||||
|
||||
if first_and_last_day:
|
||||
obj.assertEquals(new_doc.to_date, getdate(get_last_day(add_months(base_doc.to_date, no_of_months))))
|
||||
else:
|
||||
obj.assertEquals(new_doc.to_date, getdate(add_months(base_doc.to_date, no_of_months)))
|
||||
|
||||
return new_doc
|
||||
|
||||
# if yearly, test 1 repetition, else test 5 repetitions
|
||||
count = 1 if (no_of_months == 12) else 5
|
||||
for i in xrange(count):
|
||||
base_doc = _test(i)
|
@ -19,7 +19,7 @@ QUnit.test("test: item", function (assert) {
|
||||
{is_stock_item: is_stock_item},
|
||||
{standard_rate: keyboard_cost},
|
||||
{opening_stock: no_of_items_to_stock},
|
||||
{default_warehouse: "Stores - RB"}
|
||||
{default_warehouse: "Stores - FT"}
|
||||
]
|
||||
),
|
||||
() => {
|
||||
@ -45,7 +45,7 @@ QUnit.test("test: item", function (assert) {
|
||||
{is_stock_item: is_stock_item},
|
||||
{standard_rate: screen_cost},
|
||||
{opening_stock: no_of_items_to_stock},
|
||||
{default_warehouse: "Stores - RB"}
|
||||
{default_warehouse: "Stores - FT"}
|
||||
]
|
||||
),
|
||||
|
||||
@ -57,7 +57,7 @@ QUnit.test("test: item", function (assert) {
|
||||
{is_stock_item: is_stock_item},
|
||||
{standard_rate: CPU_cost},
|
||||
{opening_stock: no_of_items_to_stock},
|
||||
{default_warehouse: "Stores - RB"}
|
||||
{default_warehouse: "Stores - FT"}
|
||||
]
|
||||
),
|
||||
|
||||
@ -66,7 +66,7 @@ QUnit.test("test: item", function (assert) {
|
||||
"Item", [
|
||||
{item_code: "Laptop"},
|
||||
{item_group: "Products"},
|
||||
{default_warehouse: "Stores - RB"}
|
||||
{default_warehouse: "Stores - FT"}
|
||||
]
|
||||
),
|
||||
() => frappe.tests.make(
|
||||
@ -85,7 +85,7 @@ QUnit.test("test: item", function (assert) {
|
||||
{is_stock_item: is_stock_item},
|
||||
{standard_rate: scrap_cost},
|
||||
{opening_stock: no_of_items_to_stock},
|
||||
{default_warehouse: "Stores - RB"}
|
||||
{default_warehouse: "Stores - FT"}
|
||||
]
|
||||
),
|
||||
() => frappe.tests.make(
|
||||
|
@ -20,7 +20,7 @@ class Lead(SellingController):
|
||||
def onload(self):
|
||||
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
|
||||
self.get("__onload").is_customer = customer
|
||||
load_address_and_contact(self, "lead")
|
||||
load_address_and_contact(self)
|
||||
|
||||
def validate(self):
|
||||
self._prev = frappe._dict({
|
||||
|
5420
erpnext/demo/data/drug_list.json
Normal file
@ -167,6 +167,7 @@
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-S",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes":[
|
||||
{
|
||||
"attribute": "Size",
|
||||
@ -183,6 +184,7 @@
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-M",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes":[
|
||||
{
|
||||
"attribute": "Size",
|
||||
@ -199,6 +201,7 @@
|
||||
"item_group": "Products",
|
||||
"item_name": "Wind Turbine-L",
|
||||
"variant_of": "Wind Turbine",
|
||||
"valuation_rate": 300,
|
||||
"attributes":[
|
||||
{
|
||||
"attribute": "Size",
|
||||
|
27
erpnext/demo/data/patient.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"patient_name": "lila",
|
||||
"gender": "Female"
|
||||
},
|
||||
{
|
||||
"patient_name": "charline",
|
||||
"gender": "Female"
|
||||
},
|
||||
{
|
||||
"patient_name": "soren",
|
||||
"last_name": "le gall",
|
||||
"gender": "Male"
|
||||
},
|
||||
{
|
||||
"patient_name": "fanny",
|
||||
"gender": "Female"
|
||||
},
|
||||
{
|
||||
"patient_name": "julie",
|
||||
"gender": "Female"
|
||||
},
|
||||
{
|
||||
"patient_name": "louka",
|
||||
"gender": "Male"
|
||||
}
|
||||
]
|
17
erpnext/demo/data/physician.json
Normal file
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Physician",
|
||||
"first_name": "Eddie Jessup",
|
||||
"department": "Pathology"
|
||||
},
|
||||
{
|
||||
"doctype": "Physician",
|
||||
"first_name": "Deepshi Garg",
|
||||
"department": "ENT"
|
||||
},
|
||||
{
|
||||
"doctype": "Physician",
|
||||
"first_name": "Amit Jain",
|
||||
"department": "Microbiology"
|
||||
}
|
||||
]
|
@ -4,7 +4,7 @@ import frappe, sys
|
||||
import erpnext
|
||||
import frappe.utils
|
||||
from erpnext.demo.user import hr, sales, purchase, manufacturing, stock, accounts, projects, fixed_asset, schools
|
||||
from erpnext.demo.setup import education, manufacture, setup_data
|
||||
from erpnext.demo.setup import education, manufacture, setup_data, healthcare
|
||||
"""
|
||||
Make a demo
|
||||
|
||||
@ -30,6 +30,8 @@ def make(domain='Manufacturing', days=100):
|
||||
manufacture.setup_data()
|
||||
elif domain== 'Education':
|
||||
education.setup_data()
|
||||
elif domain== 'Healthcare':
|
||||
healthcare.setup_data()
|
||||
|
||||
site = frappe.local.site
|
||||
frappe.destroy()
|
||||
|
@ -15,5 +15,8 @@ data = {
|
||||
},
|
||||
'Education': {
|
||||
'company_name': 'Whitmore College'
|
||||
},
|
||||
'Healthcare': {
|
||||
'company_name': 'ABC Hospital Ltd.'
|
||||
}
|
||||
}
|
166
erpnext/demo/setup/healthcare.py
Normal file
@ -0,0 +1,166 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe, json
|
||||
from frappe.utils.make_random import get_random
|
||||
import datetime
|
||||
from erpnext.demo.setup.setup_data import import_json
|
||||
from frappe.utils import getdate
|
||||
from erpnext.healthcare.doctype.lab_test.lab_test import create_test_from_template
|
||||
|
||||
def setup_data():
|
||||
frappe.flags.mute_emails = True
|
||||
make_masters()
|
||||
make_patient()
|
||||
make_lab_test()
|
||||
make_consulation()
|
||||
make_appointment()
|
||||
consulation_on_appointment()
|
||||
lab_test_on_consultation()
|
||||
frappe.db.commit()
|
||||
frappe.clear_cache()
|
||||
|
||||
def make_masters():
|
||||
import_json("Physician")
|
||||
import_drug()
|
||||
frappe.db.commit()
|
||||
|
||||
def make_patient():
|
||||
file_path = get_json_path("Patient")
|
||||
with open(file_path, "r") as open_file:
|
||||
patient_data = json.loads(open_file.read())
|
||||
count = 1
|
||||
|
||||
for d in enumerate(patient_data):
|
||||
patient = frappe.new_doc("Patient")
|
||||
patient.patient_name = d[1]['patient_name'].title()
|
||||
patient.sex = d[1]['gender']
|
||||
patient.blood_group = "A Positive"
|
||||
patient.date_of_birth = datetime.datetime(1990, 3, 25)
|
||||
patient.email_id = d[1]['patient_name'] + "_" + patient.date_of_birth.strftime('%m/%d/%Y') + "@example.com"
|
||||
if count <5:
|
||||
patient.insert()
|
||||
frappe.db.commit()
|
||||
count+=1
|
||||
|
||||
def make_appointment():
|
||||
i = 1
|
||||
while i <= 4:
|
||||
physician = get_random("Physician")
|
||||
department = frappe.get_value("Physician", physician, "department")
|
||||
patient = get_random("Patient")
|
||||
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||
appointment = frappe.new_doc("Patient Appointment")
|
||||
startDate = datetime.datetime.now()
|
||||
for x in random_date(startDate,0):
|
||||
appointment_datetime = x
|
||||
appointment.appointment_datetime = appointment_datetime
|
||||
appointment.appointment_time = appointment_datetime
|
||||
appointment.appointment_date = appointment_datetime
|
||||
appointment.patient = patient
|
||||
appointment.patient_sex = patient_sex
|
||||
appointment.physician = physician
|
||||
appointment.department = department
|
||||
appointment.save(ignore_permissions = True)
|
||||
i += 1
|
||||
|
||||
def make_consulation():
|
||||
for i in xrange(3):
|
||||
physician = get_random("Physician")
|
||||
department = frappe.get_value("Physician", physician, "department")
|
||||
patient = get_random("Patient")
|
||||
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||
consultation = set_consultation(patient, patient_sex, physician, department, getdate(), i)
|
||||
consultation.save(ignore_permissions=True)
|
||||
|
||||
def consulation_on_appointment():
|
||||
for i in xrange(3):
|
||||
appointment = get_random("Patient Appointment")
|
||||
appointment = frappe.get_doc("Patient Appointment",appointment)
|
||||
consultation = set_consultation(appointment.patient, appointment.patient_sex, appointment.physician, appointment.department, appointment.appointment_date, i)
|
||||
consultation.appointment = appointment.name
|
||||
consultation.save(ignore_permissions=True)
|
||||
|
||||
def set_consultation(patient, patient_sex, physician, department, consultation_date, i):
|
||||
consultation = frappe.new_doc("Consultation")
|
||||
consultation.patient = patient
|
||||
consultation.patient_sex = patient_sex
|
||||
consultation.physician = physician
|
||||
consultation.visit_department = department
|
||||
consultation.consultation_date = consultation_date
|
||||
if i > 2 and patient_sex=='Female':
|
||||
consultation.symptoms = "Having chest pains for the last week."
|
||||
consultation.diagnosis = """This patient's description of dull, aching,
|
||||
exertion related substernal chest pain is suggestive of ischemic
|
||||
cardiac origin. Her findings of a FH of early ASCVD, hypertension,
|
||||
and early surgical menopause are pertinent risk factors for development
|
||||
of coronary artery disease. """
|
||||
else:
|
||||
consultation = append_drug_rx(consultation)
|
||||
consultation = append_test_rx(consultation)
|
||||
return consultation
|
||||
|
||||
def make_lab_test():
|
||||
physician = get_random("Physician")
|
||||
patient = get_random("Patient")
|
||||
patient_sex = frappe.get_value("Patient", patient, "sex")
|
||||
template = get_random("Lab Test Template")
|
||||
set_lab_test(patient, patient_sex, physician, template)
|
||||
|
||||
def lab_test_on_consultation():
|
||||
i = 1
|
||||
while i <= 2:
|
||||
test_rx = get_random("Lab Prescription", filters={'test_created': 0})
|
||||
test_rx = frappe.get_doc("Lab Prescription", test_rx)
|
||||
consultation = frappe.get_doc("Consultation", test_rx.parent)
|
||||
set_lab_test(consultation.patient, consultation.patient_sex, consultation.physician, test_rx.test_code, test_rx.name)
|
||||
i += 1
|
||||
|
||||
def set_lab_test(patient, patient_sex, physician, template, rx=None):
|
||||
lab_test = frappe.new_doc("Lab Test")
|
||||
lab_test.physician = physician
|
||||
lab_test.patient = patient
|
||||
lab_test.patient_sex = patient_sex
|
||||
lab_test.template = template
|
||||
lab_test.prescription = rx
|
||||
create_test_from_template(lab_test)
|
||||
|
||||
def append_test_rx(consultation):
|
||||
i = 1
|
||||
while i <= 2:
|
||||
test_rx = consultation.append("test_prescription")
|
||||
test_rx.test_code = get_random("Lab Test Template")
|
||||
i += 1
|
||||
return consultation
|
||||
|
||||
def append_drug_rx(consultation):
|
||||
i = 1
|
||||
while i <= 3:
|
||||
drug = get_random("Item", filters={"item_group":"Drug"})
|
||||
drug = frappe.get_doc("Item", drug)
|
||||
drug_rx = consultation.append("drug_prescription")
|
||||
drug_rx.drug_code = drug.item_code
|
||||
drug_rx.drug_name = drug.item_name
|
||||
drug_rx.dosage = get_random("Prescription Dosage")
|
||||
drug_rx.period = get_random("Prescription Duration")
|
||||
i += 1
|
||||
return consultation
|
||||
|
||||
def random_date(start,l):
|
||||
current = start
|
||||
while l >= 0:
|
||||
curr = current + datetime.timedelta(minutes=60)
|
||||
yield curr
|
||||
l-=1
|
||||
|
||||
def import_drug():
|
||||
frappe.flags.in_import = True
|
||||
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'drug_list.json')).read())
|
||||
for d in data:
|
||||
doc = frappe.new_doc("Item")
|
||||
doc.update(d)
|
||||
doc.insert()
|
||||
frappe.flags.in_import = False
|
||||
|
||||
def get_json_path(doctype):
|
||||
return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json')
|
@ -184,7 +184,8 @@ def setup_user_roles():
|
||||
user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager',
|
||||
'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User',
|
||||
'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager',
|
||||
'Support Team', 'Academics User')
|
||||
'Support Team', 'Academics User', 'Physician', 'Healthcare Administrator', 'Laboratory User',
|
||||
'Nursing User', 'Patient')
|
||||
|
||||
if not frappe.db.get_global('demo_hr_user'):
|
||||
user = frappe.get_doc('User', 'CharmaineGaudreau@example.com')
|
||||
@ -387,5 +388,3 @@ def import_json(doctype, submit=False, values=None):
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.flags.in_import = False
|
||||
|
||||
|
||||
|
BIN
erpnext/docs/assets/img/accounts/subscription.gif
Normal file
After Width: | Height: | Size: 818 KiB |
BIN
erpnext/docs/assets/img/accounts/subscription.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
erpnext/docs/assets/img/articles/brand-logo.gif
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
erpnext/docs/assets/img/articles/download-backup-1.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
erpnext/docs/assets/img/articles/download-backup-2.gif
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
erpnext/docs/assets/img/articles/email-error.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
erpnext/docs/assets/img/articles/fetching-1.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
erpnext/docs/assets/img/articles/fetching-2.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
erpnext/docs/assets/img/articles/fetching-3.gif
Normal file
After Width: | Height: | Size: 363 KiB |
BIN
erpnext/docs/assets/img/articles/item-valuation-1.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
erpnext/docs/assets/img/articles/item-valuation-2.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
erpnext/docs/assets/img/articles/purchase-return.gif
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
erpnext/docs/assets/img/articles/report-header-1.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
erpnext/docs/assets/img/articles/report-header-2.png
Normal file
After Width: | Height: | Size: 84 KiB |