Merge branch 'develop' into staging

This commit is contained in:
mbauskar 2017-09-07 21:08:26 +05:30
commit 9bc2b66e14
340 changed files with 51782 additions and 38805 deletions

View File

@ -56,6 +56,6 @@ script:
- bench run-tests - bench run-tests
- sleep 5 - sleep 5
- bench reinstall --yes - bench reinstall --yes
- bench execute erpnext.setup.setup_wizard.utils.complete - bench --verbose run-setup-wizard-ui-test
- bench execute erpnext.setup.utils.enable_all_roles_and_domains - bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext - bench run-ui-tests --app erpnext

View File

@ -5,7 +5,9 @@ QUnit.test("test account", function(assert) {
let done = assert.async(); let done = assert.async();
frappe.run_serially([ frappe.run_serially([
() => frappe.set_route('Tree', 'Account'), () => frappe.set_route('Tree', 'Account'),
() => frappe.timeout(3),
() => frappe.click_button('Expand All'), () => frappe.click_button('Expand All'),
() => frappe.timeout(1),
() => frappe.click_link('Debtors'), () => frappe.click_link('Debtors'),
() => frappe.click_button('Edit'), () => frappe.click_button('Edit'),
() => frappe.timeout(1), () => frappe.timeout(1),

View File

@ -1337,6 +1337,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1382,7 +1443,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-06-13 14:29:09.794076", "modified": "2017-08-31 11:21:09.442695",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry", "name": "Journal Entry",

View File

@ -644,16 +644,9 @@ frappe.ui.form.on('Payment Entry', {
if(frm.doc.party) { if(frm.doc.party) {
var party_amount = frm.doc.payment_type=="Receive" ? var party_amount = frm.doc.payment_type=="Receive" ?
frm.doc.paid_amount : frm.doc.received_amount; 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.total_allocated_amount < party_amount) {
if(frm.doc.payment_type == "Receive") { unallocated_amount = party_amount - frm.doc.total_allocated_amount;
unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
} else {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions);
}
} }
} }
frm.set_value("unallocated_amount", unallocated_amount); frm.set_value("unallocated_amount", unallocated_amount);
@ -672,11 +665,10 @@ frappe.ui.form.on('Payment Entry', {
difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount); difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount);
} }
$.each(frm.doc.deductions || [], function(i, d) { var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
if(d.amount) difference_amount -= flt(d.amount); 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); frm.events.hide_unhide_fields(frm);
}, },

View File

@ -1659,6 +1659,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1730,7 +1791,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-13 14:29:04.244537", "modified": "2017-08-31 11:20:37.578469",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@ -281,13 +281,8 @@ class PaymentEntry(AccountsController):
if self.party: if self.party:
party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount 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.total_allocated_amount < party_amount:
if self.payment_type == "Receive": self.unallocated_amount = party_amount - self.total_allocated_amount
self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions)
else:
self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions)
def set_difference_amount(self): def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@ -302,11 +297,10 @@ class PaymentEntry(AccountsController):
else: else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
for d in self.get("deductions"): total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
if d.amount:
self.difference_amount -= flt(d.amount)
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): def clear_unallocated_reference_document_rows(self):
self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]})) self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]}))

View File

@ -267,3 +267,65 @@ class TestPaymentEntry(unittest.TestCase):
return frappe.db.sql("""select account, debit, credit, against_voucher return frappe.db.sql("""select account, debit, credit, against_voucher
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", voucher_no, as_dict=1) 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)

View File

@ -0,0 +1,51 @@
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: [
[
{'qty': 1},
{'rate': 101},
{'item_code': 'Test Product 1'},
]
]}
]);
},
() => cur_frm.save(),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(0.5),
() => frappe.tests.click_button('Close'),
() => frappe.timeout(0.5),
() => frappe.click_button('Make'),
() => frappe.click_link('Payment', 1),
() => 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');
},
() => cur_frm.set_value('paid_amount', 100),
() => {
cur_frm.doc.references[0].allocated_amount = 101;
},
() => frappe.click_button('Write Off Difference Amount'),
() => {
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()
]);
});

View File

@ -25,5 +25,4 @@ QUnit.test("test payment entry", function(assert) {
() => frappe.timeout(0.3), () => frappe.timeout(0.3),
() => done() () => done()
]); ]);
}); });

