Merge branch 'pos-refactor' of https://github.com/netchampfaris/erpnext into pos-refactor
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index d6fa3f8..fb7c761 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -4,7 +4,7 @@
import frappe
from erpnext.hooks import regional_overrides
-__version__ = '8.8.1'
+__version__ = '8.8.3'
def get_default_company(user=None):
'''Get default company for user'''
@@ -28,6 +28,16 @@
if company:
return frappe.db.get_value('Company', company, 'default_currency')
+def get_default_cost_center(company):
+ '''Returns the default cost center of the company'''
+ if not company:
+ return None
+
+ if not frappe.flags.company_cost_center:
+ frappe.flags.company_cost_center = {}
+ if not company in frappe.flags.company_cost_center:
+ frappe.flags.company_cost_center[company] = frappe.db.get_value('Company', company, 'cost_center')
+ return frappe.flags.company_cost_center[company]
def get_company_currency(company):
'''Returns the default company currency'''
diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json
index 0489f9a..8de923f 100644
--- a/erpnext/accounts/doctype/account/account.json
+++ b/erpnext/accounts/doctype/account/account.json
@@ -13,6 +13,7 @@
"editable_grid": 0,
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -42,6 +43,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -70,6 +72,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -100,6 +103,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -130,6 +134,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -161,6 +166,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -190,6 +196,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -219,6 +226,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -250,6 +258,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -278,6 +287,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -309,6 +319,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -341,6 +352,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -372,6 +384,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -404,6 +417,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -433,6 +447,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -461,6 +476,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -489,6 +505,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -528,7 +545,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-04-21 17:22:41.150984",
+ "modified": "2017-08-11 15:28:35.855809",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@@ -641,6 +658,6 @@
"search_fields": "",
"show_name_in_global_search": 1,
"sort_order": "ASC",
- "track_changes": 0,
+ "track_changes": 1,
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 589bc40..7ab7901 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -53,6 +53,9 @@
if (self.is_paid == 1):
self.validate_cash()
+ if self._action=="submit" and self.update_stock:
+ self.make_batches('warehouse')
+
self.check_conversion_rate()
self.validate_credit_to_acc()
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js
new file mode 100644
index 0000000..6e33e1d
--- /dev/null
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js
@@ -0,0 +1,43 @@
+QUnit.module('Purchaes Invoice');
+
+QUnit.test("test purchase invoice", function(assert) {
+ assert.expect(4);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Invoice', [
+ {supplier: 'Test Supplier'},
+ {items: [
+ [
+ {'qty': 5},
+ {'item_code': 'Test Product 1'},
+ {'rate':100},
+ ]
+ ]},
+ {update_stock:1},
+ {supplier_address: 'Test1-Billing'},
+ {contact_person: 'Contact 3-Test Supplier'},
+ {taxes_and_charges: 'TEST In State GST'},
+ {tc_name: 'Test Term 1'},
+ {terms: 'This is Test'}
+ ]);
+ },
+ () => cur_frm.save(),
+ () => {
+ // get_item_details
+ assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
+ // get tax details
+ assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct");
+ // get tax account head details
+ assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
+ // grand_total Calculated
+ assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
+
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 8bbd6c5..3454a2e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -403,6 +403,27 @@
self.assertEquals(expected_gl_entries[gle.account][1], gle.debit)
self.assertEquals(expected_gl_entries[gle.account][2], gle.credit)
+ def test_auto_batch(self):
+ item_code = frappe.db.get_value('Item',
+ {'has_batch_no': 1, 'create_new_batch':1}, 'name')
+
+ if not item_code:
+ doc = frappe.get_doc({
+ 'doctype': 'Item',
+ 'is_stock_item': 1,
+ 'item_code': 'test batch item',
+ 'item_group': 'Products',
+ 'has_batch_no': 1,
+ 'create_new_batch': 1
+ }).insert(ignore_permissions=True)
+ item_code = doc.name
+
+ pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
+ posting_time=frappe.utils.nowtime(), item_code=item_code)
+
+ self.assertTrue(frappe.db.get_value('Batch',
+ {'item': item_code, 'reference_name': pi.name}))
+
def test_update_stock_and_purchase_return(self):
actual_qty_0 = get_qty_after_transaction()
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js
new file mode 100644
index 0000000..5e357ca
--- /dev/null
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js
@@ -0,0 +1,26 @@
+QUnit.module('Sales Taxes and Charges Template');
+
+QUnit.test("test sales taxes and charges template", function(assert) {
+ assert.expect(1);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Taxes and Charges Template', [
+ {title: "TEST In State GST"},
+ {taxes:[
+ [
+ {charge_type:"On Net Total"},
+ {account_head:"CGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
+ ],
+ [
+ {charge_type:"On Net Total"},
+ {account_head:"SGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
+ ]
+ ]}
+ ]);
+ },
+ () => {assert.ok(cur_frm.doc.title=='TEST In State GST');},
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js
new file mode 100644
index 0000000..35b2558
--- /dev/null
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js
@@ -0,0 +1,43 @@
+QUnit.module('Sales Invoice');
+
+QUnit.test("test sales Invoice", function(assert) {
+ assert.expect(4);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Sales Invoice', [
+ {customer: 'Test Customer 1'},
+ {items: [
+ [
+ {'qty': 5},
+ {'item_code': 'Test Product 1'},
+ ]
+ ]},
+ {update_stock:1},
+ {customer_address: 'Test1-Billing'},
+ {shipping_address_name: 'Test1-Shipping'},
+ {contact_person: 'Contact 1-Test Customer 1'},
+ {taxes_and_charges: 'TEST In State GST'},
+ {tc_name: 'Test Term 1'},
+ {terms: 'This is Test'}
+ ]);
+ },
+ () => cur_frm.save(),
+ () => {
+ // get_item_details
+ assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
+ // get tax details
+ assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct");
+ // get tax account head details
+ assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
+ // grand_total Calculated
+ assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
+
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
new file mode 100644
index 0000000..940f36f
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
@@ -0,0 +1,67 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order", function(assert) {
+ assert.expect(11);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-USD'},
+ {currency: 'USD'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 100},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]},
+
+ {tc_name: 'Test Term 1'},
+ {terms: 'This is a term.'}
+ ]);
+ },
+
+ () => {
+ // Get supplier details
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ assert.ok($('div.control-value.like-disabled-input.for-description').text().includes('Contact 3'), "Contact display correct");
+ assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Contact email correct");
+ // Get item details
+ assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct");
+ assert.ok(cur_frm.doc.items[0].description == 'Test Product 4', "Description correct");
+ assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct");
+ // Calculate total
+ assert.ok(cur_frm.doc.total == 500, "Total correct");
+ // Get terms
+ assert.ok(cur_frm.doc.terms == 'This is a term.', "Terms correct");
+ },
+
+ () => cur_frm.print_doc(),
+ () => frappe.timeout(2),
+ () => {
+ assert.ok($('.btn-print-print').is(':visible'), "Print Format Available");
+ assert.ok($('div > div:nth-child(5) > div > div > table > tbody > tr > td:nth-child(4) > div').text().includes('Test Product 4'), "Print Preview Works");
+ },
+
+ () => cur_frm.print_doc(),
+ () => frappe.timeout(1),
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => {
+ assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");
+ },
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
new file mode 100644
index 0000000..09fc33d
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
@@ -0,0 +1,62 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order with get items", function(assert) {
+ assert.expect(4);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-USD'},
+ {currency: 'USD'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]}
+ ]);
+ },
+
+ () => {
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ },
+
+ () => frappe.timeout(0.3),
+ () => frappe.click_button('Get items from'),
+ () => frappe.timeout(0.3),
+
+ () => frappe.click_link('Product Bundle'),
+ () => frappe.timeout(0.5),
+
+ () => cur_dialog.set_value('product_bundle', 'Computer'),
+ () => frappe.click_button('Get Items'),
+ () => frappe.timeout(1),
+
+ // Check if items are fetched from Product Bundle
+ () => {
+ assert.ok(cur_frm.doc.items[1].item_name == 'CPU', "Product bundle item 1 correct");
+ assert.ok(cur_frm.doc.items[2].item_name == 'Screen', "Product bundle item 2 correct");
+ assert.ok(cur_frm.doc.items[3].item_name == 'Keyboard', "Product bundle item 3 correct");
+ },
+
+ () => cur_frm.doc.items[1].warehouse = 'Stores - WP',
+ () => cur_frm.doc.items[2].warehouse = 'Stores - WP',
+ () => cur_frm.doc.items[3].warehouse = 'Stores - WP',
+
+ () => cur_frm.save(),
+ () => frappe.timeout(1),
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js
new file mode 100644
index 0000000..654586a
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js
@@ -0,0 +1,77 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order receipt", function(assert) {
+ assert.expect(5);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-USD'},
+ {currency: 'USD'},
+ {items: [
+ [
+ {"item_code": 'Test Product 1'},
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 100},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]},
+ ]);
+ },
+
+ () => {
+
+ // Check supplier and item details
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 1', "Item name correct");
+ assert.ok(cur_frm.doc.items[0].description == 'Test Product 1', "Description correct");
+ assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct");
+
+ },
+
+ () => frappe.timeout(1),
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+
+ () => frappe.timeout(1.5),
+ () => frappe.click_button('Close'),
+ () => frappe.timeout(0.3),
+
+ // Make Purchase Receipt
+ () => frappe.click_button('Make'),
+ () => frappe.timeout(0.3),
+
+ () => frappe.click_link('Receipt'),
+ () => frappe.timeout(2),
+
+ () => cur_frm.save(),
+
+ // Save and submit Purchase Receipt
+ () => frappe.timeout(1),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(1),
+
+ // View Purchase order in Stock Ledger
+ () => frappe.click_button('View'),
+ () => frappe.timeout(0.3),
+
+ () => frappe.click_link('Stock Ledger'),
+ () => frappe.timeout(2),
+ () => {
+ assert.ok($('div.slick-cell.l2.r2 > a').text().includes('Test Product 1')
+ && $('div.slick-cell.l9.r9 > div').text().includes(5)
+ && $('div.slick-cell.l12.r12 > div').text().includes(100), "Stock ledger entry correct");
+ },
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
new file mode 100644
index 0000000..54fa777
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
@@ -0,0 +1,48 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order with discount on grand total", function(assert) {
+ assert.expect(4);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-EUR'},
+ {currency: 'EUR'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 500 },
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]},
+ {apply_discount_on: 'Grand Total'},
+ {additional_discount_percentage: 10}
+ ]);
+ },
+
+ () => frappe.timeout(1),
+
+ () => {
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ assert.ok(cur_frm.doc.items[0].rate == 500, "Rate correct");
+ // Calculate total
+ assert.ok(cur_frm.doc.total == 2500, "Total correct");
+ // Calculate grand total after discount
+ assert.ok(cur_frm.doc.grand_total == 2250, "Grand total correct");
+ },
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
new file mode 100644
index 0000000..4ea8b97
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
@@ -0,0 +1,45 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order with item wise discount", function(assert) {
+ assert.expect(4);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-EUR'},
+ {currency: 'EUR'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"warehouse": 'Stores - WP'},
+ {"discount_percentage": 20}
+ ]
+ ]}
+ ]);
+ },
+
+ () => frappe.timeout(1),
+
+ () => {
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ assert.ok(cur_frm.doc.items[0].discount_percentage == 20, "Discount correct");
+ // Calculate totals after discount
+ assert.ok(cur_frm.doc.total == 2000, "Total correct");
+ assert.ok(cur_frm.doc.grand_total == 2000, "Grand total correct");
+ },
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
new file mode 100644
index 0000000..0f543c5
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
@@ -0,0 +1,40 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order with multi UOM", function(assert) {
+ assert.expect(3);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 100},
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]}
+ ]);
+ },
+
+ () => {
+ assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
+ assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct");
+ assert.ok(cur_frm.doc.items[0].uom == 'Unit', "Multi UOM correct");
+ },
+
+ () => frappe.timeout(1),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
new file mode 100644
index 0000000..b5f59ad
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
@@ -0,0 +1,44 @@
+QUnit.module('Buying');
+
+QUnit.test("test: purchase order with taxes and charges", function(assert) {
+ assert.expect(3);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Purchase Order', [
+ {supplier: 'Test Supplier'},
+ {company: 'Wind Power LLC'},
+ {is_subcontracted: 'No'},
+ {buying_price_list: 'Test-Buying-USD'},
+ {currency: 'USD'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 500 },
+ {"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
+ {"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
+ {"warehouse": 'Stores - WP'}
+ ]
+ ]},
+
+ {taxes_and_charges: 'TEST In State GST'}
+ ]);
+ },
+
+ () => {
+ // Check taxes and calculate grand total
+ assert.ok(cur_frm.doc.taxes[1].account_head=='SGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), "Account Head abbr correct");
+ assert.ok(cur_frm.doc.total_taxes_and_charges == 225, "Taxes and charges correct");
+ assert.ok(cur_frm.doc.grand_total == 2725, "Grand total correct");
+ },
+
+ () => frappe.timeout(0.3),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 558e072..8509d77 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -138,7 +138,6 @@
dialog.show();
},
-
make_suppplier_quotation: function(frm) {
var doc = frm.doc;
var dialog = new frappe.ui.Dialog({
@@ -207,6 +206,29 @@
if(!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
+ },
+ no_quote: function(frm, cdt, cdn) {
+ var d = locals[cdt][cdn];
+ if (d.no_quote) {
+ if (d.quote_status != __('Received')) {
+ frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote');
+ } else {
+ frappe.msgprint(__("Cannot set a received RFQ to No Quote"));
+ frappe.model.set_value(cdt, cdn, 'no_quote', 0);
+ }
+ } else {
+ d.quote_status = __('Pending');
+ frm.call({
+ method:"update_rfq_supplier_status",
+ doc: frm.doc,
+ args: {
+ sup_name: d.supplier
+ },
+ callback: function(r) {
+ frm.refresh_field("suppliers");
+ }
+ });
+ }
}
})
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index e9603fb..2d8820d 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -54,6 +54,7 @@
frappe.db.set(self, 'status', 'Submitted')
for supplier in self.suppliers:
supplier.email_sent = 0
+ supplier.quote_status = 'Pending'
def on_cancel(self):
frappe.db.set(self, 'status', 'Cancelled')
@@ -157,6 +158,28 @@
attachments.append(frappe.attach_print(self.doctype, self.name, doc=self))
return attachments
+ def update_rfq_supplier_status(self, sup_name=None):
+ for supplier in self.suppliers:
+ if sup_name == None or supplier.supplier == sup_name:
+ if supplier.quote_status != _('No Quote'):
+ quote_status = _('Received')
+ for item in self.items:
+ sqi_count = frappe.db.sql("""
+ SELECT
+ COUNT(sqi.name) as count
+ FROM
+ `tabSupplier Quotation Item` as sqi,
+ `tabSupplier Quotation` as sq
+ WHERE sq.supplier = %(supplier)s
+ AND sqi.docstatus = 1
+ AND sqi.request_for_quotation_item = %(rqi)s
+ AND sqi.parent = sq.name""",
+ {"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
+ if (sqi_count.count) == 0:
+ quote_status = _('Pending')
+ supplier.quote_status = quote_status
+
+
@frappe.whitelist()
def send_supplier_emails(rfq_name):
check_portal_enabled('Request for Quotation')
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 2e8b946..5c9fb13 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -10,21 +10,41 @@
from frappe.utils import nowdate
class TestRequestforQuotation(unittest.TestCase):
+ def test_quote_status(self):
+ from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
+ rfq = make_request_for_quotation()
+
+ self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Pending')
+ self.assertEquals(rfq.get('suppliers')[1].quote_status, 'Pending')
+
+ # Submit the first supplier quotation
+ sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
+ sq.submit()
+
+ # No Quote first supplier quotation
+ rfq.get('suppliers')[1].no_quote = 1
+ rfq.get('suppliers')[1].quote_status = 'No Quote'
+
+ rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
+
+ self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Received')
+ self.assertEquals(rfq.get('suppliers')[1].quote_status, 'No Quote')
+
def test_make_supplier_quotation(self):
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
rfq = make_request_for_quotation()
-
+
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq.submit()
-
+
sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
sq1.submit()
-
+
self.assertEquals(sq.supplier, rfq.get('suppliers')[0].supplier)
self.assertEquals(sq.get('items')[0].request_for_quotation, rfq.name)
self.assertEquals(sq.get('items')[0].item_code, "_Test Item")
self.assertEquals(sq.get('items')[0].qty, 5)
-
+
self.assertEquals(sq1.supplier, rfq.get('suppliers')[1].supplier)
self.assertEquals(sq1.get('items')[0].request_for_quotation, rfq.name)
self.assertEquals(sq1.get('items')[0].item_code, "_Test Item")
@@ -61,15 +81,15 @@
rfq.get('items')[0].rate = 100
rfq.supplier = rfq.suppliers[0].supplier
supplier_quotation_name = create_supplier_quotation(rfq)
-
+
supplier_quotation_doc = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
-
+
self.assertEquals(supplier_quotation_doc.supplier, rfq.get('suppliers')[0].supplier)
self.assertEquals(supplier_quotation_doc.get('items')[0].request_for_quotation, rfq.name)
self.assertEquals(supplier_quotation_doc.get('items')[0].item_code, "_Test Item")
self.assertEquals(supplier_quotation_doc.get('items')[0].qty, 5)
self.assertEquals(supplier_quotation_doc.get('items')[0].amount, 500)
-
+
def make_request_for_quotation(supplier_data=None):
"""
@@ -81,10 +101,10 @@
rfq.status = 'Draft'
rfq.company = '_Test Company'
rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
-
+
for data in supplier_data:
rfq.append('suppliers', data)
-
+
rfq.append("items", {
"item_code": "_Test Item",
"description": "_Test Item",
@@ -93,11 +113,11 @@
"warehouse": "_Test Warehouse - _TC",
"schedule_date": nowdate()
})
-
+
rfq.submit()
-
+
return rfq
-
+
def get_supplier_data():
return [{
"supplier": "_Test Supplier",
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
similarity index 84%
rename from erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.js
rename to erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
index 75760bf..6927d82 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js
@@ -9,7 +9,7 @@
date = frappe.datetime.add_days(frappe.datetime.now_date(), 10);
return frappe.tests.make('Request for Quotation', [
{transaction_date: date},
- {company: 'Test Company'},
+ {company: 'Wind Power LLC'},
{suppliers: [
[
{"supplier": 'Test Supplier'},
@@ -21,7 +21,7 @@
{"item_code": 'Test Product 4'},
{"qty": 5},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(),20)},
- {"warehouse": 'All Warehouses - TC'}
+ {"warehouse": 'All Warehouses - WP'}
]
]},
{message_for_supplier: 'Please supply the specified items at the best possible rates'},
@@ -29,13 +29,13 @@
]);
},
() => {
- assert.ok(cur_frm.doc.transaction_date == date, "Date correct");
- assert.ok(cur_frm.doc.company == 'Test Company', "Company correct");
+ assert.ok(cur_frm.doc.transaction_date == date, "Date correct");
+ assert.ok(cur_frm.doc.company == 'Wind Power LLC', "Company correct");
assert.ok(cur_frm.doc.suppliers[0].supplier_name == 'Test Supplier', "Supplier name correct");
assert.ok(cur_frm.doc.suppliers[0].contact == 'Contact 3-Test Supplier', "Contact correct");
assert.ok(cur_frm.doc.suppliers[0].email_id == 'test@supplier.com', "Email id correct");
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item Name correct");
- assert.ok(cur_frm.doc.items[0].warehouse == 'All Warehouses - TC', "Warehouse correct");
+ assert.ok(cur_frm.doc.items[0].warehouse == 'All Warehouses - WP', "Warehouse correct");
assert.ok(cur_frm.doc.message_for_supplier == 'Please supply the specified items at the best possible rates', "Reply correct");
assert.ok(cur_frm.doc.tc_name == 'Test Term 1', "Term name correct");
},
@@ -52,10 +52,12 @@
() => frappe.timeout(0.3),
() => frappe.click_link('Material Request'),
() => frappe.timeout(1),
+ () => frappe.click_button('Get Items'),
+ () => frappe.timeout(1),
() => {
- assert.ok($('h4').text().includes('Select Material Requests'), "Getting items from material requests work");
+ assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Getting items from material requests work");
},
- () => frappe.click_button('Close'),
+ () => cur_frm.save(),
() => frappe.timeout(1),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
diff --git a/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
new file mode 100644
index 0000000..f831b4f
--- /dev/null
+++ b/erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation_for_status.js
@@ -0,0 +1,132 @@
+QUnit.module('buying');
+
+QUnit.test("Test: Request for Quotation", function (assert) {
+ assert.expect(5);
+ let done = assert.async();
+ let rfq_name = "";
+
+ frappe.run_serially([
+ // Go to RFQ list
+ () => frappe.set_route("List", "Request for Quotation"),
+ // Create a new RFQ
+ () => 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"),
+ // Add Suppliers
+ () => {
+ cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.supplier = "_Test Supplier";
+ frappe.click_check('Send Email');
+ cur_frm.cur_grid.frm.script_manager.trigger('supplier');
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.cur_grid.toggle_view();
+ },
+ () => frappe.timeout(1),
+ () => frappe.click_button('Add Row',0),
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.fields_dict.suppliers.grid.grid_rows[1].toggle_view();
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.supplier = "_Test Supplier 1";
+ frappe.click_check('Send Email');
+ cur_frm.cur_grid.frm.script_manager.trigger('supplier');
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.cur_grid.toggle_view();
+ },
+ () => frappe.timeout(1),
+ // Add Item
+ () => {
+ cur_frm.fields_dict.items.grid.grid_rows[0].toggle_view();
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.fields_dict.items.grid.grid_rows[0].doc.item_code = "_Test Item";
+ frappe.set_control('item_code',"_Test Item");
+ frappe.set_control('qty',5);
+ frappe.set_control('schedule_date', "05-05-2017");
+ cur_frm.cur_grid.frm.script_manager.trigger('supplier');
+ },
+ () => frappe.timeout(2),
+ () => {
+ cur_frm.cur_grid.toggle_view();
+ },
+ () => frappe.timeout(2),
+ () => {
+ cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - _TC";
+ },
+ () => frappe.click_button('Save'),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Submit'),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Yes'),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Menu'),
+ () => frappe.timeout(1),
+ () => frappe.click_link('Reload'),
+ () => frappe.timeout(1),
+ () => {
+ assert.equal(cur_frm.doc.docstatus, 1);
+ rfq_name = cur_frm.doc.name;
+ assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.quote_status == "Pending");
+ assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Pending");
+ },
+ () => {
+ cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
+ },
+ () => frappe.timeout(1),
+ () => {
+ frappe.click_check('No Quote');
+ },
+ () => frappe.timeout(1),
+ () => {
+ cur_frm.cur_grid.toggle_view();
+ },
+ () => frappe.click_button('Update'),
+ () => frappe.timeout(1),
+
+ () => frappe.click_button('Supplier Quotation'),
+ () => frappe.timeout(1),
+ () => frappe.click_link('Make'),
+ () => frappe.timeout(1),
+ () => {
+ frappe.set_control('supplier',"_Test Supplier 1");
+ },
+ () => frappe.timeout(1),
+ () => frappe.click_button('Make Supplier Quotation'),
+ () => frappe.timeout(1),
+ () => cur_frm.set_value("company", "_Test Company"),
+ () => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99,
+ () => frappe.timeout(1),
+ () => frappe.click_button('Save'),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Submit'),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Yes'),
+ () => frappe.timeout(1),
+ () => frappe.set_route("List", "Request for Quotation"),
+ () => frappe.timeout(2),
+ () => frappe.set_route("List", "Request for Quotation"),
+ () => frappe.timeout(2),
+ () => frappe.click_link(rfq_name),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Menu'),
+ () => frappe.timeout(1),
+ () => frappe.click_link('Reload'),
+ () => frappe.timeout(1),
+ () => {
+ assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
+ assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1);
+ },
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
index a7c5a37..4babbe9 100644
--- a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
+++ b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
@@ -139,6 +139,69 @@
},
{
"allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
+ "fieldname": "no_quote",
+ "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": "No Quote",
+ "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:doc.docstatus >= 1 && !doc.no_quote",
+ "fieldname": "quote_status",
+ "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": "Quote Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Pending\nReceived\nNo Quote",
+ "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,
@@ -269,7 +332,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2017-07-24 06:52:19.542717",
+ "modified": "2017-07-26 22:25:58.096584",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Supplier",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 1cb5a18..b3d92be 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -31,9 +31,11 @@
def on_submit(self):
frappe.db.set(self, "status", "Submitted")
+ self.update_rfq_supplier_status(1)
def on_cancel(self):
frappe.db.set(self, "status", "Cancelled")
+ self.update_rfq_supplier_status(0)
def on_trash(self):
pass
@@ -50,6 +52,41 @@
"is_child_table": True
}
})
+ def update_rfq_supplier_status(self, include_me):
+ rfq_list = set([])
+ for item in self.items:
+ if item.request_for_quotation:
+ rfq_list.add(item.request_for_quotation)
+ for rfq in rfq_list:
+ doc = frappe.get_doc('Request for Quotation', rfq)
+ doc_sup = frappe.get_all('Request for Quotation Supplier', filters=
+ {'parent': doc.name, 'supplier': self.supplier}, fields=['name', 'quote_status'])[0]
+
+ quote_status = _('Received')
+ for item in doc.items:
+ sqi_count = frappe.db.sql("""
+ SELECT
+ COUNT(sqi.name) as count
+ FROM
+ `tabSupplier Quotation Item` as sqi,
+ `tabSupplier Quotation` as sq
+ WHERE sq.supplier = %(supplier)s
+ AND sqi.docstatus = 1
+ AND sq.name != %(me)s
+ AND sqi.request_for_quotation_item = %(rqi)s
+ AND sqi.parent = sq.name""",
+ {"supplier": self.supplier, "rqi": item.name, 'me': self.name}, as_dict=1)[0]
+ self_count = sum(my_item.request_for_quotation_item == item.name
+ for my_item in self.items) if include_me else 0
+ if (sqi_count.count + self_count) == 0:
+ quote_status = _('Pending')
+ if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'):
+ frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \
+ have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier))
+ frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
+ frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0)
+ elif doc_sup.quote_status != _('No Quote'):
+ frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
new file mode 100644
index 0000000..8404cb5
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
@@ -0,0 +1,75 @@
+QUnit.module('Buying');
+
+QUnit.test("test: supplier quotation", function(assert) {
+ assert.expect(11);
+ let done = assert.async();
+ let date;
+
+ frappe.run_serially([
+ () => {
+ date = frappe.datetime.add_days(frappe.datetime.now_date(), 10);
+ return frappe.tests.make('Supplier Quotation', [
+ {supplier: 'Test Supplier'},
+ {transaction_date: date},
+ {company: 'Wind Power LLC'},
+ {buying_price_list: 'Test-Buying-USD'},
+ {currency: 'USD'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"rate": 200},
+ {"warehouse": 'All Warehouses - WP'}
+ ]
+ ]},
+ {apply_discount_on: 'Grand Total'},
+ {additional_discount_percentage: 10},
+ {tc_name: 'Test Term 1'},
+ {terms: 'This is a term'}
+ ]);
+ },
+ () => {
+ // Get Supplier details
+ assert.ok(cur_frm.doc.supplier == 'Test Supplier', "Supplier correct");
+ assert.ok(cur_frm.doc.company == 'Wind Power LLC', "Company correct");
+ // Get Contact details
+ assert.ok(cur_frm.doc.contact_display == 'Contact 3', "Conatct correct");
+ assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Email correct");
+ // Get uom
+ assert.ok(cur_frm.doc.items[0].uom == 'Unit', "Multi uom correct");
+ assert.ok(cur_frm.doc.total == 1000, "Total correct");
+ // Calculate total after discount
+ assert.ok(cur_frm.doc.grand_total == 900, "Grand total correct");
+ // Get terms
+ assert.ok(cur_frm.doc.tc_name == 'Test Term 1', "Terms correct");
+ },
+
+ () => cur_frm.print_doc(),
+ () => frappe.timeout(2),
+ () => {
+ assert.ok($('.btn-print-print').is(':visible'), "Print Format Available");
+ assert.ok($("table > tbody > tr > td:nth-child(3) > div").text().includes("Test Product 4"), "Print Preview Works As Expected");
+ },
+ () => cur_frm.print_doc(),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Get items from'),
+ () => frappe.timeout(0.3),
+ () => frappe.click_link('Material Request'),
+ () => frappe.timeout(0.3),
+ () => frappe.click_button('Get Items'),
+ () => frappe.timeout(1),
+ () => {
+ // Get item from Material Requests
+ assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Getting items from material requests work");
+ },
+
+ () => cur_frm.save(),
+ () => frappe.timeout(1),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
new file mode 100644
index 0000000..bc07b75
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
@@ -0,0 +1,34 @@
+QUnit.module('Buying');
+
+QUnit.test("test: supplier quotation with item wise discount", function(assert){
+ assert.expect(2);
+ let done = assert.async();
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Supplier Quotation', [
+ {supplier: 'Test Supplier'},
+ {company: 'Test Company'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"uom": 'Unit'},
+ {"warehouse": 'All Warehouses - TC'},
+ {'discount_percentage': 10},
+ ]
+ ]}
+ ]);
+ },
+
+ () => {
+ assert.ok(cur_frm.doc.total == 900, "Total correct");
+ assert.ok(cur_frm.doc.grand_total == 900, "Grand total correct");
+ },
+
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
new file mode 100644
index 0000000..c4b3419
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
@@ -0,0 +1,37 @@
+QUnit.module('Buying');
+
+QUnit.test("test: supplier quotation with taxes and charges", function(assert) {
+ assert.expect(3);
+ let done = assert.async();
+ let supplier_quotation_name;
+
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Supplier Quotation', [
+ {supplier: 'Test Supplier'},
+ {items: [
+ [
+ {"item_code": 'Test Product 4'},
+ {"qty": 5},
+ {"rate": 100},
+ {"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
+ ]
+ ]},
+ {taxes_and_charges:'TEST In State GST'},
+ ]);
+ },
+ () => {supplier_quotation_name = cur_frm.doc.name;},
+ () => {
+ assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
+ assert.ok(cur_frm.doc.total_taxes_and_charges == 45, "Taxes and charges correct");
+ assert.ok(cur_frm.doc.grand_total == 545, "Grand total correct");
+ },
+
+ () => cur_frm.save(),
+ () => frappe.timeout(0.3),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 9ccf7f4..5a0b967 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -83,10 +83,12 @@
def set_landed_cost_voucher_amount(self):
for d in self.get("items"):
- lc_voucher_amount = frappe.db.sql("""select sum(applicable_charges)
+ lc_voucher_data = frappe.db.sql("""select sum(applicable_charges), cost_center
from `tabLanded Cost Item`
where docstatus = 1 and purchase_receipt_item = %s""", d.name)
- d.landed_cost_voucher_amount = lc_voucher_amount[0][0] if lc_voucher_amount else 0.0
+ d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
+ if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
+ d.db_set('cost_center', lc_voucher_data[0][1])
def set_total_in_words(self):
from frappe.utils import money_in_words
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 93a2941..8bc7ad8 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -84,11 +84,19 @@
self.delete_events()
def has_active_quotation(self):
- return frappe.db.sql("""
- select q.name
- from `tabQuotation` q, `tabQuotation Item` qi
- where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
- and q.status not in ('Lost', 'Closed')""", self.name)
+ if not self.with_items:
+ return frappe.get_all('Quotation',
+ {
+ 'opportunity': self.name,
+ 'status': ("not in", ['Lost', 'Closed']),
+ 'docstatus': 1
+ }, 'name')
+ else:
+ return frappe.db.sql("""
+ select q.name
+ from `tabQuotation` q, `tabQuotation Item` qi
+ where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
+ and q.status not in ('Lost', 'Closed')""", self.name)
def has_ordered_quotation(self):
return frappe.db.sql("""
@@ -212,6 +220,8 @@
quotation.run_method("set_missing_values")
quotation.run_method("calculate_taxes_and_totals")
+ if not source.with_items:
+ quotation.opportunity = source.name
doclist = get_mapped_doc("Opportunity", source_name, {
"Opportunity": {
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 0957afe..4cd20ea 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -3,9 +3,50 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import today
+from erpnext.crm.doctype.opportunity.opportunity import make_quotation
import unittest
test_records = frappe.get_test_records('Opportunity')
class TestOpportunity(unittest.TestCase):
- pass
+ def test_opportunity_status(self):
+ doc = make_opportunity(with_items=0)
+ quotation = make_quotation(doc.name)
+ quotation.append('items', {
+ "item_code": "_Test Item",
+ "qty": 1
+ })
+
+ quotation.run_method("set_missing_values")
+ quotation.run_method("calculate_taxes_and_totals")
+ quotation.submit()
+
+ doc = frappe.get_doc('Opportunity', doc.name)
+ self.assertEquals(doc.status, "Quotation")
+
+def make_opportunity(**args):
+ args = frappe._dict(args)
+
+ opp_doc = frappe.get_doc({
+ "doctype": "Opportunity",
+ "enquiry_from": "Customer" or args.enquiry_from,
+ "enquiry_type": "Sales",
+ "with_items": args.with_items or 0,
+ "transaction_date": today()
+ })
+
+ if opp_doc.enquiry_from == 'Customer':
+ opp_doc.customer = args.customer or "_Test Customer"
+
+ if opp_doc.enquiry_from == 'Lead':
+ opp_doc.customer = args.lead or "_T-Lead-00001"
+
+ if args.with_items:
+ opp_doc.append('items', {
+ "item_code": args.item_code or "_Test Item",
+ "qty": args.qty or 1
+ })
+
+ opp_doc.insert()
+ return opp_doc
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md b/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md
index 598aa0e..060cd17 100644
--- a/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md
+++ b/erpnext/docs/user/manual/en/accounts/multi-currency-accounting.md
@@ -23,6 +23,15 @@
In case of multi-company setup, accounting currency of Party must be same for all the companies.
+### Exchange Rates
+When dealing with multiple currencies, ERPNext has the Currency Exchange module for managing exchange rates. It allows you to save the exchange rate quotes you require.
+
+For foreign currency transactions, ERPNext checks Currency Exchange for any matching record. If this fails, ERPNext will attempt to get the exchange rate quote from [fixer.io](http://fixer.io). If this still fails, then the exchange rate will have to be entered manually.
+
+#### Exchange Rate Selection
+ERPNext automatically fetches the latest exchange rate available.
+
+
## Transactions
### Sales Invoice
@@ -50,61 +59,6 @@
<img class="screenshot" alt="Journal Entry in multi currency" src="/docs/assets/img/accounts/multi-currency/journal-entry-row.png">
-#### Example 1: Payment Entry Against Customer With Alternate Currency
-
-Suppose, default currency of the company is INR and customer's accounting currency is USD. Customer made full payment against an outstanding invoice of USD 100. Exchange Rate (USD -> INR) in Sales Invoice was 60.
-
-Exchange Rate in the payment entry should always be same as invoice (60), even if exchange rate on the payment date is 62. The bank account will be credited by the amount considering exchange rate as 62. Hence, Exchnage Gain / Loss will be booked based on exchange rate difference.
-
-<img class="screenshot" alt="Payment Entry" src="/docs/assets/img/accounts/multi-currency/payment-entry.png">
-
-#### Example 2: Inter-bank Transfer (USD -> INR)
-
-Suppose the default currency of the company is INR. You have a Paypal account for which Currency is USD. You receive payments in the paypal account and lets say, paypal transfers amount once in a week to your other bank account which is managed in INR.
-
-Paypal account gets debited on different date with different exchange rate, but on transfer date the exchange rate can be different. Hence, there is generally Exchange Loss / Gain on the transfer entry.
-In the bank transfer entry, system sets exchange rate of the credit account (Paypal) based on the average incoming exchange rate. This is to maintain Paypal balance properly in company currency. In case you modify the average exchange rate, you need to adjust the exchange rate manually in the future entries, so that balance in account currency and company currency are in sync.
-Then you should calculate and enter Exchange Loss / Gain based on the Paypal exchange rate and the exchange rate on the transfer date.
-
-Lets say, Paypal account debited by following amounts over the week, which has not been transferred to your other bank account.
-
-<table class="table table-bordered">
- <thead>
- <tr>
- <td>Date</td>
- <td>Account</td>
- <td>Debit (USD)</td>
- <td>Exchange Rate</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>2015-09-02</td>
- <td>Paypal</td>
- <td>100</td>
- <td>60</td>
- </tr>
- <tr>
- <td>2015-09-02</td>
- <td>Paypal</td>
- <td>100</td>
- <td>61</td>
- </tr>
- <tr>
- <td>2015-09-02</td>
- <td>Paypal</td>
- <td>100</td>
- <td>64</td>
- </tr>
- </tbody>
-</table>
-
-
-Suppose, Exchange Rate on the payment date is 62 and Bank Transfer Entry will be look like below:
-
-<img class="screenshot" alt="Inter Bank Transfer" src="/docs/assets/img/accounts/multi-currency/bank-transfer.png">
-
-
## Reports
### General Ledger
diff --git a/erpnext/docs/user/manual/en/buying/request-for-quotation.md b/erpnext/docs/user/manual/en/buying/request-for-quotation.md
index 182c89d..a48c297 100644
--- a/erpnext/docs/user/manual/en/buying/request-for-quotation.md
+++ b/erpnext/docs/user/manual/en/buying/request-for-quotation.md
@@ -2,43 +2,40 @@
A Request for Quotation is a document that an organization submits to one or more suppliers eliciting quotation for items.
-In ERPNext, You can create request for quotation directly by going to:
+In ERPNext, You can create Request for Quotation directly by going to:
> Buying > Documents > Request for Quotation > New Request for Quotation
-
+After creation of Request for Quotation, there are two ways to generate Supplier Quotation from Request for Quotation.
-After creation of request for quotation, there are two ways to generate supplier quotation from request for quotation.
#### For User
-__Step 1:__ Open request for quotation and click on make supplier quotation.
+__Step 1:__ Open Request for Quotation and click on make Supplier Quotation.

-__Step 2:__ Select supplier and click on make supplier quotation.
+__Step 2:__ Select supplier and click on make Supplier Quotation.

-__Step 3:__ System will open the supplier quotation, user has to enter the rate and submit it.
+__Step 3:__ System will open the Supplier Quotation, user has to enter the rate and submit it.

#### For Supplier
-__Step 1:__ User has to create contact or enter Email Address against the supplier on request for quotation.
+__Step 1:__ User has to create contact or enter Email Address against the supplier on Request for Quotation.

__Step 2:__ User has to click on send supplier emails button.
-
-
-* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the request for quotation form.
+* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the Request for Quotation form.

-* If supplier's user available: system will send request for quotation link to supplier, supplier has to login using his credentials to view request for quotation form on portal.
+* If supplier's user available: system will send Request for Quotation link to supplier, supplier has to login using his credentials to view Request for Quotation form on portal.

@@ -46,9 +43,12 @@

-__Step 4:__ On submission, system will create supplier quotation(draft mode) against the supplier. User has to review the supplier quotation
- and submit it.
-
-More details:-
+__Step 4:__ On submission, system will create Supplier Quotation (draft mode) against the supplier. The user has to review the Supplier Quotation
+ and submit it. When all items from the Request for Quotation have been quoted by a supplier, the status is updated in the Supplier
+ table of the Request for Quotation.
-
\ No newline at end of file
+#### More details
+
+If a supplier indicates that they will not provide a quotation for the item, this can be indicated in the RFQ document by checking the 'No Quote' box after the Request for Quotation has been submitted.
+
+
diff --git a/erpnext/docs/user/manual/es/index.txt b/erpnext/docs/user/manual/es/index.txt
index d19ef97..feb68a2 100644
--- a/erpnext/docs/user/manual/es/index.txt
+++ b/erpnext/docs/user/manual/es/index.txt
@@ -1,2 +1,3 @@
introduction
accounts
+schools
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/__init__.py b/erpnext/docs/user/manual/es/schools/Assessment/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md
new file mode 100644
index 0000000..92f338f
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_criteria.md
@@ -0,0 +1,13 @@
+#Criterios de Evaluación
+
+Criterios de evaluación es el parámetro basado en el que se evalúa el estudiante.
+
+<img class="screenshot" alt="Assessment Criteria" src="/docs/assets/img/schools/assessment/assessment-criteria.png">
+
+Después de la evaluación para un curso, las calificaciones obtenidas se ingresan en base a los criterios de evaluación. Por ejemplo, si la evaluación se llevó a cabo para el tema de la ciencia, entonces usted puede evaluar al estudiante en ciencia en varios criterios como la escritura, prácticas, presentación, etc
+
+Los Criterios de Evaluación se usan al programar el Plan de Evaluación para el Grupo de Estudiantes y el Curso.
+
+<img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/schools/assessment/assessment-plan-criteria.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md
new file mode 100644
index 0000000..7102888
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_group.md
@@ -0,0 +1,13 @@
+#Grupo de Evaluación
+
+La estructura del grupo de evaluación es un maestro donde se puede definir la jerarquía para el examen realizado en su instituto de educación.
+
+Por ejemplo, si realiza dos evaluaciones en un año académico, configure el Grupo de evaluación de la siguiente manera.
+
+<img class="screenshot" alt="Assessment Group Term" src="/docs/assets/img/schools/assessment/assessment-group-term.png">
+
+En la misma línea, también puede definir varios Grupo de Evaluación basado sobre la evaluación llevada a cabo en su instituto.
+
+<img class="screenshot" alt="Assessment Group Term" src="/docs/assets/img/schools/assessment/assessment-group-details.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md
new file mode 100644
index 0000000..9d66c21
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_plan.md
@@ -0,0 +1,19 @@
+#Plan de Evaluación
+
+Para programar una evaluación/examinación para un Grupo de Estudiantes, para un curso específico, crea un Plan de Evaluación. En el plan de evaluación, puedes capturar detalles como:
+
+1. Escala de Calificaciones basada en qué calificaciones serán asignadas a los estudiantes.
+
+2. Fecha de planificación de la Evaluación
+
+3. Aula donde se va a llevar a cabo la evaluación
+
+4. Examinador y Supervisor
+
+<img class="screenshot" alt="Assessment Plan Details" src="/docs/assets/img/schools/assessment/assessment-plan-details.png">
+
+5. Los Criterios de Evaluación son la lista de criterios basados ​​en que cada estudiante en será evaluado y los grados serán asignados.
+
+<img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/schools/assessment/assessment-plan-criteria.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md
new file mode 100644
index 0000000..2d94097
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result.md
@@ -0,0 +1,7 @@
+#Resultados de Evaluación
+
+El resultado de la evaluación es un registro de las calificaciones obtenidas por el estudiante para una evaluación específica. El resultado de la evaluación se crea en el backend en base a los puntos en [Herramienta de Resultados de Evaluación](/docs/user/manual/es/schools/assessment/assessment_result_tool.html).
+
+<img class="screenshot" alt="Assessment Result" src="/docs/assets/img/schools/assessment/assessment-result.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md
new file mode 100644
index 0000000..70e4ee3
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/assessment_result_tool.md
@@ -0,0 +1,10 @@
+#Herramienta de resultados de la evaluación
+
+
+Herramienta de resultados de evaluación le ayuda a ingresar las calificaciones obtenidas por los estudiantes para un curso específico. En esta herramienta, basada en el plan de evaluación, todos los estudiantes van a ser filtrados dentro de la herramienta de resultados de la evaluación. También, Columnas para los Criterios de Evaluación serán donde las calificaciones ganadas pueden ser ingresadas para cada Estudiante.
+
+<img class="screenshot" alt="Assessment Result Tool" src="/docs/assets/img/schools/assessment/assessment-result-tool.png">
+
+A medida que vaya introduciendo las notas para un Estudiante y cambie al siguiente alumno, en el backend, el registro de Resultados del Estudiante se creará automáticamente para ese Estudiante.
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md b/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md
new file mode 100644
index 0000000..9107f92
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/grading_scale.md
@@ -0,0 +1,7 @@
+#Escala de calificación
+
+En la escala de calificación, puedes definir varios grados y límites para los estudiantes. Basado en la calificación obtenida por el estudiante en la evaluación, el grado (Grade) será asignado.
+
+<img class="screenshot" alt="Grading Scale" src="/docs/assets/img/schools/assessment/grading-scale.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/index.md b/erpnext/docs/user/manual/es/schools/Assessment/index.md
new file mode 100644
index 0000000..5b4c35a
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/index.md
@@ -0,0 +1,19 @@
+#Evaluación
+
+Cada instituto de educación organiza evaluaciones / examenes para evaluar el progreso de sus estudiantes. En ERPNext, puedes gestionar completamente el proceso de evaluación para sus cuentas en ERPNext.
+
+
+
+A continuación se muestra el orden en el que debe configurar los masters en el módulo de evaluación.
+
+1. Criterio de Evaluación
+2. Grupo de Evaluación
+3. Escala de Evaluación
+
+Una vez tengas definido los Grupos de Estudiantes y Cursos, puedes programar una evaluación / examinación creando un Plan de Evaluación.
+
+Basado en el rendimiento del estudiante en la evaluación, puedes ingresar los resultados de la evaluación por un estudiante. Puedes ingresar todos los registros de los estudiantes usando la herramienta de resultados de evaluación. En esta herramienta, en la selecció del Plan de Evaluación, todos los estudiantes (De un grupo de estudiantes) van a ser buscados y mostrados. Puedes rápidamente ingresar los puntos obtenidos por cada estudiante en cada uno de los Criterios de Evaluación en una sola linea.
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/Assessment/index.txt b/erpnext/docs/user/manual/es/schools/Assessment/index.txt
new file mode 100644
index 0000000..61d744c
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/Assessment/index.txt
@@ -0,0 +1,6 @@
+assessment_criteria
+assessment_group
+grading_scale
+assessment_plan
+assessment_result_tool
+assessment_result
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/__init__.py b/erpnext/docs/user/manual/es/schools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/admission/__init__.py b/erpnext/docs/user/manual/es/schools/admission/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/admission/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/admission/index.md b/erpnext/docs/user/manual/es/schools/admission/index.md
new file mode 100644
index 0000000..df44679
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/admission/index.md
@@ -0,0 +1,7 @@
+# Admisiones
+
+Esta sección contiene los documentos relacionados a adminisiones de estudiantes.
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/admission/index.txt b/erpnext/docs/user/manual/es/schools/admission/index.txt
new file mode 100644
index 0000000..ec9e768
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/admission/index.txt
@@ -0,0 +1,2 @@
+student-applicant
+program-enrollment
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md b/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md
new file mode 100644
index 0000000..34e8996
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/admission/program-enrollment.md
@@ -0,0 +1,7 @@
+# Inscripción al Programa
+
+Este formulario te permite inscribir un estudiante a un programa. Un estudiante puede ser inscrito en multiples programas.
+
+<img class="screenshot" alt="Student Applicant Enrollment" src="/docs/assets/img/schools/admission/program-enrollment.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/admission/student-applicant.md b/erpnext/docs/user/manual/es/schools/admission/student-applicant.md
new file mode 100644
index 0000000..33d8dd7
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/admission/student-applicant.md
@@ -0,0 +1,28 @@
+# Aplicación de Estudiante
+
+Un registro de Aplicación de Estudiante necesita ser creado cuando un estudiante aplica para un programa en su institución.
+Puedes Aprobar o Rechazar una aplicación de estudiante. Aceptando una aplicación de un estudiante puedes agregarlos al master de estudiantes.
+
+<img class="screenshot" alt="Student Applicant" src="/docs/assets/img/schools/admission/student-applicant.png">
+
+### Estados de la Aplicación
+
+- Por defecto cuando una aplicación de estudiante es creada en el sistema, el estado de la aplicación es seteado a 'Applied'
+
+- Puedes actualizar el estado a 'Approved' una vez apruebas la aplicación de estudiante para unirse a su institución
+
+- Cuando el estado de la aplicación es actualizada a 'Approved', el botón de 'Inscribir' debería aparecer.
+ Puedes crear un registro de estudiante usando la aplicación de estudiante e inscribirlos a un programa al presionar el botón de Inscribir
+
+- Una vez el estudiante es creado usando la aplicación de estudiante, el sistema va a setear el estado del documento de aplicación a 'Admitted'
+ y no te va a permiter cambiar el estado de la aplicación a menos que el estudiante sea eliminado
+
+### Registro de Estudiante
+
+
+Una vez aprobada una Aplicación de Estudiante, puedes inscribirlo a un programa. Cuando le das click al butón 'Inscribir',
+el sistema creará un estudiante usando esa aplicación y le va a redireccionar a el [Formulario de Inscripción al Programa](/docs/user/manual/es/schools/student/program-enrollment.html).
+
+<img class="screenshot" alt="Student Applicant Enrollment" src="/docs/assets/img/schools/admission/student-applicant-enroll.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/fees/__init__.py b/erpnext/docs/user/manual/es/schools/fees/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/fees/fee-category.md b/erpnext/docs/user/manual/es/schools/fees/fee-category.md
new file mode 100644
index 0000000..c05b3f5
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/fee-category.md
@@ -0,0 +1,7 @@
+# Categoría de Cuota
+
+Todos los tipos diferentes de cuotas que se cobran
+
+<img class="screenshot" alt="Fees Category" src="/docs/assets/img/schools/fees/fee-category.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/fees/fee-structure.md b/erpnext/docs/user/manual/es/schools/fees/fee-structure.md
new file mode 100644
index 0000000..1f621d7
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/fee-structure.md
@@ -0,0 +1,7 @@
+# Estructura de Cuota
+
+Una Estructura de Cuota es una plantilla que puede ser usada cuando se hacen registros de cuotas.
+
+<img class="screenshot" alt="Fees Structure" src="/docs/assets/img/schools/fees/fee-structure.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/fees/fees.md b/erpnext/docs/user/manual/es/schools/fees/fees.md
new file mode 100644
index 0000000..d6b74dc
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/fees.md
@@ -0,0 +1,8 @@
+# Cuotas
+
+Mantiene un registro de todas las cuotas recolectadas de los estudiantes.
+La [Estructura de Cuota](/docs/user/manual/es/schools/fees/fee-structure.html) es seleccionada basada en el programa seleccionada y los Términos Académicos.
+
+<img class="screenshot" alt="Fees" src="/docs/assets/img/schools/fees/fees.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/fees/index.md b/erpnext/docs/user/manual/es/schools/fees/index.md
new file mode 100644
index 0000000..e883813
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/index.md
@@ -0,0 +1,9 @@
+# Cuota
+
+Esta sección contiene todos los documentos relacionado a las 'Cuota'
+
+<img class="screenshot" alt="Fees Section" src="/docs/assets/img/schools/fees/fees-section.png">
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/fees/index.txt b/erpnext/docs/user/manual/es/schools/fees/index.txt
new file mode 100644
index 0000000..265ac6a
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/fees/index.txt
@@ -0,0 +1,3 @@
+fees
+fee-structure
+fee-category
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/index.md b/erpnext/docs/user/manual/es/schools/index.md
new file mode 100644
index 0000000..a1824dc
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/index.md
@@ -0,0 +1,8 @@
+# Schools
+
+
+Los módulos de School estan diseñados para satisfacer los requerimientos de Escuelas, Colegios e Institutos Educacionales.
+
+<img class="screenshot" alt="Fees Section" src="/docs/assets/img/schools/module.png">
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/index.txt b/erpnext/docs/user/manual/es/schools/index.txt
new file mode 100644
index 0000000..b485fdc
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/index.txt
@@ -0,0 +1,6 @@
+student
+admission
+schedule
+fees
+setup
+assessment
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/schedule/__init__.py b/erpnext/docs/user/manual/es/schools/schedule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md b/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md
new file mode 100644
index 0000000..629c828
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/course-schedule.md
@@ -0,0 +1,25 @@
+# Horario de un Curso
+
+El Horario de Curso es el horario de una sesión de un profesor para un Curso en particular.
+Puedes ver un resumen del horario del curso en la vista de Calendario.
+
+<img class="screenshot" alt="Course Schedule" src="/docs/assets/img/schools/schedule/course-schedule.png">
+
+### Marcando asistencia
+
+Puedes pasar la asistencia para un grupo de estudiantes usando el Horario de Curso.
+
+<img class="screenshot" alt="Course Schedule Attendance" src="/docs/assets/img/schools/schedule/course-schedule-att.png">
+
+- Para hacer la asistencia, expandir la sección de asistencia.
+- Selecciona los estudiantes que estaban presentes para esa sesión.
+- Click en el botón de 'Mark Attendance'. El sistema va a crear los registros de asistencia automáticamente.
+
+### Ver Asistencia
+
+Una vez hayas marcado la asistencia usando la sección de asistencia en el Horario de un Curso, esta sección debería estar oculta.
+Un botón de Ver Asistencia debería aparecer. Click en el botón para ver todos los registros de asistencia creados para ese Horario de Curso.
+
+<img class="screenshot" alt="Course Schedule Attendance" src="/docs/assets/img/schools/schedule/course-schedule-att-1.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/schedule/examination.md b/erpnext/docs/user/manual/es/schools/schedule/examination.md
new file mode 100644
index 0000000..5f85aed
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/examination.md
@@ -0,0 +1,8 @@
+# Examinación
+
+El registro de examinación puede ser usado para hacer el seguimiento del horario de los examenes y los resultados de los mismos.
+
+<img class="screenshot" alt="Examination" src="/docs/assets/img/schools/schedule/examination.png">
+
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/schedule/index.md b/erpnext/docs/user/manual/es/schools/schedule/index.md
new file mode 100644
index 0000000..f9c2c3b
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/index.md
@@ -0,0 +1,7 @@
+# Horario
+
+<img class="screenshot" alt="Schedule Section" src="/docs/assets/img/schools/schedule/schedule-section.png">
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/schedule/index.txt b/erpnext/docs/user/manual/es/schools/schedule/index.txt
new file mode 100644
index 0000000..704ad84
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/index.txt
@@ -0,0 +1,4 @@
+course-schedule
+student-attendance
+scheduling-tool
+examination
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md b/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md
new file mode 100644
index 0000000..070a035
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/scheduling-tool.md
@@ -0,0 +1,23 @@
+# Herramienta para Horarios
+
+Esta herramienta puede ser usada para crear los Horarios de los Cursos.
+
+<img class="screenshot" alt="Scheduling Tool" src="/docs/assets/img/schools/schedule/scheduling-tool.png">
+
+### Creando Horarios de Cursos
+
+- Selecciona el Grupo de Estudiante para el cual vas a establecer el Horario.
+- Seleccione el curso, Aula y un Instructor para el Horario.
+- Específica los rangos de fecha desde-hasta para el horario de los cursos.
+- Especifíca Fecha Inicio y Fecha Final de un curso(Los Horarios de los cursos van a ser creados dentro de este rango de fechas)
+- Espeficíca el dia de la semana en el cual deseas establecer el horario.
+- Presiona el botón de 'Schedule Course'
+- El sistema va a crear la planificación si el Aula y el Instructor estan disponibles y no hay ningún conflicto con el grupo de estudiantes seleccionados con otros horarios para el mismo grupo.
+
+###Reprogramar Horario
+
+- Si deseas reprogramar el horario de un curso, sigue las instrucciones para crear horarios de cursos.
+- Selecciona el checkbox 'Reschedule' y luego da click en el botón 'Schedule Course'.
+- El sistema va a eliminar la progración de horario especifícada para ese curso dentro del rango de fecha seleccionado y crearia la nueva planificación de horario para el curso.
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md b/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md
new file mode 100644
index 0000000..a06c4f2
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/schedule/student-attendance.md
@@ -0,0 +1,7 @@
+# Asistencia de Estudiante
+
+Mantiene los registros de la asistencia del estudiante. Los registros de asistencia pueden ser creados sobre los horarios de los cursos (Course Schedules).
+
+<img class="screenshot" alt="Student Attendance" src="/docs/assets/img/schools/schedule/student-attendance.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/setup/__init__.py b/erpnext/docs/user/manual/es/schools/setup/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/setup/academic-term.md b/erpnext/docs/user/manual/es/schools/setup/academic-term.md
new file mode 100644
index 0000000..83af58a
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/academic-term.md
@@ -0,0 +1,6 @@
+# Término Académico
+
+<img class="screenshot" alt="Academic Term" src="/docs/assets/img/schools/setup/academic-term.png">
+
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/setup/academic-year.md b/erpnext/docs/user/manual/es/schools/setup/academic-year.md
new file mode 100644
index 0000000..56a46f9
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/academic-year.md
@@ -0,0 +1,5 @@
+# Año Académico
+
+<img class="screenshot" alt="Academic Year" src="/docs/assets/img/schools/setup/academic-year.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/setup/course.md b/erpnext/docs/user/manual/es/schools/setup/course.md
new file mode 100644
index 0000000..799f9b4
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/course.md
@@ -0,0 +1,5 @@
+# Curso
+
+<img class="screenshot" alt="Course" src="/docs/assets/img/schools/setup/course.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/setup/index.md b/erpnext/docs/user/manual/es/schools/setup/index.md
new file mode 100644
index 0000000..070db54
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/index.md
@@ -0,0 +1,7 @@
+# Configuración
+
+<img class="screenshot" alt="Setup Section" src="/docs/assets/img/schools/setup/setup-section.png">
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/setup/index.txt b/erpnext/docs/user/manual/es/schools/setup/index.txt
new file mode 100644
index 0000000..fb9ba05
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/index.txt
@@ -0,0 +1,6 @@
+course
+program
+instructor
+room
+academic-term
+academic-year
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/setup/instructor.md b/erpnext/docs/user/manual/es/schools/setup/instructor.md
new file mode 100644
index 0000000..1a4d351
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/instructor.md
@@ -0,0 +1,5 @@
+# Instructor
+
+<img class="screenshot" alt="Instructor" src="/docs/assets/img/schools/setup/instructor.png">
+
+{next}
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/es/schools/setup/program.md b/erpnext/docs/user/manual/es/schools/setup/program.md
new file mode 100644
index 0000000..78895c5
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/program.md
@@ -0,0 +1,5 @@
+# Programa
+
+<img class="screenshot" alt="Program" src="/docs/assets/img/schools/setup/program.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/setup/room.md b/erpnext/docs/user/manual/es/schools/setup/room.md
new file mode 100644
index 0000000..92a4de7
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/setup/room.md
@@ -0,0 +1,6 @@
+# Aula
+
+
+<img class="screenshot" alt="Room" src="/docs/assets/img/schools/setup/room.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/student/__init__.py b/erpnext/docs/user/manual/es/schools/student/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/__init__.py
diff --git a/erpnext/docs/user/manual/es/schools/student/index.md b/erpnext/docs/user/manual/es/schools/student/index.md
new file mode 100644
index 0000000..2d9c6e1
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/index.md
@@ -0,0 +1,7 @@
+# Student
+
+Esta sección contiene los documentos relacionados a los estudiantes.
+
+### Temas
+
+{index}
diff --git a/erpnext/docs/user/manual/es/schools/student/index.txt b/erpnext/docs/user/manual/es/schools/student/index.txt
new file mode 100644
index 0000000..9b31be4
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/index.txt
@@ -0,0 +1,5 @@
+student
+student-log
+student-batch
+student-group
+student-group-creation-tool
diff --git a/erpnext/docs/user/manual/es/schools/student/student-batch.md b/erpnext/docs/user/manual/es/schools/student/student-batch.md
new file mode 100644
index 0000000..4d5a17e
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/student-batch.md
@@ -0,0 +1,8 @@
+# Lote de Estudiantes
+
+
+Un lote de estudiantes es una colección de estudiantes desde los Grupos de Estudiantes.
+
+<img class="screenshot" alt="Student" src="/docs/assets/img/schools/student/student-batch.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md b/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md
new file mode 100644
index 0000000..2102c34
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/student-group-creation-tool.md
@@ -0,0 +1,8 @@
+# Herramienta para creación de Grupo de Estudiantes
+
+Esta herramienta te permite crear grupos de estudiantes. Puedes especificar multiples parámetros para crearlos.
+
+
+<img class="screenshot" alt="Student Group Creation Tool" src="/docs/assets/img/schools/student/student-group-creation-tool.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/student/student-group.md b/erpnext/docs/user/manual/es/schools/student/student-group.md
new file mode 100644
index 0000000..f5841cc
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/student-group.md
@@ -0,0 +1,8 @@
+# Grupo de Estudiante
+
+Un Grupo de Estudiante es una colección de estudiantes tomando el mismo curso. Puedes crear calendarios para los cursos y examinaciones para un Grupo de Estudiante.
+Un Grupo de Estudiante necesita ser creado para cada curso en un año o término académico en particular.
+
+<img class="screenshot" alt="Student Group" src="/docs/assets/img/schools/student/student-group.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/student/student-log.md b/erpnext/docs/user/manual/es/schools/student/student-log.md
new file mode 100644
index 0000000..296b867
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/student-log.md
@@ -0,0 +1,8 @@
+# Bitácora de Estudiante (Log)
+
+Puedes crear una nota de una actividad de un estudiante usando la bitácora de estudiante (log)
+Los registros de bitágora pueden ser categorizadas como 'General', 'Academic', 'Medical' or 'Achievement'
+
+<img class="screenshot" alt="Student" src="/docs/assets/img/schools/student/student-log.png">
+
+{next}
diff --git a/erpnext/docs/user/manual/es/schools/student/student.md b/erpnext/docs/user/manual/es/schools/student/student.md
new file mode 100644
index 0000000..fee84c5
--- /dev/null
+++ b/erpnext/docs/user/manual/es/schools/student/student.md
@@ -0,0 +1,10 @@
+# Estudiante
+
+Un estudiante es una persona que se ha registrado en su institución y has aceptado se solicitud de aplicación.
+El doctype de Estudiante mantiene los detalles personales de los estudiantes.
+
+Puedes ver todo lo relacionado a un estudiante en particular en esta página. Ejemplo: Pagos, Grupo de Estudiante, etc.
+
+<img class="screenshot" alt="Student" src="/docs/assets/img/schools/student/student.png">
+
+{next}
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index fc8d39b..4df83af 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -50,7 +50,8 @@
fixtures = ["Web Form"]
-website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"]
+website_generators = ["Item Group", "Item", "BOM", "Sales Partner",
+ "Job Opening", "Student Admission"]
website_context = {
"favicon": "/assets/erpnext/images/favicon.png",
@@ -83,7 +84,7 @@
{"from_route": "/quotations/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Quotation",
- "parents": [{"label": _("Quotations"), "route": "quotation"}]
+ "parents": [{"label": _("Quotations"), "route": "quotations"}]
}
},
{"from_route": "/shipments", "to_route": "Delivery Note"},
diff --git a/erpnext/hr/doctype/employee/test_employee.js b/erpnext/hr/doctype/employee/test_employee.js
index 64a6b7a..b7f5105 100644
--- a/erpnext/hr/doctype/employee/test_employee.js
+++ b/erpnext/hr/doctype/employee/test_employee.js
@@ -1,39 +1,40 @@
QUnit.module('hr');
QUnit.test("Test: Employee [HR]", function (assert) {
- assert.expect(3);
+ assert.expect(4);
let done = assert.async();
- let today_date = frappe.datetime.nowdate();
-
- frappe.run_serially([
+ // let today_date = frappe.datetime.nowdate();
+ let employee_creation = (name,joining_date,birth_date) => {
+ frappe.run_serially([
// test employee creation
- () => frappe.set_route("List", "Employee", "List"),
- () => frappe.new_doc("Employee"),
- () => frappe.timeout(1),
- () => cur_frm.set_value("employee_name", "Test Employee 1"),
- () => cur_frm.set_value("salutation", "Ms"),
- () => cur_frm.set_value("company", "Test Company"),
- () => cur_frm.set_value("date_of_joining", frappe.datetime.add_months(today_date, -2)), // joined 2 month from now
- () => cur_frm.set_value("date_of_birth", frappe.datetime.add_months(today_date, -240)), // age is 20 years
- () => cur_frm.set_value("employment_type", "Test Employment type"),
- () => cur_frm.set_value("holiday_list", "Test Holiday list"),
- () => cur_frm.set_value("branch", "Test Branch"),
- () => cur_frm.set_value("department", "Test Department"),
- () => cur_frm.set_value("designation", "Test Designation"),
- () => frappe.click_button('Add Row'),
- () => cur_frm.fields_dict.leave_approvers.grid.grid_rows[0].doc.leave_approver="Administrator",
- // save data
- () => cur_frm.save(),
- () => frappe.timeout(1),
- // check name of employee
- () => assert.equal("Test Employee 1", cur_frm.doc.employee_name,
- 'name of employee correctly saved'),
- // check auto filled gender according to salutation
- () => assert.equal("Female", cur_frm.doc.gender,
- 'gender correctly saved as per salutation'),
- // check auto filled retirement date [60 years from DOB]
- () => assert.equal(frappe.datetime.add_months(today_date, 480), cur_frm.doc.date_of_retirement, // 40 years from now
- 'retirement date correctly saved as per date of birth'),
+ () => {
+ frappe.tests.make('Employee', [
+ { employee_name: name},
+ { salutation: 'Mr'},
+ { company: 'Test Company'},
+ { date_of_joining: joining_date},
+ { date_of_birth: birth_date},
+ { employment_type: 'Test Employment Type'},
+ { holiday_list: 'Test Holiday List'},
+ { branch: 'Test Branch'},
+ { department: 'Test Department'},
+ { designation: 'Test Designation'}
+ ]);
+ },
+ () => frappe.timeout(2),
+ () => {
+ assert.ok(cur_frm.get_field('employee_name').value==name,
+ 'Name of an Employee is correctly set');
+ assert.ok(cur_frm.get_field('gender').value=='Male',
+ 'Gender of an Employee is correctly set');
+ },
+ ]);
+ };
+ frappe.run_serially([
+ () => employee_creation('Test Employee 1','2017-04-01','1992-02-02'),
+ () => frappe.timeout(6),
+ () => employee_creation('Test Employee 3','2017-04-01','1992-02-02'),
+ () => frappe.timeout(4),
() => done()
]);
});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
index a71ba0f..3ec8ac0 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
+++ b/erpnext/hr/doctype/employee_attendance_tool/test_employee_attendance_tool.js
@@ -1,10 +1,10 @@
QUnit.module('hr');
QUnit.test("Test: Employee attendance tool [HR]", function (assert) {
- assert.expect(3);
+ assert.expect(2);
let done = assert.async();
let today_date = frappe.datetime.nowdate();
- let date_of_attendance = frappe.datetime.add_days(today_date, -1); // previous day
+ let date_of_attendance = frappe.datetime.add_days(today_date, -2); // previous day
frappe.run_serially([
// create employee
@@ -38,11 +38,9 @@
() => frappe.set_route("List", "Attendance", "List"),
() => frappe.timeout(1),
() => {
- assert.deepEqual(["Test Employee 2", "Test Employee 1"], [cur_list.data[0].employee_name, cur_list.data[1].employee_name],
- "marked attendance correctly saved for both employee");
let marked_attendance = cur_list.data.filter(d => d.attendance_date == date_of_attendance);
- assert.equal(marked_attendance.length, 2,
- 'both the attendance are marked for correct date');
+ assert.equal(marked_attendance.length, 3,
+ 'all the attendance are marked for correct date');
},
() => done()
]);
diff --git a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
index e71ff6e..5133c0c 100644
--- a/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
+++ b/erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
@@ -28,8 +28,8 @@
() => frappe.timeout(1),
() => {
let leave_allocated = cur_list.data.filter(d => d.leave_type == "Test Leave type");
- assert.equal(2, leave_allocated.length,
- 'leave allocation successfully done for both the employee');
+ assert.equal(3, leave_allocated.length,
+ 'leave allocation successfully done for all the employees');
},
() => done()
]);
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 1cee022..1d17613 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -11,6 +11,7 @@
from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.utilities.transaction_base import TransactionBase
+from frappe.utils.background_jobs import enqueue
class SalarySlip(TransactionBase):
def autoname(self):
@@ -75,13 +76,15 @@
def eval_condition_and_formula(self, d, data):
try:
- if d.condition:
- if not frappe.safe_eval(d.condition, None, data):
+ condition = d.condition.strip() if d.condition else None
+ if condition:
+ if not frappe.safe_eval(condition, None, data):
return None
amount = d.amount
if d.amount_based_on_formula:
- if d.formula:
- amount = frappe.safe_eval(d.formula, None, data)
+ formula = d.formula.strip() if d.formula else None
+ if formula:
+ amount = frappe.safe_eval(formula, None, data)
if amount:
data[d.abbr] = amount
@@ -394,9 +397,15 @@
receiver = frappe.db.get_value("Employee", self.employee, "prefered_email")
if receiver:
- subj = 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date)
- frappe.sendmail([receiver], subject=subj, message = _("Please see attachment"),
- attachments=[frappe.attach_print(self.doctype, self.name, file_name=self.name)], reference_doctype= self.doctype, reference_name= self.name)
+ email_args = {
+ "recipients": [receiver],
+ "message": _("Please see attachment"),
+ "subject": 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date),
+ "attachments": [frappe.attach_print(self.doctype, self.name, file_name=self.name)],
+ "reference_doctype": self.doctype,
+ "reference_name": self.name
+ }
+ enqueue(method=frappe.sendmail, queue='short', timeout=300, async=True, **email_args)
else:
msgprint(_("{0}: Employee email not found, hence email not sent").format(self.employee_name))
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.js b/erpnext/hr/doctype/salary_slip/test_salary_slip.js
new file mode 100644
index 0000000..a49c973
--- /dev/null
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.js
@@ -0,0 +1,49 @@
+QUnit.test("test salary slip", function(assert) {
+ assert.expect(6);
+ let done = assert.async();
+ let employee_name;
+
+ let salary_slip = (ename) => {
+ frappe.run_serially([
+ () => frappe.db.get_value('Employee', {'employee_name': ename}, 'name'),
+ (r) => {
+ employee_name = r.message.name;
+ },
+ () => {
+ // Creating a salary slip for a employee
+ frappe.tests.make('Salary Slip', [
+ { employee: employee_name}
+ ]);
+ },
+ () => frappe.timeout(1),
+ () => {
+ // To check if all the calculations are correctly done
+ if(ename === 'Test Employee 1')
+ {
+ assert.ok(cur_frm.doc.gross_pay==24000,
+ 'Gross amount for first employee is correctly calculated');
+ assert.ok(cur_frm.doc.total_deduction==4800,
+ 'Deduction amount for first employee is correctly calculated');
+ assert.ok(cur_frm.doc.net_pay==19200,
+ 'Net amount for first employee is correctly calculated');
+ }
+ if(ename === 'Test Employee 3')
+ {
+ assert.ok(cur_frm.doc.gross_pay==28800,
+ 'Gross amount for second employee is correctly calculated');
+ assert.ok(cur_frm.doc.total_deduction==5760,
+ 'Deduction amount for second employee is correctly calculated');
+ assert.ok(cur_frm.doc.net_pay==23040,
+ 'Net amount for second employee is correctly calculated');
+ }
+ },
+ ]);
+ };
+ frappe.run_serially([
+ () => salary_slip('Test Employee 1'),
+ () => frappe.timeout(6),
+ () => salary_slip('Test Employee 3'),
+ () => frappe.timeout(3),
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index dc1c04d..d8b56e3 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -17,6 +17,7 @@
for e in self.get('employees'):
set_employee_name(e)
self.validate_date()
+ self.strip_condition_and_formula_fields()
def get_ss_values(self,employee):
basic_info = frappe.db.sql("""select bank_name, bank_ac_no
@@ -62,6 +63,16 @@
frappe.throw(_("Active Salary Structure {0} found for employee {1} for the given dates")
.format(st_name[0][0], employee.employee))
+ def strip_condition_and_formula_fields(self):
+ # remove whitespaces from condition and formula fields
+ for row in self.earnings:
+ row.condition = row.condition.strip() if row.condition else ""
+ row.formula = row.formula.strip() if row.formula else ""
+
+ for row in self.deductions:
+ row.condition = row.condition.strip() if row.condition else ""
+ row.formula = row.formula.strip() if row.formula else ""
+
@frappe.whitelist()
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None):
def postprocess(source, target):
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.js b/erpnext/hr/doctype/salary_structure/test_salary_structure.js
new file mode 100644
index 0000000..0740357
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.js
@@ -0,0 +1,81 @@
+QUnit.test("test Salary Structure", function(assert) {
+ assert.expect(6);
+ let done = assert.async();
+ let employee_name1;
+
+ let salary_structure = (ename1,ename2) => {
+ frappe.run_serially([
+ () => frappe.db.get_value('Employee', {'employee_name': ename1}, 'name'),
+ (r) => {
+ employee_name1 = r.message.name;
+ },
+ () => frappe.db.get_value('Employee', {'employee_name': ename2}, 'name'),
+ (r) => {
+ // Creating Salary Structure for employees);
+ frappe.tests.make('Salary Structure', [
+ { company: 'Test Company'},
+ { payroll_frequency: 'Monthly'},
+ { employees: [
+ [
+ {employee: employee_name1},
+ {from_date: '2017-07-01'},
+ {base: 25000}
+ ],
+ [
+ {employee: r.message.name},
+ {from_date: '2017-07-01'},
+ {base: 30000}
+ ]
+ ]},
+ { earnings: [
+ [
+ {salary_component: 'Basic'},
+ {formula: 'base * .80'}
+ ],
+ [
+ {salary_component: 'Leave Encashment'},
+ {formula: 'B * .20'}
+ ]
+ ]},
+ { deductions: [
+ [
+ {salary_component: 'Income Tax'},
+ {formula: '(B+LE) * .20'}
+ ]
+ ]},
+ { payment_account: 'CASH - TC'},
+ ]);
+ },
+ () => frappe.timeout(9),
+ () => cur_dialog.set_value('value','Test Salary Structure'),
+ () => frappe.timeout(2),
+ () => frappe.click_button('Create'),
+ () => {
+ // To check if all the fields are correctly set
+ assert.ok(cur_frm.doc.employees[0].employee_name.includes('Test Employee 1') &&
+ cur_frm.doc.employees[1].employee_name.includes('Test Employee 3'),
+ 'Employee names are correctly set');
+
+ assert.ok(cur_frm.doc.employees[0].base==25000,
+ 'Base value for first employee is correctly set');
+
+ assert.ok(cur_frm.doc.employees[1].base==30000,
+ 'Base value for second employee is correctly set');
+
+ assert.ok(cur_frm.doc.earnings[0].formula.includes('base * .80'),
+ 'Formula for earnings as Basic is correctly set');
+
+ assert.ok(cur_frm.doc.earnings[1].formula.includes('B * .20'),
+ 'Formula for earnings as Leave Encashment is correctly set');
+
+ assert.ok(cur_frm.doc.deductions[0].formula.includes('(B+LE) * .20'),
+ 'Formula for deductions as Income Tax is correctly set');
+ },
+ ]);
+ };
+ frappe.run_serially([
+ () => salary_structure('Test Employee 1','Test Employee 3'),
+ () => frappe.timeout(14),
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index 3abdaf3..6b1404c 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -44,6 +44,26 @@
self.assertEquals(sal_slip.get("deductions")[1].amount, 2500)
self.assertEquals(sal_slip.get("total_deduction"), 7500)
self.assertEquals(sal_slip.get("net_pay"), 7500)
+
+ def test_whitespaces_in_formula_conditions_fields(self):
+ make_salary_structure("Salary Structure Sample")
+ salary_structure = frappe.get_doc("Salary Structure", "Salary Structure Sample")
+
+ for row in salary_structure.earnings:
+ row.formula = "\n%s\n\n"%row.formula
+ row.condition = "\n%s\n\n"%row.condition
+
+ for row in salary_structure.deductions:
+ row.formula = "\n%s\n\n"%row.formula
+ row.condition = "\n%s\n\n"%row.condition
+
+ salary_structure.save()
+
+ for row in salary_structure.earnings:
+ self.assertFalse("\n" in row.formula or "\n" in row.condition)
+
+ for row in salary_structure.deductions:
+ self.assertFalse(("\n" in row.formula) or ("\n" in row.condition))
def make_employee(user):
if not frappe.db.get_value("User", user):
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 7dc6f2c..f41087b 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -12,6 +12,7 @@
"editable_grid": 0,
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -44,10 +45,12 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "eval:!doc.__islocal",
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
@@ -64,7 +67,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,
@@ -73,6 +76,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -105,6 +109,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -132,6 +137,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -163,6 +169,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -194,6 +201,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -223,6 +231,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -252,6 +261,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -282,6 +292,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -311,6 +322,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -341,6 +353,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -369,6 +382,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -398,6 +412,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -429,6 +444,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -460,6 +476,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -489,6 +506,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -520,6 +538,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -549,6 +568,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -579,6 +599,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -608,6 +629,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -637,6 +659,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -666,6 +689,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -696,6 +720,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -723,6 +748,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -753,6 +779,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -783,6 +810,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -813,6 +841,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -841,6 +870,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -870,6 +900,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -898,6 +929,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -928,6 +960,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -956,6 +989,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -987,6 +1021,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1017,6 +1052,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1046,6 +1082,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1073,6 +1110,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1102,6 +1140,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1130,6 +1169,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1158,6 +1198,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1186,6 +1227,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1215,6 +1257,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1245,6 +1288,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1274,6 +1318,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1305,6 +1350,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -1335,6 +1381,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1365,6 +1412,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1394,6 +1442,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1425,6 +1474,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1454,6 +1504,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -1485,6 +1536,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1515,6 +1567,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1545,6 +1598,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1586,7 +1640,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-05-20 13:10:59.630780",
+ "modified": "2017-08-11 14:09:30.492628",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
@@ -1642,4 +1696,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
-}
+}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 60caf0f..56af066 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -431,4 +431,7 @@
erpnext.patches.v8_5.update_customer_group_in_POS_profile
erpnext.patches.v8_6.update_timesheet_company_from_PO
erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager
-erpnext.patches.v8_5.remove_project_type_property_setter
\ No newline at end of file
+<<<<<<< HEAD
+erpnext.patches.v8_5.remove_project_type_property_setter
+=======
+>>>>>>> Set write permission to sales manger for permlevel 1 in Quotation doctype
diff --git a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py b/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py
index c2320ec..db4f947 100644
--- a/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py
+++ b/erpnext/patches/v8_6/set_write_permission_for_quotation_for_sales_manager.py
@@ -8,4 +8,4 @@
# Set write permission to permlevel 1 for sales manager role in Quotation doctype
frappe.db.sql(""" update `tabCustom DocPerm` set `tabCustom DocPerm`.write = 1
where `tabCustom DocPerm`.parent = 'Quotation' and `tabCustom DocPerm`.role = 'Sales Manager'
- and `tabCustom DocPerm`.permlevel = 1 """)
+ and `tabCustom DocPerm`.permlevel = 1 """)
\ No newline at end of file
diff --git a/erpnext/public/css/website.css b/erpnext/public/css/website.css
index 0245675..733194a 100644
--- a/erpnext/public/css/website.css
+++ b/erpnext/public/css/website.css
@@ -62,11 +62,6 @@
.featured-products {
border-top: 1px solid #EBEFF2;
}
-.transaction-list-item:hover,
-.transaction-list-item:active,
-.transaction-list-item:focus {
- background-color: #fafbfc;
-}
.transaction-list-item .indicator {
font-weight: inherit;
color: #8D99A6;
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index c798ade..3a8ddb5 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -261,7 +261,7 @@
var item = frappe.get_doc(cdt, cdn);
var update_stock = 0, show_batch_dialog = 0;
- if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
+ if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
update_stock = cint(me.frm.doc.update_stock);
show_batch_dialog = update_stock;
diff --git a/erpnext/public/less/website.less b/erpnext/public/less/website.less
index 79a89a0..25172a1 100644
--- a/erpnext/public/less/website.less
+++ b/erpnext/public/less/website.less
@@ -76,12 +76,6 @@
}
.transaction-list-item {
- &:hover,
- &:active,
- &:focus {
- background-color: @light-bg;
- }
-
.indicator {
font-weight: inherit;
color: @text-muted;
diff --git a/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js b/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js
index b983afd..5a6f6df 100644
--- a/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js
+++ b/erpnext/schools/doctype/student_applicant/tests/test_student_applicant_options.js
@@ -86,8 +86,9 @@
// Enrolling the Student into a Program
() => {$('.form-documents .row:nth-child(1) .col-xs-6:nth-child(1) .octicon-plus').click();},
() => frappe.timeout(1),
+ () => cur_frm.set_value('program', 'Standard Test'),
+ () => frappe.timeout(1),
() => {
- cur_frm.set_value('program', 'Standard Test');
cur_frm.set_value('student_category', 'Reservation');
cur_frm.set_value('student_batch_name', 'A');
cur_frm.set_value('academic_year', '2016-17');
diff --git a/erpnext/schools/doctype/student_group/test_student_group.js b/erpnext/schools/doctype/student_group/test_student_group.js
index df72ae9..634ad18 100644
--- a/erpnext/schools/doctype/student_group/test_student_group.js
+++ b/erpnext/schools/doctype/student_group/test_student_group.js
@@ -57,7 +57,7 @@
() => frappe.set_route("Form", ('Student Group/' + index)),
() => frappe.timeout(0.5),
() => frappe.tests.click_button('Get Students'),
- () => frappe.timeout(0.5),
+ () => frappe.timeout(1),
() => {
assert.equal(cur_frm.doc.students.length, 5, 'Successfully fetched list of students');
},
diff --git a/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js b/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js
index 366822e..a8567b3 100644
--- a/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js
+++ b/erpnext/schools/doctype/student_group_creation_tool/test_student_group_creation_tool.js
@@ -19,9 +19,10 @@
cur_frm.set_value("program", "Standard Test");
frappe.tests.click_button('Get Courses');
},
- () => frappe.timeout(0.5),
+ () => frappe.timeout(1),
() => {
- assert.equal(cur_frm.doc.courses.length, 4, 'Successfully created groups using the tool');
+ let no_of_courses = $('input.grid-row-check.pull-left').size() - 1;
+ assert.equal(cur_frm.doc.courses.length, no_of_courses, 'Successfully created groups using the tool');
},
() => {
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index c149742..3e5e52f 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -38,10 +38,6 @@
var me = this;
- if (doc.valid_till && frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) < 0) {
- this.frm.set_intro(__("Validity period of this quotation has ended"));
- }
-
if (doc.__islocal) {
this.frm.set_value('valid_till', frappe.datetime.add_months(doc.transaction_date, 1))
}
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index ab3e8c9..0afc5ca 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -2588,6 +2588,37 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "opportunity",
+ "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": "Opportunity",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Opportunity",
+ "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
}
],
"has_web_view": 0,
@@ -2602,7 +2633,7 @@
"istable": 0,
"max_attachments": 1,
"menu_index": 0,
- "modified": "2017-08-09 01:16:21.104135",
+ "modified": "2017-08-09 06:35:48.691648",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 5eb8b06..f3ebe81 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -14,6 +14,14 @@
}
class Quotation(SellingController):
+ def set_indicator(self):
+ if self.docstatus==1:
+ self.indicator_color = 'blue'
+ self.indicator_title = 'Submitted'
+ if self.valid_till and getdate(self.valid_till) < getdate(nowdate()):
+ self.indicator_color = 'darkgrey'
+ self.indicator_title = 'Expired'
+
def validate(self):
super(Quotation, self).validate()
self.set_status()
@@ -49,9 +57,18 @@
def update_opportunity(self):
for opportunity in list(set([d.prevdoc_docname for d in self.get("items")])):
if opportunity:
- opp = frappe.get_doc("Opportunity", opportunity)
- opp.status = None
- opp.set_status(update=True)
+ self.update_opportunity_status(opportunity)
+
+ if self.opportunity:
+ self.update_opportunity_status()
+
+ def update_opportunity_status(self, opportunity=None):
+ if not opportunity:
+ opportunity = self.opportunity
+
+ opp = frappe.get_doc("Opportunity", opportunity)
+ opp.status = None
+ opp.set_status(update=True)
def declare_order_lost(self, arg):
if not self.has_sales_order():
diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js
index 204ace1..8baf9b2 100644
--- a/erpnext/selling/doctype/quotation/quotation_list.js
+++ b/erpnext/selling/doctype/quotation/quotation_list.js
@@ -1,9 +1,13 @@
frappe.listview_settings['Quotation'] = {
add_fields: ["customer_name", "base_grand_total", "status",
- "company", "currency"],
+ "company", "currency", 'valid_till'],
get_indicator: function(doc) {
if(doc.status==="Submitted") {
- return [__("Submitted"), "blue", "status,=,Submitted"];
+ if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) {
+ return [__("Expired"), "darkgrey", "valid_till,<," + frappe.datetime.nowdate()];
+ } else {
+ return [__("Submitted"), "blue", "status,=,Submitted"];
+ }
} else if(doc.status==="Ordered") {
return [__("Ordered"), "green", "status,=,Ordered"];
} else if(doc.status==="Lost") {
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index f4cc355..41d7e3c 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -107,9 +107,9 @@
this.cur_frm.doc.items.forEach((item) => {
if (item.item_code === item_code) {
frappe.model.set_value(item.doctype, item.name, 'qty', item.qty + qty)
- .then(() => {
- this.cart.add_item(item);
- })
+ .then(() => {
+ this.cart.add_item(item);
+ })
// update cart
}
});
@@ -767,9 +767,9 @@
if (frappe.flags[fieldname]) {
const value = this.dialog.get_value(fieldname);
this.frm.set_value(fieldname, value)
- .then(() => {
- callback()
- })
+ .then(() => {
+ callback()
+ })
}
frappe.flags[fieldname] = true;
@@ -780,10 +780,10 @@
$.each(this.frm.doc.payments, function(i, data) {
if (__(data.mode_of_payment) == __(fieldname)) {
frappe.model.set_value('Sales Invoice Payment', data.name, 'amount', value)
- .then(() => {
- me.update_change_amount();
- me.update_write_off_amount();
- })
+ .then(() => {
+ me.update_change_amount();
+ me.update_write_off_amount();
+ })
}
});
}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.js b/erpnext/stock/doctype/delivery_note/test_delivery_note.js
new file mode 100644
index 0000000..482f892
--- /dev/null
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.js
@@ -0,0 +1,37 @@
+QUnit.module('Stock');
+
+QUnit.test("test delivery note", function(assert) {
+ assert.expect(2);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Delivery Note', [
+ {customer:'Test Customer 1'},
+ {items: [
+ [
+ {'item_code': 'Test Product 1'},
+ {'qty': 5},
+ ]
+ ]},
+ {shipping_address_name: 'Test1-Shipping'},
+ {contact_person: 'Contact 1-Test Customer 1'},
+ {taxes_and_charges: 'TEST In State GST'},
+ {tc_name: 'Test Term 1'},
+ {transporter_name:'TEST TRANSPORT'},
+ {lr_no:'MH-04-FG 1111'}
+ ]);
+ },
+ () => cur_frm.save(),
+ () => {
+ // get_item_details
+ assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
+ assert.ok(cur_frm.doc.grand_total==590, " Grand Total correct");
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
+
diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
index 61c19f0..4140bbc 100644
--- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
+++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
@@ -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,
@@ -22,6 +24,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
@@ -41,6 +44,7 @@
"width": "100px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -51,6 +55,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
@@ -72,6 +77,7 @@
"width": "120px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -82,6 +88,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Receipt Document Type",
@@ -101,6 +108,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -111,6 +119,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Receipt Document",
@@ -130,6 +139,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -140,6 +150,7 @@
"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,
@@ -156,6 +167,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -166,6 +178,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Qty",
@@ -183,6 +196,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -193,6 +207,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Rate",
@@ -211,6 +226,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -221,6 +237,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
@@ -241,6 +258,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -251,6 +269,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Applicable Charges",
@@ -269,6 +288,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -279,6 +299,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Receipt Item",
@@ -294,19 +315,79 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "cost_center",
+ "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": "Cost Center",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Cost Center",
+ "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": 1,
"image_view": 0,
"in_create": 0,
- "in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-12-20 04:50:19.785273",
+ "modified": "2017-08-08 19:51:36.501765",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Item",
@@ -315,5 +396,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "track_changes": 0,
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index a2d3606..f51652c 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -2,7 +2,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe import _
from frappe.utils import flt
from frappe.model.meta import get_field_precision
@@ -15,7 +15,7 @@
for pr in self.get("purchase_receipts"):
if pr.receipt_document_type and pr.receipt_document:
pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description,
- pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name
+ pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name, pr_item.cost_center
from `tab{doctype} Item` pr_item where parent = %s
and exists(select name from tabItem where name = pr_item.item_code and is_stock_item = 1)
""".format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True)
@@ -26,6 +26,8 @@
item.description = d.description
item.qty = d.qty
item.rate = d.base_rate
+ item.cost_center = d.cost_center or \
+ erpnext.get_default_cost_center(self.company)
item.amount = d.base_amount
item.receipt_document_type = pr.receipt_document_type
item.receipt_document = pr.receipt_document
@@ -61,6 +63,10 @@
frappe.throw(_("Item Row {idx}: {doctype} {docname} does not exist in above '{doctype}' table")
.format(idx=item.idx, doctype=item.receipt_document_type, docname=item.receipt_document))
+ if not item.cost_center:
+ frappe.throw(_("Row {0}: Cost center is required for an item {1}")
+ .format(item.idx, item.item_code))
+
def set_total_taxes_and_charges(self):
self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
new file mode 100644
index 0000000..828738e
--- /dev/null
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
@@ -0,0 +1,32 @@
+QUnit.module('Stock');
+
+QUnit.test("test material request", function(assert) {
+ assert.expect(2);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Stock Entry', [
+ {purpose:'Material Receipt'},
+ {to_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
+ {items: [
+ [
+ {'item_code': 'Test Product 1'},
+ {'qty': 5},
+ ]
+ ]},
+ ]);
+ },
+ () => cur_frm.save(),
+ () => frappe.click_button('Update Rate and Availability'),
+ () => {
+ // get_item_details
+ assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
+ assert.ok(cur_frm.doc.total_incoming_value==500, " Incoming Value correct");
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
new file mode 100644
index 0000000..cdeb4ab
--- /dev/null
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
@@ -0,0 +1,34 @@
+QUnit.module('Stock');
+
+QUnit.test("test material request", function(assert) {
+ assert.expect(3);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Stock Entry', [
+ {purpose:'Material Transfer'},
+ {from_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
+ {to_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
+ {items: [
+ [
+ {'item_code': 'Test Product 1'},
+ {'qty': 5},
+ ]
+ ]},
+ ]);
+ },
+ () => cur_frm.save(),
+ () => frappe.click_button('Update Rate and Availability'),
+ () => {
+ // get_item_details
+ assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
+ assert.ok(cur_frm.doc.total_outgoing_value==500, " Outgoing Value correct");
+ assert.ok(cur_frm.doc.total_incoming_value==500, " Incoming Value correct");
+ },
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.3),
+ () => done()
+ ]);
+});
+
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index ce357db..e95f5ca 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -38,6 +38,12 @@
"options": "Item"
},
{
+ "fieldname":"item_group",
+ "label": __("Item Group"),
+ "fieldtype": "Link",
+ "options": "Item Group"
+ },
+ {
"fieldname":"batch_no",
"label": __("Batch No"),
"fieldtype": "Link",
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 916adff..6820d8f 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -65,7 +65,7 @@
def get_item_details(filters):
item_details = {}
for item in frappe.db.sql("""select name, item_name, description, item_group,
- brand, stock_uom from `tabItem` {item_conditions}"""\
+ brand, stock_uom from `tabItem` item {item_conditions}"""\
.format(item_conditions=get_item_conditions(filters)), filters, as_dict=1):
item_details.setdefault(item.name, item)
@@ -74,9 +74,11 @@
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
- conditions.append("name=%(item_code)s")
+ conditions.append("item.name=%(item_code)s")
if filters.get("brand"):
- conditions.append("brand=%(brand)s")
+ conditions.append("item.brand=%(brand)s")
+ if filters.get("item_group"):
+ conditions.append(get_item_group_condition(filters.get("item_group")))
return "where {}".format(" and ".join(conditions)) if conditions else ""
@@ -84,7 +86,7 @@
conditions = []
item_conditions=get_item_conditions(filters)
if item_conditions:
- conditions.append("""item_code in (select name from tabItem
+ conditions.append("""sle.item_code in (select item.name from tabItem item
{item_conditions})""".format(item_conditions=item_conditions))
if filters.get("warehouse"):
conditions.append(get_warehouse_condition(filters.get("warehouse")))
@@ -122,3 +124,12 @@
warehouse_details.rgt)
return ''
+
+def get_item_group_condition(item_group):
+ item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
+ if item_group_details:
+ return "item.item_group in (select ig.name from `tabItem Group` ig \
+ where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"%(item_group_details.lft,
+ item_group_details.rgt)
+
+ return ''
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 471576f..462d77d 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -1,22 +1,22 @@
{% if doc.taxes %}
<div class="row tax-net-total-row">
- <div class="col-xs-8 text-right">{{ _("Net Total") }}</div>
- <div class="col-xs-4 text-right">
+ <div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
+ <div class="col-xs-6 text-right">
{{ doc.get_formatted("net_total") }}</div>
</div>
{% endif %}
{% for d in doc.taxes %}
{% if d.base_tax_amount > 0 %}
<div class="row tax-row">
- <div class="col-xs-8 text-right">{{ d.description }}</div>
- <div class="col-xs-4 text-right">
+ <div class="col-xs-6 text-right">{{ d.description }}</div>
+ <div class="col-xs-6 text-right">
{{ d.get_formatted("base_tax_amount") }}</div>
</div>
{% endif %}
{% endfor %}
<div class="row tax-grand-total-row">
- <div class="col-xs-8 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
- <div class="col-xs-4 text-right">
+ <div class="col-xs-6 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
+ <div class="col-xs-6 text-right">
<span class="tax-grand-total bold">
{{ doc.get_formatted("grand_total") }}
</span>
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html
index 10d7605..0e47f7d 100644
--- a/erpnext/templates/includes/transaction_row.html
+++ b/erpnext/templates/includes/transaction_row.html
@@ -1,23 +1,22 @@
<div class="web-list-item transaction-list-item">
<a href="/{{ pathname }}/{{ doc.name }}">
<div class="row">
- <div class="col-sm-5">
- <span class="indicator small {{ doc.indicator_color or "darkgrey" }}">
+ <div class="col-sm-4" style='margin-top: -3px;'>
+ <span class="indicator small {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
{{ doc.name }}</span>
<div class="small text-muted transaction-time"
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}">
- {{ frappe.utils.format_datetime(doc.modified, "medium") }}
+ {{ frappe.utils.global_date_format(doc.modified) }}
</div>
</div>
- <div class="col-sm-4 items-preview ellipsis small">
- {{ doc.items_preview }}
+ <div class="col-sm-5">
+ <div class="small text-muted items-preview ellipsis">
+ {{ doc.items_preview }}
+ </div>
</div>
<div class="col-sm-3 text-right bold">
{{ doc.get_formatted("grand_total") }}
</div>
- <!-- <div class="col-sm-3 text-right">
-
- </div> -->
</div>
</a>
</div>
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 2481808..8a495b1 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -7,21 +7,44 @@
{% block title %}{{ doc.name }}{% endblock %}
-{% block header %}<h1>{{ doc.name }}</h1>{% endblock %}
+{% block header %}
+ <h1>{{ doc.name }}</h1>
+{% endblock %}
+
+{% block header_actions %}
+<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
+{% endblock %}
{% block page_content %}
<div class="row transaction-subheading">
<div class="col-xs-6">
- <span class="indicator {{ doc.indicator_color or "darkgrey" }}">
+ <span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
{{ doc.indicator_title or doc.status or "Submitted" }}
</span>
</div>
<div class="col-xs-6 text-muted text-right small">
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
+ {% if doc.valid_till %}
+ <p>
+ {{ _("Valid Till") }}: {{ frappe.utils.formatdate(doc.valid_till, 'medium') }}
+ </p>
+ {% endif %}
</div>
</div>
+<p class='small' style='padding-top: 15px;'>
+{% if doc.doctype == 'Supplier Quotation' %}
+ <b>{{ doc.supplier_name}}</b>
+{% else %}
+ <b>{{ doc.customer_name}}</b>
+{% endif %}
+{% if doc.contact_display %}
+ <br>
+ {{ doc.contact_display }}
+{% endif %}
+</p>
+
{% if doc._header %}
{{ doc._header }}
{% endif %}
@@ -31,29 +54,29 @@
<!-- items -->
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
- <div class="col-sm-8 col-xs-6 h6 text-uppercase">
+ <div class="col-sm-6 col-xs-6 h6 text-uppercase">
{{ _("Item") }}
</div>
- <div class="col-sm-2 col-xs-3 text-right h6 text-uppercase">
+ <div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Quantity") }}
</div>
- <div class="col-sm-2 col-xs-3 text-right h6 text-uppercase">
+ <div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
{{ _("Amount") }}
</div>
</div>
{% for d in doc.items %}
<div class="row order-items">
- <div class="col-sm-8 col-xs-6">
+ <div class="col-sm-6 col-xs-6">
{{ item_name_and_description(d) }}
</div>
- <div class="col-sm-2 col-xs-3 text-right">
+ <div class="col-sm-3 col-xs-3 text-right">
{{ d.qty }}
{% if d.delivered_qty is defined and d.delivered_qty != None %}
<p class="text-muted small">{{
_("Delivered: {0}").format(d.delivered_qty) }}</p>
{% endif %}
</div>
- <div class="col-sm-2 col-xs-3 text-right">
+ <div class="col-sm-3 col-xs-3 text-right">
{{ d.get_formatted("amount") }}
<p class="text-muted small">{{
_("@ {0}").format(d.get_formatted("rate")) }}</p>
@@ -72,8 +95,8 @@
</div>
<div class="cart-taxes row small">
- <div class="col-sm-8"><!-- empty --></div>
- <div class="col-sm-4">
+ <div class="col-sm-6"><!-- empty --></div>
+ <div class="col-sm-6">
{% if enabled_checkout %}
{% if (doc.doctype=="Sales Order" and doc.per_billed <= 0)
or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0) %}
@@ -86,7 +109,7 @@
{% endif %}
{% endif %}
</div>
-
+
{% if attachments %}
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
@@ -106,4 +129,9 @@
</div>
{% endif %}
</div>
+{% if doc.terms %}
+<div class="terms-and-condition text-muted small">
+ <hr><p>{{ doc.terms }}</p>
+</div>
+{% endif %}
{% endblock %}
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 7551a0f..110eb57 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -15,7 +15,7 @@
context.doc.set_indicator()
if show_attachments():
- context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name)
+ context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name)
context.parents = frappe.form_dict.parents
context.title = frappe.form_dict.name
@@ -28,5 +28,6 @@
frappe.throw(_("Not Permitted"), frappe.PermissionError)
def get_attachments(dt, dn):
- return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"],
- filters = {"attached_to_name": dn, "attached_to_doctype": dt, "is_private":0})
+ return frappe.get_all("File",
+ fields=["name", "file_name", "file_url", "is_private"],
+ filters = {"attached_to_name": dn, "attached_to_doctype": dt, "is_private":0})
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index 0dc19ba..2e71285 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -4,6 +4,7 @@
erpnext/accounts/doctype/account/test_make_tax_account.js
erpnext/accounts/doctype/pricing_rule/test_pricing_rule.js
erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.js
+erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.js
erpnext/accounts/doctype/shipping_rule/test_shipping_rule.js
erpnext/crm/doctype/lead/test_lead.js
erpnext/crm/doctype/opportunity/test_opportunity.js
@@ -51,15 +52,21 @@
erpnext/manufacturing/doctype/production_order/test_production_order.js #long
erpnext/accounts/page/pos/test_pos.js
erpnext/selling/doctype/product_bundle/test_product_bundle.js
+erpnext/stock/doctype/delivery_note/test_delivery_note.js
erpnext/stock/doctype/material_request/tests/test_material_request.js
erpnext/stock/doctype/material_request/tests/test_material_request_type_material_issue.js
erpnext/stock/doctype/material_request/tests/test_material_request_type_material_transfer.js
erpnext/stock/doctype/material_request/tests/test_material_request_type_manufacture.js
+erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js
+erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt.js
+erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer.js
erpnext/schools/doctype/grading_scale/test_grading_scale.js
erpnext/schools/doctype/assessment_criteria_group/test_assessment_criteria_group.js
erpnext/schools/doctype/assessment_criteria/test_assessment_criteria.js
erpnext/schools/doctype/course/test_course.js
erpnext/schools/doctype/program/test_program.js
+erpnext/hr/doctype/salary_structure/test_salary_structure.js
+erpnext/hr/doctype/salary_slip/test_salary_slip.js
erpnext/schools/doctype/guardian/test_guardian.js
erpnext/schools/doctype/student_admission/test_student_admission.js
erpnext/schools/doctype/student_applicant/tests/test_student_applicant_dummy_data.js
@@ -71,10 +78,21 @@
erpnext/schools/doctype/student_leave_application/test_student_leave_application.js
erpnext/schools/doctype/student_attendance_tool/test_student_attendance_tool.js
erpnext/schools/doctype/student_attendance/test_student_attendance.js
-erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue.js
erpnext/schools/doctype/assessment_group/test_assessment_group.js
erpnext/schools/doctype/assessment_plan/test_assessment_plan.js
erpnext/schools/doctype/assessment_result/test_assessment_result.js
erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js
erpnext/buying/doctype/supplier/test_supplier.js
-erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.js
\ No newline at end of file
+erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.js
+erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js
+erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js
+erpnext/accounts/doctype/sales_invoice/test_sales_invoice.js
+erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js
+erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_multi_uom.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_get_items.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_discount_on_grand_total.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_item_wise_discount.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_taxes_and_charges.js
+erpnext/buying/doctype/purchase_order/tests/test_purchase_order_receipt.js
\ No newline at end of file