View File

@ -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: '_Test Company'},
{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 - _TC"),
() => 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 - _TC', '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", "_Test Company", "write_off_account", "_Test Write Off - _TC");
frappe.timeout(1);
frappe.db.set_value("Company", "_Test Company",
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC");
},
() => 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()
]);
});

View File

@ -8,10 +8,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
return { filters: { selling: 1 } }; 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() { erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc); 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', { 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) { refresh: function(frm) {
if(frm.doc.company) { if(frm.doc.company) {
frm.trigger("toggle_display_account_head"); frm.trigger("toggle_display_account_head");

View File

@ -631,8 +631,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Point of Sale", "fieldname": "print_format_for_online",
"fieldname": "print_format",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -641,7 +640,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Print Format", "label": "Print Format for Online",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Print Format", "options": "Print Format",
@ -822,7 +821,7 @@
"columns": 0, "columns": 0,
"fieldname": "apply_discount", "fieldname": "apply_discount",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -836,7 +835,7 @@
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 1,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
@ -851,7 +850,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Grand Total", "default": "Grand Total",
"depends_on": "apply_discount", "depends_on": "",
"fieldname": "apply_discount_on", "fieldname": "apply_discount_on",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -883,7 +882,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "customer_details", "fieldname": "offline_pos_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -892,7 +891,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "New Customer Details", "label": "Offline POS Section",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -969,6 +968,38 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1291,7 +1322,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-28 03:40:03.253088", "modified": "2017-09-01 15:55:14.890452",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Profile", "name": "POS Profile",

View 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() {
}
});

View File

@ -0,0 +1,94 @@
{
"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": "1",
"fieldname": "is_online",
"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": "Online",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"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-08-30 18:34:58.960276",
"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
}
],
"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
}

View 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

View 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()
]);
});

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:title", "autoname": "field:title",
@ -12,6 +13,7 @@
"editable_grid": 0, "editable_grid": 0,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -40,6 +42,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -69,6 +72,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -99,6 +103,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -129,6 +134,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -159,6 +165,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -189,6 +196,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -217,6 +225,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -247,6 +256,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -275,6 +285,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -303,6 +314,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -331,6 +343,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -359,6 +372,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -387,6 +401,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -417,6 +432,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -447,6 +463,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -477,6 +494,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -507,6 +525,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -537,6 +556,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -567,6 +587,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -597,6 +618,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -627,6 +649,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -655,6 +678,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -683,6 +707,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -711,6 +736,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -739,6 +765,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -767,6 +794,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -796,6 +824,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -824,6 +853,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -851,6 +881,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -880,6 +911,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -910,6 +942,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -941,6 +974,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -969,6 +1003,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1000,6 +1035,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1028,6 +1064,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1058,6 +1095,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1085,13 +1123,14 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.price_or_discount==\"Price\"", "depends_on": "eval:doc.price_or_discount==\"Price\"",
"fieldname": "price", "fieldname": "price",
"fieldtype": "Float", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -1114,6 +1153,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1143,6 +1183,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1173,6 +1214,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1202,6 +1244,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -1230,18 +1273,18 @@
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-02-17 16:21:28.446208", "modified": "2017-08-31 16:34:41.614743",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@ -46,6 +46,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
cur_frm.add_custom_button(__('Return / Debit Note'), cur_frm.add_custom_button(__('Return / Debit Note'),
this.make_debit_note, __("Make")); 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) { if(doc.docstatus===0) {
@ -343,6 +349,7 @@ frappe.ui.form.on("Purchase Invoice", {
'Payment Entry': 'Payment' 'Payment Entry': 'Payment'
} }
}, },
onload: function(frm) { onload: function(frm) {
$.each(["warehouse", "rejected_warehouse"], function(i, field) { $.each(["warehouse", "rejected_warehouse"], function(i, field) {
frm.set_query(field, "items", function() { frm.set_query(field, "items", function() {
@ -370,5 +377,5 @@ frappe.ui.form.on("Purchase Invoice", {
erpnext.buying.get_default_bom(frm); erpnext.buying.get_default_bom(frm);
} }
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
} },
}) })

View File

@ -3348,6 +3348,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -3358,7 +3419,7 @@
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal", "depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice", "fieldname": "recurring_invoice",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -3797,7 +3858,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-07-19 13:53:48.673757", "modified": "2017-08-31 11:22:47.074420",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -667,7 +667,7 @@ class PurchaseInvoice(BuyingController):
if account_type != 'Fixed Asset': if account_type != 'Fixed Asset':
frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx)) 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 self.due_date = None
@frappe.whitelist() @frappe.whitelist()

View File

@ -8,7 +8,8 @@ def get_data():
'Payment Entry': 'reference_name', 'Payment Entry': 'reference_name',
'Payment Request': 'reference_name', 'Payment Request': 'reference_name',
'Landed Cost Voucher': 'receipt_document', 'Landed Cost Voucher': 'receipt_document',
'Purchase Invoice': 'return_against' 'Purchase Invoice': 'return_against',
'Subscription': 'reference_document'
}, },
'internal_links': { 'internal_links': {
'Purchase Order': ['items', 'purchase_order'], 'Purchase Order': ['items', 'purchase_order'],
@ -27,5 +28,9 @@ def get_data():
'label': _('Returns'), 'label': _('Returns'),
'items': ['Purchase Invoice'] 'items': ['Purchase Invoice']
}, },
{
'label': _('Subscription'),
'items': ['Subscription']
},
] ]
} }

View File

@ -256,10 +256,6 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account` self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_type='Purchase Invoice' and reference_name=%s""", pi.name)) 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): def test_total_purchase_cost_for_project(self):
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount) existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""") from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")

View File

@ -86,7 +86,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.make_payment_request, __("Make")); 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 // Show buttons only when pos view is active

View File

@ -4175,6 +4175,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -4185,7 +4246,7 @@
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal", "depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice", "fieldname": "recurring_invoice",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -4688,7 +4749,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-07-07 13:05:37.469682", "modified": "2017-08-31 11:23:08.675028",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -107,7 +107,7 @@ class SalesInvoice(SellingController):
def on_submit(self): def on_submit(self):
self.validate_pos_paid_amount() self.validate_pos_paid_amount()
if not self.recurring_id: if not self.subscription:
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total, self) self.company, self.base_grand_total, self)
@ -313,7 +313,7 @@ class SalesInvoice(SellingController):
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name', for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account', '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)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
@ -799,7 +799,7 @@ class SalesInvoice(SellingController):
for dn in set(updated_delivery_notes): for dn in set(updated_delivery_notes):
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified) 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"): for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"):
self.set(fieldname, reference_doc.get(fieldname)) self.set(fieldname, reference_doc.get(fieldname))

View File

@ -8,7 +8,8 @@ def get_data():
'Journal Entry': 'reference_name', 'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name', 'Payment Entry': 'reference_name',
'Payment Request': 'reference_name', 'Payment Request': 'reference_name',
'Sales Invoice': 'return_against' 'Sales Invoice': 'return_against',
'Subscription': 'reference_document',
}, },
'internal_links': { 'internal_links': {
'Sales Order': ['items', 'sales_order'] 'Sales Order': ['items', 'sales_order']
@ -26,5 +27,9 @@ def get_data():
'label': _('Returns'), 'label': _('Returns'),
'items': ['Sales Invoice'] 'items': ['Sales Invoice']
}, },
{
'label': _('Subscription'),
'items': ['Subscription']
},
] ]
} }

View File

@ -809,10 +809,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_name=%s""", si.name)) 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): def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item 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 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -1167,8 +1163,15 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(flt(si.outstanding_amount), flt(si.grand_total + si.total_advance, si.precision("outstanding_amount"))) self.assertEqual(flt(si.outstanding_amount), flt(si.grand_total + si.total_advance, si.precision("outstanding_amount")))
def test_multiple_uom_in_selling(self): 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].uom = "_Test UOM 1"
si.items[0].conversion_factor = None si.items[0].conversion_factor = None
si.items[0].price_list_rate = None si.items[0].price_list_rate = None

View File

@ -8,7 +8,16 @@ frappe.pages['pos'].on_page_load = function (wrapper) {
single_column: true 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.is_online && !cint(r.is_online)) {
// 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) { frappe.pages['pos'].refresh = function (wrapper) {

View File

@ -1,16 +1,15 @@
QUnit.test("test:POS Profile", function(assert) { QUnit.test("test:Sales Invoice", function(assert) {
assert.expect(1); assert.expect(3);
let done = assert.async(); let done = assert.async();
frappe.run_serially([ frappe.run_serially([
() => { () => {
return frappe.tests.make("POS Profile", [ return frappe.tests.make("POS Profile", [
{naming_series: "SINV"}, {naming_series: "SINV"},
{company: "Test Company"},
{country: "India"}, {country: "India"},
{currency: "INR"}, {currency: "INR"},
{write_off_account: "Write Off - TC"}, {write_off_account: "Write Off - FT"},
{write_off_cost_center: "Main - TC"}, {write_off_cost_center: "Main - FT"},
{payments: [ {payments: [
[ [
{"default": 1}, {"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"); assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested");
}, },
() => done() () => frappe.timeout(1),
]);
});
QUnit.test("test:Sales Invoice", function(assert) {
assert.expect(2);
let done = assert.async();
frappe.run_serially([
() => { () => {
return frappe.tests.make("Sales Invoice", [ return frappe.tests.make("Sales Invoice", [
{customer: "Test Customer 2"}, {customer: "Test Customer 2"},
{company: "Test Company"},
{is_pos: 1}, {is_pos: 1},
{posting_date: frappe.datetime.get_today()}, {posting_date: frappe.datetime.get_today()},
{due_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"}, {"item_code": "Test Product 1"},
{"qty": 5}, {"qty": 5},
{"warehouse":'Stores - TC'} {"warehouse":'Stores - FT'}
]] ]]
} }
]); ]);

View File

@ -10,7 +10,7 @@
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>", "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"idx": 0, "idx": 0,
"line_breaks": 0, "line_breaks": 0,
"modified": "2017-05-19 14:36:04.740728", "modified": "2017-09-01 14:27:04.871233",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Point of Sale", "name": "Point of Sale",

View File

@ -13,6 +13,7 @@ frappe.ui.form.on("Purchase Order", {
'Stock Entry': 'Material to Supplier' 'Stock Entry': 'Material to Supplier'
} }
}, },
onload: function(frm) { onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() { erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc); return erpnext.queries.warehouse(frm.doc);
@ -20,8 +21,7 @@ frappe.ui.form.on("Purchase Order", {
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
},
}
}); });
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({ 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") { if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __("Make")); 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"));
} }
}, },

View File

@ -2856,6 +2856,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -2866,7 +2927,7 @@
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal", "depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order", "fieldname": "recurring_order",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
@ -3335,7 +3396,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-19 14:03:51.838328", "modified": "2017-08-31 11:22:30.190589",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@ -5,7 +5,8 @@ def get_data():
'fieldname': 'purchase_order', 'fieldname': 'purchase_order',
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Journal Entry': 'reference_name', 'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name' 'Payment Entry': 'reference_name',
'Subscription': 'reference_document'
}, },
'internal_links': { 'internal_links': {
'Material Request': ['items', 'material_request'], 'Material Request': ['items', 'material_request'],
@ -23,11 +24,11 @@ def get_data():
}, },
{ {
'label': _('Reference'), 'label': _('Reference'),
'items': ['Material Request', 'Supplier Quotation', 'Project'] 'items': ['Material Request', 'Supplier Quotation', 'Project', 'Subscription']
}, },
{ {
'label': _('Sub-contracting'), 'label': _('Sub-contracting'),
'items': ['Stock Entry'] 'items': ['Stock Entry']
} },
] ]
} }

View 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()
]);
});

View File

@ -22,7 +22,9 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
cur_frm.page.set_inner_btn_group_as_primary(__("Make")); cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
cur_frm.add_custom_button(__("Quotation"), this.make_quotation, cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
__("Make")); __("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) { else if (this.frm.doc.docstatus===0) {

View File

@ -2051,6 +2051,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -2247,7 +2308,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-07-19 13:51:18.929697", "modified": "2017-08-31 11:23:25.268924",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier Quotation", "name": "Supplier Quotation",

View File

@ -3,6 +3,9 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'supplier_quotation', 'fieldname': 'supplier_quotation',
'non_standard_fieldnames': {
'Subscription': 'reference_document'
},
'internal_links': { 'internal_links': {
'Material Request': ['items', 'material_request'], 'Material Request': ['items', 'material_request'],
'Request for Quotation': ['items', 'request_for_quotation'], 'Request for Quotation': ['items', 'request_for_quotation'],
@ -17,6 +20,10 @@ def get_data():
'label': _('Reference'), 'label': _('Reference'),
'items': ['Material Request', 'Request for Quotation', 'Project'] 'items': ['Material Request', 'Request for Quotation', 'Project']
}, },
{
'label': _('Subscription'),
'items': ['Subscription']
},
] ]
} }

View 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: 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()
]);
});

View File

@ -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.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase 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.controllers.sales_and_purchase_return import validate_return
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
@ -53,13 +52,6 @@ class AccountsController(TransactionBase):
self.validate_party() self.validate_party()
self.validate_currency() 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': if self.doctype == 'Purchase Invoice':
self.validate_paid_amount() self.validate_paid_amount()
@ -84,11 +76,6 @@ class AccountsController(TransactionBase):
else: else:
frappe.db.set(self,'paid_amount',0) 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): def set_missing_values(self, for_validate=False):
if frappe.flags.in_test: if frappe.flags.in_test:
for fieldname in ["posting_date","transaction_date"]: for fieldname in ["posting_date","transaction_date"]:

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,13 @@
# Freeze an Account
Once an Account is Frozen, you won't be able to use it any accounting transaction. Since this is a critical action, you need to explicitly define a Role who can set an Account as Frozen. You can define this Role in the Account Settings.
`Accounts > Account Settings`
To freeze an Account, go to Chart of Accounts, and edit an Account.
<img class="screenshot" alt="Download Backup" src="/docs/assets/img/articles/freeze-account-1.png">
If User has Role define in the  Account Setting assigned, then he/she will be able to set an Account as Frozen.
<img class="screenshot" alt="Download Backup" src="/docs/assets/img/articles/freeze-account-2.png">

View File

@ -13,4 +13,5 @@ what-is-the-differences-of-total-and-valuation-in-tax-and-charges
withdrawing-salary-from-owners-equity-account withdrawing-salary-from-owners-equity-account
adjust-withhold-amount-payment-entry adjust-withhold-amount-payment-entry
common-receivable-account.md common-receivable-account.md
types-in-tax-template types-in-tax-template
freeze-account

View File

@ -0,0 +1,19 @@
# Fetching Data from one Document to Another
**Question:** We track Customer's PO No and PO Date field in the Sales Order. To have these values fetched into Sales Invoice as well, we have inserted Custom Field in the Sales Invoice. However, when we create Sales Invoice from the Sales Order, Customer's PO details are not being fetched.
**Answer:** When data is fetched from one transaction to the another transaction, then the mapping of data is done based on the field names. If two transactions have fields with the exact same name, then it's values are mapped.
For example, if you want Customer's PO No. and PO Date to be fetched from Sales Order to Sales Invoice, then you should ensure that Custom Fields added in the Sales Invoice has an exact same field name as in the Sales Order.
Sales Order (standard fields)
<img class="screenshot" alt="Standard fields in Sales Order" src="/docs/assets/img/articles/fetching-1.png">
Sales Invoice (custom fields)
<img class="screenshot" alt="Custom Field in Sales Invoice" src="/docs/assets/img/articles/fetching-2.png">
Since names for the Customer's PO related fields are same in the Sales Order and Sales Invoice, when creating Sales Invoice from the Sales Order, values in these fields are auto-fetched.
<img class="screenshot" alt="Values fetching from Sales Order to Sales Invoice" src="/docs/assets/img/articles/fetching-3.gif">

View File

@ -15,4 +15,5 @@ set-language
set-precision set-precision
user-restriction user-restriction
maximum-numbers-of-fields-in-a-form maximum-numbers-of-fields-in-a-form
child-table child-table
fetching-data-from-a-document

View File

@ -9,22 +9,23 @@ Schedule seminars, workshops, conferences etc using Training Event. You can also
### Inviting Employees for Event ### Inviting Employees for Event
You can invite your employees to attend the event. You can do so by selecting the employees to be invited in the employee table. You can invite your employees to attend the event. You can do so by selecting the employees to be invited in the employee table.
By default the status of the employee will be 'Open'. By default the status of the employee will be 'Open'.
The system shall notify the employee with status 'Open' by sending a email to the office email address of the employee as mentioned in the employee master if you have selected 'Send Email' checkbox.
The status is changed to 'Invited' when an invitation email is sent to the employee by the system.
When an Employee confirms his/her presence for the event you can change the status to 'Confirmed'.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event_employee.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event_employee.png">
When you submit the Training Event, a notifcation will be sent to the employee notifying that the Training has been scheduled. This is sent via Email Alert "Training Scheduled". You can modifiy this Email Alert to customize the message.
### Training Result ### Training Result
After compleation of the training Employee Wise training results can be stored based on the Feedback received from the Trainer. After compleation of the training Employee-wise training results can be stored based on the Feedback received from the Trainer.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_result.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_result.png">
When the Training Result is submitted, all the employees will receive an email notifying them that they must share their feedback via "Training Feedback". This is also managed via an Email Alert, so you can customize this alert too.
### Trainig Feedback ### Training Feedback
Collect feedback regarding the event from your Employees using Training Feedback. Employees can then share their feedback via Training Feedback.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_feedback.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_feedback.png">

View File

@ -9,6 +9,7 @@ manufacturing
projects projects
support support
human-resources human-resources
subscription
customer-portal customer-portal
website website
using-erpnext using-erpnext

View File

@ -48,6 +48,8 @@ In the Timesheet, if "Is Billable" is checked, you will find option to create Sa
<img class="screenshot" alt="Sales Invoice" src="/docs/assets/img/project/timesheet/timesheet-invoice-1.png"> <img class="screenshot" alt="Sales Invoice" src="/docs/assets/img/project/timesheet/timesheet-invoice-1.png">
<img class="screenshot" alt="Sales Invoice timesheet" src="{{docs_base_url}}/assets/img/project/timesheet/make_invoice_from_timesheet.gif">
####Sales Invoice ####Sales Invoice
Sales Invoice has dedicated table for the Timesheet table where Timesheet details will be updated. You can select more Timesheets in this table. Sales Invoice has dedicated table for the Timesheet table where Timesheet details will be updated. You can select more Timesheets in this table.

View File

@ -0,0 +1,6 @@
#Attendance
### Topics
{index}

View File

@ -0,0 +1,3 @@
student-attendance
student-leave-application
student-attendance-tool

View File

@ -0,0 +1,15 @@
# Student Attendance Tool
The Student Attendance tool allow you to bulk update the attendance for students based on **Student Group and Course Schedule**.
To mark the **Attedance* based on Student Group select the group based on
**1. Batch
2. Course
3. Activity **
Student detials will be autofetched and you can mark the attendance of the given date.
<img class="screenshot" alt="Student Attendance" src="/docs/assets/img/schools/setup/student-attendance-tool.gif">
{next}

View File

@ -0,0 +1,15 @@
# Student Attendance
Attendance doctype allows you to track and manage attendance of a student in all the days at any time. The Attendance module is designed to help teachers easily mark student attendance during class.
Attendance Records can be created against Student on daily basis.
To create Attendance record :
Select the **Student, Course Schedule and Student Group** for which attendance is to be marked for the given date. Set the Status to Present/Absent and save.
<img class="screenshot" alt="Student Attendance" src="/docs/assets/img/schools/schedule/student-attendance.gif">
**Student Attendance tool** can be used for bulk updation of the attendance based on **Batch, Course or Activity**.
{next}

View File

@ -0,0 +1,13 @@
#Student Leave Application
ERPNext allows you to record the leave application for a student.
To create a Student Leave application record, enter the Student and the date for the leave is applied and save.
<img class="screenshot" alt="Student Attendance" src="/docs/assets/img/schools/schedule/student-leave-application.gif">
Incase the student is not attending the school in order to participate or represent school in any event, he/she can be mark as present from the Leave Application itself.
Once a Leave Application is recorded for a student it will not be recorded in the absent student report as he has applied for a leave.
{next}

View File

@ -1,6 +1,6 @@
# Admission # Admission
This section contains student admission related documents. The Admission section allow you to create all records starting from Student application till the program enrollment. Below is the list of documents for Student addmission.
### Topics ### Topics

View File

@ -1,2 +1,4 @@
student-admission
student-applicant student-applicant
program-enrollment program-enrollment
program-enrollment-Tool

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