Merge branch 'develop' into pos-refactor
diff --git a/.travis.yml b/.travis.yml
index 92c15e0..80d979f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -56,7 +56,6 @@
- bench run-tests
- sleep 5
- bench reinstall --yes
- - bench execute erpnext.setup.setup_wizard.utils.complete
- - bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench --verbose run-setup-wizard-ui-test
+ - bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 70ae35a..da3786b 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -4,7 +4,7 @@
import frappe
from erpnext.hooks import regional_overrides
-__version__ = '8.10.2'
+__version__ = '8.11.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 57e83b0..19f4b56 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -1343,6 +1343,67 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1382,7 +1443,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-06-13 14:29:09.794076",
+ "modified": "2017-08-31 11:21:09.442695",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 12e46c4..dc37574 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -644,16 +644,9 @@
if(frm.doc.party) {
var party_amount = frm.doc.payment_type=="Receive" ?
frm.doc.paid_amount : frm.doc.received_amount;
-
- var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
- function(d) { return flt(d.amount) }));
if(frm.doc.total_allocated_amount < party_amount) {
- if(frm.doc.payment_type == "Receive") {
- unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
- } else {
- unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions);
- }
+ unallocated_amount = party_amount - frm.doc.total_allocated_amount;
}
}
frm.set_value("unallocated_amount", unallocated_amount);
@@ -672,11 +665,10 @@
difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount);
}
- $.each(frm.doc.deductions || [], function(i, d) {
- if(d.amount) difference_amount -= flt(d.amount);
- })
+ var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
+ function(d) { return flt(d.amount) }));
- frm.set_value("difference_amount", difference_amount);
+ frm.set_value("difference_amount", difference_amount - total_deductions);
frm.events.hide_unhide_fields(frm);
},
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index a59223d..abf4ac9 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -1665,6 +1665,67 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
@@ -1730,7 +1791,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-06-13 14:29:04.244537",
+ "modified": "2017-08-31 11:20:37.578469",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9832c05..908e58e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -281,13 +281,8 @@
if self.party:
party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
- total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
-
if self.total_allocated_amount < party_amount:
- if self.payment_type == "Receive":
- self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions)
- else:
- self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions)
+ self.unallocated_amount = party_amount - self.total_allocated_amount
def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@@ -302,11 +297,10 @@
else:
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
- for d in self.get("deductions"):
- if d.amount:
- self.difference_amount -= flt(d.amount)
+ total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
- self.difference_amount = flt(self.difference_amount, self.precision("difference_amount"))
+ self.difference_amount = flt(self.difference_amount - total_deductions,
+ self.precision("difference_amount"))
def clear_unallocated_reference_document_rows(self):
self.set("references", self.get("references", {"allocated_amount": ["not in", [0, None, ""]]}))
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 0316cca..60be20d 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -267,3 +267,65 @@
return frappe.db.sql("""select account, debit, credit, against_voucher
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", voucher_no, as_dict=1)
+
+ def test_payment_entry_write_off_difference(self):
+ si = create_sales_invoice()
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+ pe.reference_no = "1"
+ pe.reference_date = "2016-01-01"
+ pe.received_amount = pe.paid_amount = 110
+ pe.insert()
+
+ self.assertEqual(pe.unallocated_amount, 10)
+
+ pe.received_amount = pe.paid_amount = 95
+ pe.append("deductions", {
+ "account": "_Test Write Off - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": 5
+ })
+ pe.save()
+
+ self.assertEqual(pe.unallocated_amount, 0)
+ self.assertEqual(pe.difference_amount, 0)
+
+ pe.submit()
+
+ expected_gle = dict((d[0], d) for d in [
+ ["Debtors - _TC", 0, 100, si.name],
+ ["_Test Cash - _TC", 95, 0, None],
+ ["_Test Write Off - _TC", 5, 0, None]
+ ])
+
+ self.validate_gl_entries(pe.name, expected_gle)
+
+ def test_payment_entry_exchange_gain_loss(self):
+ si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
+ currency="USD", conversion_rate=50)
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
+ pe.reference_no = "1"
+ pe.reference_date = "2016-01-01"
+ pe.target_exchange_rate = 55
+
+ pe.append("deductions", {
+ "account": "_Test Exchange Gain/Loss - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "amount": -500
+ })
+ pe.save()
+
+ self.assertEqual(pe.unallocated_amount, 0)
+ self.assertEqual(pe.difference_amount, 0)
+
+ pe.submit()
+
+ expected_gle = dict((d[0], d) for d in [
+ ["_Test Receivable USD - _TC", 0, 5000, si.name],
+ ["_Test Bank USD - _TC", 5500, 0, None],
+ ["_Test Exchange Gain/Loss - _TC", 0, 500, None],
+ ])
+
+ self.validate_gl_entries(pe.name, expected_gle)
+
+ outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
+ self.assertEqual(outstanding_amount, 0)
diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js
new file mode 100644
index 0000000..7dea76d
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js
@@ -0,0 +1,51 @@
+QUnit.module('Payment Entry');
+
+QUnit.test("test payment entry", function(assert) {
+ assert.expect(6);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Sales Invoice', [
+ {customer: 'Test Customer 1'},
+ {items: [
+ [
+ {'qty': 1},
+ {'rate': 101},
+ {'item_code': 'Test Product 1'},
+ ]
+ ]}
+ ]);
+ },
+ () => cur_frm.save(),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_button('Close'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Make'),
+ () => frappe.click_link('Payment', 1),
+ () => frappe.timeout(2),
+ () => {
+ assert.equal(frappe.get_route()[1], 'Payment Entry',
+ 'made payment entry');
+ assert.equal(cur_frm.doc.party, 'Test Customer 1',
+ 'customer set in payment entry');
+ assert.equal(cur_frm.doc.paid_amount, 101,
+ 'paid amount set in payment entry');
+ assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
+ 'amount allocated against sales invoice');
+ },
+ () => cur_frm.set_value('paid_amount', 100),
+ () => {
+ cur_frm.doc.references[0].allocated_amount = 101;
+ },
+ () => frappe.click_button('Write Off Difference Amount'),
+ () => {
+ assert.equal(cur_frm.doc.difference_amount, 0,
+ 'difference amount is zero');
+ assert.equal(cur_frm.doc.deductions[0].amount, 1,
+ 'Write off amount = 1');
+ },
+ () => done()
+ ]);
+});
diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js
index a4ef0ca..0c76343 100644
--- a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js
@@ -25,5 +25,4 @@
() => frappe.timeout(0.3),
() => done()
]);
-});
-
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js
new file mode 100644
index 0000000..133f136
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry_write_off.js
@@ -0,0 +1,67 @@
+QUnit.module('Payment Entry');
+
+QUnit.test("test payment entry", function(assert) {
+ assert.expect(8);
+ let done = assert.async();
+ frappe.run_serially([
+ () => {
+ return frappe.tests.make('Sales Invoice', [
+ {customer: 'Test Customer 1'},
+ {company: '_Test Company'},
+ {currency: 'INR'},
+ {selling_price_list: '_Test Price List'},
+ {items: [
+ [
+ {'qty': 1},
+ {'item_code': 'Test Product 1'},
+ ]
+ ]}
+ ]);
+ },
+ () => frappe.timeout(1),
+ () => cur_frm.save(),
+ () => frappe.tests.click_button('Submit'),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(1.5),
+ () => frappe.click_button('Close'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Make'),
+ () => frappe.timeout(1),
+ () => frappe.click_link('Payment'),
+ () => frappe.timeout(2),
+ () => cur_frm.set_value("paid_to", "_Test Cash - _TC"),
+ () => frappe.timeout(0.5),
+ () => {
+ assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
+ assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
+ assert.equal(cur_frm.doc.paid_from, 'Debtors - _TC', 'customer account set in payment entry');
+ assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
+ assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
+ 'amount allocated against sales invoice');
+ },
+ () => cur_frm.set_value('paid_amount', 95),
+ () => frappe.timeout(1),
+ () => {
+ frappe.model.set_value("Payment Entry Reference",
+ cur_frm.doc.references[0].name, "allocated_amount", 100);
+ },
+ () => frappe.timeout(.5),
+ () => {
+ assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
+ },
+ () => {
+ frappe.db.set_value("Company", "_Test Company", "write_off_account", "_Test Write Off - _TC");
+ frappe.timeout(1);
+ frappe.db.set_value("Company", "_Test Company",
+ "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC");
+ },
+ () => frappe.timeout(1),
+ () => frappe.click_button('Write Off Difference Amount'),
+ () => frappe.timeout(2),
+ () => {
+ assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
+ assert.equal(cur_frm.doc.deductions[0].amount, 5, 'Write off amount = 5');
+ },
+ () => done()
+ ]);
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index ac5f5dd..b9a7dae 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -46,6 +46,12 @@
cur_frm.add_custom_button(__('Return / Debit Note'),
this.make_debit_note, __("Make"));
}
+
+ if(!doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
}
if(doc.docstatus===0) {
@@ -343,6 +349,7 @@
'Payment Entry': 'Payment'
}
},
+
onload: function(frm) {
$.each(["warehouse", "rejected_warehouse"], function(i, field) {
frm.set_query(field, "items", function() {
@@ -370,5 +377,5 @@
erpnext.buying.get_default_bom(frm);
}
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
- }
-})
+ },
+})
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 8ea48f6..748c24d 100755
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -3352,13 +3352,74 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -3797,7 +3858,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-07-19 13:53:48.673757",
+ "modified": "2017-08-31 11:22:47.074420",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 7ab7901..be388aa 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -667,7 +667,7 @@
if account_type != 'Fixed Asset':
frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx))
- def on_recurring(self, reference_doc):
+ def on_recurring(self, reference_doc, subscription_doc):
self.due_date = None
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
index 6141db5..062a2d2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py
@@ -8,7 +8,8 @@
'Payment Entry': 'reference_name',
'Payment Request': 'reference_name',
'Landed Cost Voucher': 'receipt_document',
- 'Purchase Invoice': 'return_against'
+ 'Purchase Invoice': 'return_against',
+ 'Subscription': 'reference_document'
},
'internal_links': {
'Purchase Order': ['items', 'purchase_order'],
@@ -27,5 +28,9 @@
'label': _('Returns'),
'items': ['Purchase Invoice']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 3454a2e..639620f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -256,10 +256,6 @@
self.assertFalse(frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_type='Purchase Invoice' and reference_name=%s""", pi.name))
- def test_recurring_invoice(self):
- from erpnext.controllers.tests.test_recurring_document import test_recurring_document
- test_recurring_document(self, test_records)
-
def test_total_purchase_cost_for_project(self):
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index ef233c6..11d1825 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -86,7 +86,11 @@
this.make_payment_request, __("Make"));
}
-
+ if(!doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
}
// Show buttons only when pos view is active
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 260c05e..2bb0044 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -4179,13 +4179,74 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -4688,7 +4749,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-07-07 13:05:37.469682",
+ "modified": "2017-08-31 11:23:08.675028",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index a1d66ad..065fb94 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -107,7 +107,7 @@
def on_submit(self):
self.validate_pos_paid_amount()
- if not self.recurring_id:
+ if not self.subscription:
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total, self)
@@ -799,7 +799,7 @@
for dn in set(updated_delivery_notes):
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
- def on_recurring(self, reference_doc):
+ def on_recurring(self, reference_doc, subscription_doc):
for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"):
self.set(fieldname, reference_doc.get(fieldname))
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
index bc9d766..efd18b5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
@@ -8,7 +8,8 @@
'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name',
'Payment Request': 'reference_name',
- 'Sales Invoice': 'return_against'
+ 'Sales Invoice': 'return_against',
+ 'Subscription': 'reference_document',
},
'internal_links': {
'Sales Order': ['items', 'sales_order']
@@ -26,5 +27,9 @@
'label': _('Returns'),
'items': ['Sales Invoice']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 2b0daba..db29563 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -809,10 +809,6 @@
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account`
where reference_name=%s""", si.name))
- def test_recurring_invoice(self):
- from erpnext.controllers.tests.test_recurring_document import test_recurring_document
- test_recurring_document(self, test_records)
-
def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index c533e6b..a51246b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -13,6 +13,7 @@
'Stock Entry': 'Material to Supplier'
}
},
+
onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
@@ -20,8 +21,7 @@
frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
-
- }
+ },
});
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
@@ -86,8 +86,13 @@
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __("Make"));
}
- cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
+ if(!doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
+ cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
}
},
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 09c987f..c4096cc 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -2860,13 +2860,74 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order",
"fieldtype": "Section Break",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -3335,7 +3396,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-07-19 14:03:51.838328",
+ "modified": "2017-08-31 11:22:30.190589",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
index df10a54..d57b0e2 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py
@@ -5,7 +5,8 @@
'fieldname': 'purchase_order',
'non_standard_fieldnames': {
'Journal Entry': 'reference_name',
- 'Payment Entry': 'reference_name'
+ 'Payment Entry': 'reference_name',
+ 'Subscription': 'reference_document'
},
'internal_links': {
'Material Request': ['items', 'material_request'],
@@ -23,11 +24,11 @@
},
{
'label': _('Reference'),
- 'items': ['Material Request', 'Supplier Quotation', 'Project']
+ 'items': ['Material Request', 'Supplier Quotation', 'Project', 'Subscription']
},
{
'label': _('Sub-contracting'),
'items': ['Stock Entry']
- }
+ },
]
}
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.js b/erpnext/buying/doctype/purchase_order/test_purchase_order.js
new file mode 100644
index 0000000..e9db270
--- /dev/null
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Purchase Order", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially('Purchase Order', [
+ // insert a new Purchase Order
+ () => frappe.tests.make([
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index 3767248..3899bba 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -22,7 +22,9 @@
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
__("Make"));
-
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
+ }, __("Make"))
}
else if (this.frm.doc.docstatus===0) {
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index ea3ae74..eed0c15 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -2055,6 +2055,67 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "more_info",
@@ -2247,7 +2308,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-07-19 13:51:18.929697",
+ "modified": "2017-08-31 11:23:25.268924",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
index df69063..4321f27 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py
@@ -3,6 +3,9 @@
def get_data():
return {
'fieldname': 'supplier_quotation',
+ 'non_standard_fieldnames': {
+ 'Subscription': 'reference_document'
+ },
'internal_links': {
'Material Request': ['items', 'material_request'],
'Request for Quotation': ['items', 'request_for_quotation'],
@@ -17,6 +20,10 @@
'label': _('Reference'),
'items': ['Material Request', 'Request for Quotation', 'Project']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.js
new file mode 100644
index 0000000..7097a6d
--- /dev/null
+++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Supplier Quotation", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially('Supplier Quotation', [
+ // insert a new Supplier Quotation
+ () => frappe.tests.make([
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b24e047..d04143d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -8,7 +8,6 @@
from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase
-from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
from erpnext.controllers.sales_and_purchase_return import validate_return
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
from erpnext.exceptions import InvalidCurrency
@@ -53,13 +52,6 @@
self.validate_party()
self.validate_currency()
- if self.meta.get_field("is_recurring"):
- if self.amended_from and self.recurring_id == self.amended_from:
- self.recurring_id = None
- if not self.get("__islocal"):
- validate_recurring_document(self)
- convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
-
if self.doctype == 'Purchase Invoice':
self.validate_paid_amount()
@@ -84,11 +76,6 @@
else:
frappe.db.set(self,'paid_amount',0)
- def on_update_after_submit(self):
- if self.meta.get_field("is_recurring"):
- validate_recurring_document(self)
- convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
-
def set_missing_values(self, for_validate=False):
if frappe.flags.in_test:
for fieldname in ["posting_date","transaction_date"]:
diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py
deleted file mode 100644
index 713e9ba..0000000
--- a/erpnext/controllers/recurring_document.py
+++ /dev/null
@@ -1,230 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-import calendar
-import frappe.utils
-import frappe.defaults
-
-from frappe.utils import cint, cstr, getdate, nowdate, \
- get_first_day, get_last_day, split_emails
-
-from frappe import _, msgprint, throw
-
-month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
-date_field_map = {
- "Sales Order": "transaction_date",
- "Sales Invoice": "posting_date",
- "Purchase Order": "transaction_date",
- "Purchase Invoice": "posting_date"
-}
-
-def create_recurring_documents():
- manage_recurring_documents("Sales Order")
- manage_recurring_documents("Sales Invoice")
- manage_recurring_documents("Purchase Order")
- manage_recurring_documents("Purchase Invoice")
-
-def manage_recurring_documents(doctype, next_date=None, commit=True):
- """
- Create recurring documents on specific date by copying the original one
- and notify the concerned people
- """
- next_date = next_date or nowdate()
-
- date_field = date_field_map[doctype]
-
- condition = " and ifnull(status, '') != 'Closed'" if doctype in ("Sales Order", "Purchase Order") else ""
-
- recurring_documents = frappe.db.sql("""select name, recurring_id
- from `tab{0}` where is_recurring=1
- and (docstatus=1 or docstatus=0) and next_date=%s
- and next_date <= ifnull(end_date, '2199-12-31') {1}""".format(doctype, condition), next_date)
-
- exception_list = []
- for ref_document, recurring_id in recurring_documents:
- if not frappe.db.sql("""select name from `tab%s`
- where %s=%s and recurring_id=%s and (docstatus=1 or docstatus=0)"""
- % (doctype, date_field, '%s', '%s'), (next_date, recurring_id)):
- try:
- reference_doc = frappe.get_doc(doctype, ref_document)
- new_doc = make_new_document(reference_doc, date_field, next_date)
- if reference_doc.notify_by_email:
- send_notification(new_doc)
- if commit:
- frappe.db.commit()
- except:
- if commit:
- frappe.db.rollback()
-
- frappe.db.begin()
- frappe.db.sql("update `tab%s` \
- set is_recurring = 0 where name = %s" % (doctype, '%s'),
- (ref_document))
- notify_errors(ref_document, doctype, reference_doc.get("customer") or reference_doc.get("supplier"),
- reference_doc.owner)
- frappe.db.commit()
-
- exception_list.append(frappe.get_traceback())
- finally:
- if commit:
- frappe.db.begin()
-
- if exception_list:
- exception_message = "\n\n".join([cstr(d) for d in exception_list])
- frappe.throw(exception_message)
-
-def make_new_document(reference_doc, date_field, posting_date):
- new_document = frappe.copy_doc(reference_doc, ignore_no_copy=False)
- mcount = month_map[reference_doc.recurring_type]
-
- from_date = get_next_date(reference_doc.from_date, mcount)
-
- # get last day of the month to maintain period if the from date is first day of its own month
- # and to date is the last day of its own month
- if (cstr(get_first_day(reference_doc.from_date)) == cstr(reference_doc.from_date)) and \
- (cstr(get_last_day(reference_doc.to_date)) == cstr(reference_doc.to_date)):
- to_date = get_last_day(get_next_date(reference_doc.to_date, mcount))
- else:
- to_date = get_next_date(reference_doc.to_date, mcount)
-
- new_document.update({
- date_field: posting_date,
- "from_date": from_date,
- "to_date": to_date,
- "next_date": get_next_date(reference_doc.next_date, mcount,cint(reference_doc.repeat_on_day_of_month))
- })
-
- if new_document.meta.get_field('set_posting_time'):
- new_document.set('set_posting_time', 1)
-
- # copy document fields
- for fieldname in ("owner", "recurring_type", "repeat_on_day_of_month",
- "recurring_id", "notification_email_address", "is_recurring", "end_date",
- "title", "naming_series", "select_print_heading", "ignore_pricing_rule",
- "posting_time", "remarks", 'submit_on_creation'):
- if new_document.meta.get_field(fieldname):
- new_document.set(fieldname, reference_doc.get(fieldname))
-
- # copy item fields
- for i, item in enumerate(new_document.items):
- for fieldname in ("page_break",):
- item.set(fieldname, reference_doc.items[i].get(fieldname))
-
- new_document.run_method("on_recurring", reference_doc=reference_doc)
-
- if reference_doc.submit_on_creation:
- new_document.insert()
- new_document.submit()
- else:
- new_document.docstatus=0
- new_document.insert()
-
- return new_document
-
-def get_next_date(dt, mcount, day=None):
- dt = getdate(dt)
-
- from dateutil.relativedelta import relativedelta
- dt += relativedelta(months=mcount, day=day)
-
- return dt
-
-def send_notification(new_rv):
- """Notify concerned persons about recurring document generation"""
-
- frappe.sendmail(new_rv.notification_email_address,
- subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
- message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
- attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=new_rv.recurring_print_format)])
-
-def notify_errors(doc, doctype, party, owner):
- from frappe.utils.user import get_system_managers
- recipients = get_system_managers(only_name=True)
-
- frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
- subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc),
- message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
- "type": doctype,
- "name": doc,
- "party": party
- }))
-
- assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients)
-
-def assign_task_to_owner(doc, doctype, msg, users):
- for d in users:
- from frappe.desk.form import assign_to
- args = {
- 'assign_to' : d,
- 'doctype' : doctype,
- 'name' : doc,
- 'description' : msg,
- 'priority' : 'High'
- }
- assign_to.add(args)
-
-def validate_recurring_document(doc):
- if doc.is_recurring:
- validate_notification_email_id(doc)
- if not doc.recurring_type:
- frappe.throw(_("Please select {0}").format(doc.meta.get_label("recurring_type")))
-
- elif not (doc.from_date and doc.to_date):
- frappe.throw(_("Period From and Period To dates mandatory for recurring {0}").format(doc.doctype))
-
-def validate_recurring_next_date(doc):
- posting_date = doc.get("posting_date") or doc.get("transaction_date")
- if getdate(posting_date) > getdate(doc.next_date):
- frappe.throw(_("Next Date must be greater than Posting Date"))
-
- next_date = getdate(doc.next_date)
- if next_date.day != doc.repeat_on_day_of_month:
-
- # if the repeat day is the last day of the month (31)
- # and the current month does not have as many days,
- # then the last day of the current month is a valid date
- lastday = calendar.monthrange(next_date.year, next_date.month)[1]
- if doc.repeat_on_day_of_month < lastday:
-
- # the specified day of the month is not same as the day specified
- # or the last day of the month
- frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
-
-def convert_to_recurring(doc, posting_date):
- if doc.is_recurring:
- if not doc.recurring_id:
- doc.db_set("recurring_id", doc.name)
-
- set_next_date(doc, posting_date)
-
- if doc.next_date:
- validate_recurring_next_date(doc)
-
- elif doc.recurring_id:
- doc.db_set("recurring_id", None)
-
-def validate_notification_email_id(doc):
- if doc.notify_by_email:
- if doc.notification_email_address:
- email_list = split_emails(doc.notification_email_address.replace("\n", ""))
-
- from frappe.utils import validate_email_add
- for email in email_list:
- if not validate_email_add(email):
- throw(_("{0} is an invalid email address in 'Notification \
- Email Address'").format(email))
-
- else:
- frappe.throw(_("'Notification Email Addresses' not specified for recurring %s") \
- % doc.doctype)
-
-def set_next_date(doc, posting_date):
- """ Set next date on which recurring document will be created"""
- if not doc.repeat_on_day_of_month:
- msgprint(_("Please enter 'Repeat on Day of Month' field value"), raise_exception=1)
-
- next_date = get_next_date(posting_date, month_map[doc.recurring_type],
- cint(doc.repeat_on_day_of_month))
-
- doc.db_set('next_date', next_date)
-
- msgprint(_("Next Recurring {0} will be created on {1}").format(doc.doctype, next_date))
diff --git a/erpnext/controllers/tests/test_recurring_document.py b/erpnext/controllers/tests/test_recurring_document.py
deleted file mode 100644
index d47c5c7..0000000
--- a/erpnext/controllers/tests/test_recurring_document.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import frappe.permissions
-from erpnext.controllers.recurring_document import date_field_map
-from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate, add_days
-
-def test_recurring_document(obj, test_records):
- frappe.db.set_value("Print Settings", "Print Settings", "send_print_as_pdf", 1)
- today = nowdate()
- base_doc = frappe.copy_doc(test_records[0])
-
- base_doc.update({
- "is_recurring": 1,
- "submit_on_create": 1,
- "recurring_type": "Monthly",
- "notification_email_address": "test@example.com, test1@example.com, test2@example.com",
- "repeat_on_day_of_month": getdate(today).day,
- "due_date": None,
- "from_date": get_first_day(today),
- "to_date": get_last_day(today)
- })
-
- date_field = date_field_map[base_doc.doctype]
- base_doc.set(date_field, today)
-
- if base_doc.doctype == "Sales Order":
- base_doc.set("delivery_date", add_days(today, 15))
-
- # monthly
- doc1 = frappe.copy_doc(base_doc)
- doc1.insert()
- doc1.submit()
- _test_recurring_document(obj, doc1, date_field, True)
-
- # monthly without a first and last day period
- if getdate(today).day != 1:
- doc2 = frappe.copy_doc(base_doc)
- doc2.update({
- "from_date": today,
- "to_date": add_to_date(today, days=30)
- })
- doc2.insert()
- doc2.submit()
- _test_recurring_document(obj, doc2, date_field, False)
-
- # quarterly
- doc3 = frappe.copy_doc(base_doc)
- doc3.update({
- "recurring_type": "Quarterly",
- "from_date": get_first_day(today),
- "to_date": get_last_day(add_to_date(today, months=3))
- })
- doc3.insert()
- doc3.submit()
- _test_recurring_document(obj, doc3, date_field, True)
-
- # quarterly without a first and last day period
- doc4 = frappe.copy_doc(base_doc)
- doc4.update({
- "recurring_type": "Quarterly",
- "from_date": today,
- "to_date": add_to_date(today, months=3)
- })
- doc4.insert()
- doc4.submit()
- _test_recurring_document(obj, doc4, date_field, False)
-
- # yearly
- doc5 = frappe.copy_doc(base_doc)
- doc5.update({
- "recurring_type": "Yearly",
- "from_date": get_first_day(today),
- "to_date": get_last_day(add_to_date(today, years=1))
- })
- doc5.insert()
- doc5.submit()
- _test_recurring_document(obj, doc5, date_field, True)
-
- # yearly without a first and last day period
- doc6 = frappe.copy_doc(base_doc)
- doc6.update({
- "recurring_type": "Yearly",
- "from_date": today,
- "to_date": add_to_date(today, years=1)
- })
- doc6.insert()
- doc6.submit()
- _test_recurring_document(obj, doc6, date_field, False)
-
- # change date field but keep recurring day to be today
- doc7 = frappe.copy_doc(base_doc)
- doc7.update({
- date_field: today,
- })
- doc7.insert()
- doc7.submit()
-
- # setting so that _test function works
- # doc7.set(date_field, today)
- _test_recurring_document(obj, doc7, date_field, True)
-
-def _test_recurring_document(obj, base_doc, date_field, first_and_last_day):
- from frappe.utils import add_months, get_last_day
- from erpnext.controllers.recurring_document import manage_recurring_documents, \
- get_next_date
-
- no_of_months = ({"Monthly": 1, "Quarterly": 3, "Yearly": 12})[base_doc.recurring_type]
-
- def _test(i):
- obj.assertEquals(i+1, frappe.db.sql("""select count(*) from `tab%s`
- where recurring_id=%s and (docstatus=1 or docstatus=0)""" % (base_doc.doctype, '%s'),
- (base_doc.recurring_id))[0][0])
-
- next_date = get_next_date(base_doc.get(date_field), no_of_months,
- base_doc.repeat_on_day_of_month)
-
- manage_recurring_documents(base_doc.doctype, next_date=next_date, commit=False)
-
- recurred_documents = frappe.db.sql("""select name from `tab%s`
- where recurring_id=%s and (docstatus=1 or docstatus=0) order by name desc"""
- % (base_doc.doctype, '%s'), (base_doc.recurring_id))
-
- obj.assertEquals(i+2, len(recurred_documents))
-
- new_doc = frappe.get_doc(base_doc.doctype, recurred_documents[0][0])
-
- for fieldname in ["is_recurring", "recurring_type",
- "repeat_on_day_of_month", "notification_email_address"]:
- obj.assertEquals(base_doc.get(fieldname),
- new_doc.get(fieldname))
-
- obj.assertEquals(new_doc.get(date_field), getdate(next_date))
-
- obj.assertEquals(new_doc.from_date, getdate(add_months(base_doc.from_date, no_of_months)))
-
- if first_and_last_day:
- obj.assertEquals(new_doc.to_date, getdate(get_last_day(add_months(base_doc.to_date, no_of_months))))
- else:
- obj.assertEquals(new_doc.to_date, getdate(add_months(base_doc.to_date, no_of_months)))
-
- return new_doc
-
- # if yearly, test 1 repetition, else test 5 repetitions
- count = 1 if (no_of_months == 12) else 5
- for i in xrange(count):
- base_doc = _test(i)
diff --git a/erpnext/docs/assets/img/subscription/__init__.py b/erpnext/docs/assets/img/subscription/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/assets/img/subscription/__init__.py
diff --git a/erpnext/docs/assets/img/subscription/subscription.gif b/erpnext/docs/assets/img/subscription/subscription.gif
new file mode 100644
index 0000000..6848805
--- /dev/null
+++ b/erpnext/docs/assets/img/subscription/subscription.gif
Binary files differ
diff --git a/erpnext/docs/assets/img/subscription/subscription.png b/erpnext/docs/assets/img/subscription/subscription.png
new file mode 100644
index 0000000..8b2cdc3
--- /dev/null
+++ b/erpnext/docs/assets/img/subscription/subscription.png
Binary files differ
diff --git a/erpnext/docs/user/manual/en/index.txt b/erpnext/docs/user/manual/en/index.txt
index fff4da7..712ab8e 100644
--- a/erpnext/docs/user/manual/en/index.txt
+++ b/erpnext/docs/user/manual/en/index.txt
@@ -9,6 +9,7 @@
projects
support
human-resources
+subscription
customer-portal
website
using-erpnext
diff --git a/erpnext/docs/user/manual/en/subscription/__init__.py b/erpnext/docs/user/manual/en/subscription/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/en/subscription/__init__.py
diff --git a/erpnext/docs/user/manual/en/subscription/index.md b/erpnext/docs/user/manual/en/subscription/index.md
new file mode 100644
index 0000000..24d75ed
--- /dev/null
+++ b/erpnext/docs/user/manual/en/subscription/index.md
@@ -0,0 +1,22 @@
+If you have a contract with the Customer where your organization gives bill to the Customer on a monthly, quarterly, half-yearly or annual basis, you can use subscription feature to make auto invoicing.
+
+<img class="screenshot" alt="Subscription" src="{{docs_base_url}}/assets/img/subscription/subscription.png">
+
+#### Scenario
+
+Subscription for your hosted ERPNext account requires yearly renewal. We use Sales Invoice for generating proforma invoices. To automate proforma invoicing for renewal, we set original Sales Invoice on the subscription form. Recurring proforma invoice is created automatically just before customer's account is about to expire, and requires renewal. This recurring Proforma Invoice is also emailed automatically to the customer.
+
+To set the subscription for the sales invoice
+Goto Subscription > select base doctype "Sales Invoice" > select base docname "Invoice No" > Save
+
+<img class="screenshot" alt="Subscription" src="{{docs_base_url}}/assets/img/subscription/subscription.gif">
+
+**From Date and To Date**: This defines contract period with the customer.
+
+**Repeat on Day**: If frequency is set as Monthly, then it will be day of the month on which recurring invoice will be generated.
+
+**Notify By Email**: If you want to notify the user about auto recurring invoice.
+
+**Print Format**: Select a print format to define document view which should be emailed to customer.
+
+**Disabled**: It will stop to make auto recurring documents against the subscription
\ No newline at end of file
diff --git a/erpnext/docs/user/manual/en/subscription/index.txt b/erpnext/docs/user/manual/en/subscription/index.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/docs/user/manual/en/subscription/index.txt
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e5d9ef9..b2c3285 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -184,7 +184,7 @@
scheduler_events = {
"hourly": [
- "erpnext.controllers.recurring_document.create_recurring_documents",
+ "erpnext.subscription.doctype.subscription.subscription.make_subscription_entry",
'erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.trigger_emails'
],
"daily": [
diff --git a/erpnext/hr/doctype/training_event/tests/test_training_event_attandance.js b/erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
similarity index 100%
rename from erpnext/hr/doctype/training_event/tests/test_training_event_attandance.js
rename to erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py
index 20a3bc5..b7eae38 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.py
@@ -20,4 +20,4 @@
training_event.status = 'Feedback Submitted'
break
- training_event.update_after_submit()
+ training_event.save()
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 1edff10..0579cc2 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -14,4 +14,5 @@
Portal
Maintenance
Schools
-Regional
\ No newline at end of file
+Regional
+Subscription
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 452720f..50c64b5 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -439,3 +439,4 @@
erpnext.patches.v8_9.add_setup_progress_actions
erpnext.patches.v8_9.rename_company_sales_target_field
erpnext.patches.v8_8.set_bom_rate_as_per_uom
+erpnext.patches.v8_7.make_subscription_from_recurring_data
\ No newline at end of file
diff --git a/erpnext/patches/v8_7/make_subscription_from_recurring_data.py b/erpnext/patches/v8_7/make_subscription_from_recurring_data.py
new file mode 100644
index 0000000..03d8eb4
--- /dev/null
+++ b/erpnext/patches/v8_7/make_subscription_from_recurring_data.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import today
+
+def execute():
+ frappe.reload_doc('subscription', 'doctype', 'subscription')
+ frappe.reload_doc('selling', 'doctype', 'sales_order')
+ frappe.reload_doc('buying', 'doctype', 'purchase_order')
+ frappe.reload_doc('accounts', 'doctype', 'sales_invoice')
+ frappe.reload_doc('accounts', 'doctype', 'purchase_invoice')
+
+ for doctype in ['Sales Order', 'Sales Invoice',
+ 'Purchase Invoice', 'Purchase Invoice']:
+ for data in get_data(doctype):
+ make_subscription(doctype, data)
+
+def get_data(doctype):
+ return frappe.db.sql(""" select name, from_date, end_date, recurring_type,recurring_id
+ next_date, notify_by_email, notification_email_address, recurring_print_format,
+ repeat_on_day_of_month, submit_on_creation
+ from `tab{0}` where is_recurring = 1 and next_date >= %s
+ """.format(doctype), today(), as_dict=1)
+
+def make_subscription(doctype, data):
+ doc = frappe.get_doc({
+ 'doctype': 'Subscription',
+ 'reference_doctype': doctype,
+ 'reference_document': data.name,
+ 'start_date': data.from_date,
+ 'end_date': data.end_date,
+ 'frequency': data.recurring_type,
+ 'repeat_on_day': data.repeat_on_day_of_month,
+ 'notify_by_email': data.notify_by_email,
+ 'recipients': data.notification_email_address,
+ 'next_schedule_date': data.next_date,
+ 'submit_on_creation': data.submit_on_creation
+ }).insert(ignore_permissions=True)
+
+ doc.submit()
+
+ if not doc.subscription:
+ frappe.db.set_value(doctype, data.name, "subscription", doc.name)
\ No newline at end of file
diff --git a/erpnext/patches/v8_9/rename_company_sales_target_field.py b/erpnext/patches/v8_9/rename_company_sales_target_field.py
index 8c54283..5433eb6 100644
--- a/erpnext/patches/v8_9/rename_company_sales_target_field.py
+++ b/erpnext/patches/v8_9/rename_company_sales_target_field.py
@@ -4,4 +4,5 @@
def execute():
frappe.reload_doc("setup", "doctype", "company")
- rename_field("Company", "sales_target", "monthly_sales_target")
+ if frappe.db.has_column('Company', 'sales_target'):
+ rename_field("Company", "sales_target", "monthly_sales_target")
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index a333ca8..8a47df6 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -127,6 +127,20 @@
}
},
+ make_subscription: function(doctype, docname) {
+ frappe.call({
+ method: "erpnext.subscription.doctype.subscription.subscription.make_subscription",
+ args: {
+ doctype: doctype,
+ docname: docname
+ },
+ callback: function(r) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+ }
+ })
+ },
+
/**
* Checks if the first row of a given child table is empty
* @param child_table - Child table Doctype
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 5b137c3..1863fb2 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -10,12 +10,15 @@
'Sales Order': 'Make Sales Order'
}
},
+
refresh: function(frm) {
frm.trigger("set_label");
},
+
quotation_to: function(frm) {
frm.trigger("set_label");
},
+
set_label: function(frm) {
frm.fields_dict.customer_address.set_label(__(frm.doc.quotation_to + " Address"));
}
@@ -44,14 +47,22 @@
if(doc.docstatus == 1 && doc.status!=='Lost') {
if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) > 0) {
- cur_frm.add_custom_button(__('Make Sales Order'),
- cur_frm.cscript['Make Sales Order']);
+ cur_frm.add_custom_button(__('Sales Order'),
+ cur_frm.cscript['Make Sales Order'], __("Make"));
}
if(doc.status!=="Ordered") {
cur_frm.add_custom_button(__('Set as Lost'),
cur_frm.cscript['Declare Order Lost']);
}
+
+ if(!doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
+
+ cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
}
if (this.frm.doc.docstatus===0) {
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 0afc5ca..ab879a1 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -2341,6 +2341,67 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "more_info",
@@ -2633,7 +2694,7 @@
"istable": 0,
"max_attachments": 1,
"menu_index": 0,
- "modified": "2017-08-09 06:35:48.691648",
+ "modified": "2017-08-31 11:22:15.268846",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index f3ebe81..1cdd840 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -108,6 +108,9 @@
print_lst.append(lst1)
return print_lst
+ def on_recurring(self, reference_doc, subscription_doc):
+ self.valid_till = None
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context)
diff --git a/erpnext/selling/doctype/quotation/quotation_dashboard.py b/erpnext/selling/doctype/quotation/quotation_dashboard.py
index f1c41e5..c6297e2 100644
--- a/erpnext/selling/doctype/quotation/quotation_dashboard.py
+++ b/erpnext/selling/doctype/quotation/quotation_dashboard.py
@@ -3,9 +3,17 @@
def get_data():
return {
'fieldname': 'prevdoc_docname',
+ 'non_standard_fieldnames': {
+ 'Subscription': 'reference_document',
+ },
'transactions': [
{
+ 'label': _('Sales Order'),
'items': ['Sales Order']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation.js b/erpnext/selling/doctype/quotation/tests/test_quotation.js
index 44173cc..31b1797 100644
--- a/erpnext/selling/doctype/quotation/tests/test_quotation.js
+++ b/erpnext/selling/doctype/quotation/tests/test_quotation.js
@@ -50,4 +50,4 @@
},
() => done()
]);
-});
\ No newline at end of file
+});
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 901e236..00d2121 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -141,6 +141,12 @@
function() { me.make_project() }, __("Make"));
}
+ if(!doc.subscription) {
+ this.frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
+
} else {
if (this.frm.has_perm("submit")) {
// un-close
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index b69b3fd..3b8eb68 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -3181,6 +3181,67 @@
},
{
"allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -3189,7 +3250,7 @@
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order",
"fieldtype": "Section Break",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -3659,7 +3720,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-08-07 21:27:10.073581",
+ "modified": "2017-08-31 11:21:36.332326",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 5f904c2..5f82890 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -11,9 +11,9 @@
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
from frappe.desk.notifications import clear_doctype_notifications
-from erpnext.controllers.recurring_document import month_map, get_next_date
from frappe.contacts.doctype.address.address import get_company_address
from erpnext.controllers.selling_controller import SellingController
+from erpnext.subscription.doctype.subscription.subscription import month_map, get_next_date
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -346,17 +346,17 @@
return items
- def on_recurring(self, reference_doc):
- mcount = month_map[reference_doc.recurring_type]
+ def on_recurring(self, reference_doc, subscription_doc):
+ mcount = month_map[subscription_doc.frequency]
self.set("delivery_date", get_next_date(reference_doc.delivery_date, mcount,
- cint(reference_doc.repeat_on_day_of_month)))
+ cint(subscription_doc.repeat_on_day)))
for d in self.get("items"):
reference_delivery_date = frappe.db.get_value("Sales Order Item",
{"parent": reference_doc.name, "item_code": d.item_code, "idx": d.idx}, "delivery_date")
d.set("delivery_date",
- get_next_date(reference_delivery_date, mcount, cint(reference_doc.repeat_on_day_of_month)))
+ get_next_date(reference_delivery_date, mcount, cint(subscription_doc.repeat_on_day)))
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
index a0ed034..ffce7ce 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
+++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
@@ -7,7 +7,8 @@
'Delivery Note': 'against_sales_order',
'Journal Entry': 'reference_name',
'Payment Entry': 'reference_name',
- 'Payment Request': 'reference_name'
+ 'Payment Request': 'reference_name',
+ 'Subscription': 'reference_document',
},
'internal_links': {
'Quotation': ['items', 'prevdoc_docname']
@@ -31,7 +32,7 @@
},
{
'label': _('Reference'),
- 'items': ['Quotation']
+ 'items': ['Quotation', 'Subscription']
},
{
'label': _('Payment'),
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.js b/erpnext/selling/doctype/sales_order/test_sales_order.js
new file mode 100644
index 0000000..57ed19b
--- /dev/null
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Sales Order", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially('Sales Order', [
+ // insert a new Sales Order
+ () => frappe.tests.make([
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multi_uom.js b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multi_uom.js
index 74f51ca..84301f5 100644
--- a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multi_uom.js
+++ b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_multi_uom.js
@@ -12,7 +12,7 @@
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
{'qty': 5},
{'item_code': 'Test Product 4'},
- {'uom': 'unit'},
+ {'uom': 'Unit'},
]
]},
{customer_address: 'Test1-Billing'},
diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py
index aed6698..2db63c1 100644
--- a/erpnext/setup/setup_wizard/test_setup_wizard.py
+++ b/erpnext/setup/setup_wizard/test_setup_wizard.py
@@ -9,12 +9,13 @@
def run_setup_wizard_test():
driver = TestDriver()
frappe.db.set_default('in_selenium', '1')
+ frappe.db.commit()
driver.login('#page-setup-wizard')
print('Running Setup Wizard Test...')
# Language slide
- driver.set_select("language", "English (United Kingdom)")
+ driver.set_select("language", "English (United States)")
driver.wait_for_ajax(True)
driver.wait_till_clickable(".next-btn").click()
@@ -25,9 +26,9 @@
driver.wait_till_clickable(".next-btn").click()
# Profile slide
- driver.set_field("full_name", "Joe Davis")
- driver.set_field("email", "joe@example.com")
- driver.set_field("password", "somethingrandom")
+ driver.set_field("full_name", "Great Tester")
+ driver.set_field("email", "great@example.com")
+ driver.set_field("password", "test")
driver.wait_till_clickable(".next-btn").click()
# Brand slide
@@ -35,14 +36,14 @@
driver.wait_till_clickable(".next-btn").click()
# Org slide
- driver.set_field("company_name", "Acme Corp")
+ driver.set_field("company_name", "For Testing")
driver.wait_till_clickable(".next-btn").click()
- driver.set_field("company_tagline", "Build Tools for Builders")
- driver.set_field("bank_account", "BNL")
+ driver.set_field("company_tagline", "Just for GST")
+ driver.set_field("bank_account", "HDFC")
driver.wait_till_clickable(".complete-btn").click()
- # Wait for desk (Lock wait timeout error)
- # driver.wait_for('#page-desktop', timeout=200)
+ # Wait for desktop
+ driver.wait_for('#page-desktop', timeout=600)
console = driver.get_console()
if frappe.flags.tests_verbose:
@@ -52,6 +53,8 @@
time.sleep(1)
frappe.db.set_default('in_selenium', None)
+ frappe.db.commit()
+
driver.close()
return True
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py
index dc4abd4..d821a12 100644
--- a/erpnext/setup/setup_wizard/utils.py
+++ b/erpnext/setup/setup_wizard/utils.py
@@ -10,6 +10,3 @@
#setup_wizard.create_sales_tax(data)
setup_complete(data)
-
-
-
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 46d536d..e0ee370 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -166,6 +166,12 @@
__("Status"))
}
erpnext.stock.delivery_note.set_print_hide(doc, dt, dn);
+
+ if(doc.docstatus==1 && !doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(doc.doctype, doc.name)
+ }, __("Make"))
+ }
},
make_sales_invoice: function() {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 41f8b84..980f79b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -3255,6 +3255,67 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_section",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Section",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "total_commission",
"columns": 0,
@@ -3487,7 +3548,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-08-23 13:25:34.182268",
+ "modified": "2017-08-31 11:21:59.084183",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
index c296d8c..2e150f7 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
@@ -5,7 +5,8 @@
'fieldname': 'delivery_note',
'non_standard_fieldnames': {
'Stock Entry': 'delivery_note_no',
- 'Quality Inspection': 'reference_name'
+ 'Quality Inspection': 'reference_name',
+ 'Subscription': 'reference_document',
},
'internal_links': {
'Sales Order': ['items', 'against_sales_order'],
@@ -23,5 +24,9 @@
'label': _('Returns'),
'items': ['Stock Entry']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.js b/erpnext/stock/doctype/delivery_note/test_delivery_note.js
index 482f892..bbc97b8 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.js
@@ -34,4 +34,3 @@
]);
});
-
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 5dc97f6..f2ea1d8 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -488,6 +488,8 @@
def validate_warehouse_for_reorder(self):
warehouse = []
for i in self.get("reorder_levels"):
+ if not i.warehouse_group:
+ i.warehouse_group = i.warehouse
if i.get("warehouse") and i.get("warehouse") not in warehouse:
warehouse += [i.get("warehouse")]
else:
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index ed647d6..87cde0d 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -86,7 +86,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
"length": 0,
@@ -144,7 +144,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
"label": "Series",
"length": 0,
@@ -211,7 +211,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"length": 0,
@@ -369,7 +369,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
"label": "Transaction Date",
"length": 0,
@@ -686,7 +686,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-06-13 14:29:18.032657",
+ "modified": "2017-07-26 19:43:31.823549",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
diff --git a/erpnext/stock/doctype/material_request/test_material_request.js b/erpnext/stock/doctype/material_request/test_material_request.js
new file mode 100644
index 0000000..793cad0
--- /dev/null
+++ b/erpnext/stock/doctype/material_request/test_material_request.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Material Request", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially('Material Request', [
+ // insert a new Material Request
+ () => frappe.tests.make([
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 19cc44a..4447fb8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -48,7 +48,7 @@
toggle_display_account_head: function(frm) {
var enabled = erpnext.is_perpetual_inventory_enabled(frm.doc.company)
frm.fields_dict["items"].grid.set_column_disp(["cost_center"], enabled);
- }
+ },
});
erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend({
@@ -98,6 +98,13 @@
if(flt(this.frm.doc.per_billed) < 100) {
cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice, __("Make"));
}
+
+ if(!this.frm.doc.subscription) {
+ cur_frm.add_custom_button(__('Subscription'), function() {
+ erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
+ }, __("Make"))
+ }
+
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
}
}
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index adcea6d..7140dbd 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -2615,6 +2615,67 @@
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription_detail",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription Detail",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "subscription",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Subscription",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "printing_settings",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
index 9722d87..9ade1af 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
@@ -5,7 +5,8 @@
'fieldname': 'purchase_receipt_no',
'non_standard_fieldnames': {
'Purchase Invoice': 'purchase_receipt',
- 'Landed Cost Voucher': 'receipt_document'
+ 'Landed Cost Voucher': 'receipt_document',
+ 'Subscription': 'reference_document'
},
'internal_links': {
'Purchase Order': ['items', 'purchase_order'],
@@ -25,5 +26,9 @@
'label': _('Returns'),
'items': ['Stock Entry']
},
+ {
+ 'label': _('Subscription'),
+ 'items': ['Subscription']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/subscription/__init__.py b/erpnext/subscription/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/subscription/__init__.py
diff --git a/erpnext/subscription/doctype/__init__.py b/erpnext/subscription/doctype/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/subscription/doctype/__init__.py
diff --git a/erpnext/subscription/doctype/subscription/__init__.py b/erpnext/subscription/doctype/subscription/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/__init__.py
diff --git a/erpnext/subscription/doctype/subscription/subscription.js b/erpnext/subscription/doctype/subscription/subscription.js
new file mode 100644
index 0000000..75e1473
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/subscription.js
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Subscription', {
+ setup: function(frm) {
+ frm.fields_dict['reference_document'].get_query = function() {
+ return {
+ filters: {
+ "docstatus": 1
+ }
+ };
+ };
+
+ frm.fields_dict['print_format'].get_query = function() {
+ return {
+ filters: {
+ "doc_type": frm.doc.reference_doctype
+ }
+ };
+ };
+ },
+
+ refresh: function(frm) {
+ if(frm.doc.docstatus == 1) {
+ let label = __('View {0}', [frm.doc.reference_doctype]);
+ frm.add_custom_button(__(label),
+ function() {
+ frappe.route_options = {
+ "subscription": frm.doc.name,
+ };
+ frappe.set_route("List", frm.doc.reference_doctype);
+ }
+ );
+ }
+ }
+});
\ No newline at end of file
diff --git a/erpnext/subscription/doctype/subscription/subscription.json b/erpnext/subscription/doctype/subscription/subscription.json
new file mode 100644
index 0000000..6cfee1e
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/subscription.json
@@ -0,0 +1,731 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "naming_series:",
+ "beta": 0,
+ "creation": "2017-07-18 17:50:43.967266",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "section_break_1",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Series",
+ "length": 0,
+ "no_copy": 0,
+ "options": "SUB-",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Reference Doctype",
+ "length": 0,
+ "no_copy": 0,
+ "options": "DocType",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "reference_document",
+ "fieldtype": "Dynamic Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Reference Document",
+ "length": 0,
+ "no_copy": 1,
+ "options": "reference_doctype",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Disabled",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "submit_on_creation",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Submit on Creation",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Start Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "end_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "End Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "next_schedule_date",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Next Schedule Date",
+ "length": 0,
+ "no_copy": 1,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "frequency_detail",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "frequency",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Frequency",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "eval: in_list([\"Monthly\", \"Quarterly\", \"Yearly\"], doc.frequency)",
+ "fieldname": "repeat_on_day",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Repeat on Day",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "notification",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Notification",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "notify_by_email",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Notify by Email",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "notify_by_email",
+ "fieldname": "recipients",
+ "fieldtype": "Small Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Recipients",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "notify_by_email",
+ "fieldname": "print_format",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Print Format",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Print Format",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
+ "fieldname": "section_break_16",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Status",
+ "length": 0,
+ "no_copy": 0,
+ "options": "\nDraft\nSubmitted\nCancelled\nCompleted",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Amended From",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Subscription",
+ "permlevel": 0,
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2017-08-29 15:45:16.157643",
+ "modified_by": "Administrator",
+ "module": "Subscription",
+ "name": "Subscription",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "search_fields": "reference_document",
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "reference_document",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/subscription/doctype/subscription/subscription.py b/erpnext/subscription/doctype/subscription/subscription.py
new file mode 100644
index 0000000..be36211
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/subscription.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import calendar
+from frappe import _
+from frappe.desk.form import assign_to
+from dateutil.relativedelta import relativedelta
+from frappe.utils.user import get_system_managers
+from frappe.utils import cstr, getdate, split_emails, add_days, today
+from frappe.model.document import Document
+
+month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
+class Subscription(Document):
+ def validate(self):
+ self.update_status()
+ self.validate_dates()
+ self.validate_next_schedule_date()
+ self.validate_email_id()
+
+ def before_submit(self):
+ self.set_next_schedule_date()
+
+ def on_submit(self):
+ self.update_subscription_id()
+
+ def on_update_after_submit(self):
+ self.validate_dates()
+ self.set_next_schedule_date()
+
+ def validate_dates(self):
+ if self.end_date and getdate(self.start_date) > getdate(self.end_date):
+ frappe.throw(_("End date must be greater than start date"))
+
+ def validate_next_schedule_date(self):
+ if self.repeat_on_day and self.next_schedule_date:
+ next_date = getdate(self.next_schedule_date)
+ if next_date.day != self.repeat_on_day:
+ # if the repeat day is the last day of the month (31)
+ # and the current month does not have as many days,
+ # then the last day of the current month is a valid date
+ lastday = calendar.monthrange(next_date.year, next_date.month)[1]
+ if self.repeat_on_day < lastday:
+
+ # the specified day of the month is not same as the day specified
+ # or the last day of the month
+ frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
+
+ def validate_email_id(self):
+ if self.notify_by_email:
+ if self.recipients:
+ email_list = split_emails(self.recipients.replace("\n", ""))
+
+ from frappe.utils import validate_email_add
+ for email in email_list:
+ if not validate_email_add(email):
+ frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
+ else:
+ frappe.throw(_("'Recipients' not specified"))
+
+ def set_next_schedule_date(self):
+ self.next_schedule_date = get_next_schedule_date(self.start_date,
+ self.frequency, self.repeat_on_day)
+
+ def update_subscription_id(self):
+ doc = frappe.get_doc(self.reference_doctype, self.reference_document)
+ if not doc.meta.get_field('subscription'):
+ frappe.throw(_("Add custom field Subscription Id in the doctype {0}").format(self.reference_doctype))
+
+ doc.db_set('subscription', self.name)
+
+ def update_status(self):
+ self.status = {
+ '0': 'Draft',
+ '1': 'Submitted',
+ '2': 'Cancelled'
+ }[cstr(self.docstatus or 0)]
+
+def get_next_schedule_date(start_date, frequency, repeat_on_day):
+ mcount = month_map.get(frequency)
+ if mcount:
+ next_date = get_next_date(start_date, mcount, repeat_on_day)
+ else:
+ days = 7 if frequency == 'Weekly' else 1
+ next_date = add_days(start_date, days)
+ return next_date
+
+def make_subscription_entry(date=None):
+ date = date or today()
+ for data in get_subscription_entries(date):
+ schedule_date = getdate(data.next_schedule_date)
+ while schedule_date <= getdate(today()):
+ create_documents(data, schedule_date)
+
+ schedule_date = get_next_schedule_date(schedule_date,
+ data.frequency, data.repeat_on_day)
+
+ if schedule_date:
+ frappe.db.set_value('Subscription', data.name, 'next_schedule_date', schedule_date)
+
+def get_subscription_entries(date):
+ return frappe.db.sql(""" select * from `tabSubscription`
+ where docstatus = 1 and next_schedule_date <=%s
+ and reference_document is not null and reference_document != ''
+ and next_schedule_date <= ifnull(end_date, '2199-12-31')
+ and ifnull(disabled, 0) = 0""", (date), as_dict=1)
+
+def create_documents(data, schedule_date):
+ try:
+ doc = make_new_document(data, schedule_date)
+ if data.notify_by_email:
+ send_notification(doc, data.print_format, data.recipients)
+
+ frappe.db.commit()
+ except Exception:
+ frappe.db.rollback()
+ frappe.db.begin()
+ frappe.log_error(frappe.get_traceback())
+ frappe.db.commit()
+ if data.reference_document and not frappe.flags.in_test:
+ notify_error_to_user(data)
+
+def notify_error_to_user(data):
+ party = ''
+ party_type = ''
+
+ if data.reference_doctype in ['Sales Order', 'Sales Invoice', 'Delivery Note']:
+ party_type = 'customer'
+ elif data.reference_doctype in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
+ party_type = 'supplier'
+
+ if party_type:
+ party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
+
+ notify_errors(data.reference_document, data.reference_doctype, party, data.owner)
+
+def make_new_document(args, schedule_date):
+ doc = frappe.get_doc(args.reference_doctype, args.reference_document)
+ new_doc = frappe.copy_doc(doc, ignore_no_copy=False)
+ update_doc(new_doc, doc , args, schedule_date)
+ new_doc.insert(ignore_permissions=True)
+
+ if args.submit_on_creation:
+ new_doc.submit()
+
+ return new_doc
+
+def update_doc(new_document, reference_doc, args, schedule_date):
+ new_document.docstatus = 0
+ if new_document.meta.get_field('set_posting_time'):
+ new_document.set('set_posting_time', 1)
+
+ if new_document.meta.get_field('subscription'):
+ new_document.set('subscription', args.name)
+
+ new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
+ for data in new_document.meta.fields:
+ if data.fieldtype == 'Date' and data.reqd:
+ new_document.set(data.fieldname, schedule_date)
+
+def get_next_date(dt, mcount, day=None):
+ dt = getdate(dt)
+ dt += relativedelta(months=mcount, day=day)
+
+ return dt
+
+def send_notification(new_rv, print_format='Standard', recipients=None):
+ """Notify concerned persons about recurring document generation"""
+ recipients = recipients or new_rv.notification_email_address
+ print_format = print_format or new_rv.recurring_print_format
+
+ frappe.sendmail(recipients,
+ subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
+ message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
+ attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
+
+def notify_errors(doc, doctype, party, owner):
+ recipients = get_system_managers(only_name=True)
+ frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
+ subject="[Urgent] Error while creating recurring %s for %s" % (doctype, doc),
+ message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
+ "type": doctype,
+ "name": doc,
+ "party": party or ""
+ }))
+
+ assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients)
+
+def assign_task_to_owner(doc, doctype, msg, users):
+ for d in users:
+ args = {
+ 'assign_to' : d,
+ 'doctype' : doctype,
+ 'name' : doc,
+ 'description' : msg,
+ 'priority' : 'High'
+ }
+ assign_to.add(args)
+
+@frappe.whitelist()
+def make_subscription(doctype, docname):
+ doc = frappe.new_doc('Subscription')
+ doc.reference_doctype = doctype
+ doc.reference_document = docname
+ return doc
diff --git a/erpnext/subscription/doctype/subscription/subscription_list.js b/erpnext/subscription/doctype/subscription/subscription_list.js
new file mode 100644
index 0000000..6a33638
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/subscription_list.js
@@ -0,0 +1,12 @@
+frappe.listview_settings['Subscription'] = {
+ add_fields: ["next_schedule_date"],
+ get_indicator: function(doc) {
+ if(doc.next_schedule_date >= frappe.datetime.get_today() ) {
+ return [__("Active"), "green"];
+ } else if(doc.docstatus === 0) {
+ return [__("Draft"), "red", "docstatus,=,0"];
+ } else {
+ return [__("Expired"), "darkgrey"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/subscription/doctype/subscription/test_subscription.js b/erpnext/subscription/doctype/subscription/test_subscription.js
new file mode 100644
index 0000000..2872a21
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/test_subscription.js
@@ -0,0 +1,32 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Subscription", function (assert) {
+ assert.expect(4);
+ let done = assert.async();
+ frappe.run_serially([
+ // insert a new Subscription
+ () => {
+ return frappe.tests.make("Subscription", [
+ {reference_doctype: 'Sales Invoice'},
+ {reference_document: 'SINV-00004'},
+ {start_date: frappe.datetime.month_start()},
+ {end_date: frappe.datetime.month_end()},
+ {frequency: 'Weekly'}
+ ]);
+ },
+ () => cur_frm.savesubmit(),
+ () => frappe.timeout(1),
+ () => frappe.click_button('Yes'),
+ () => frappe.timeout(2),
+ () => {
+ assert.ok(cur_frm.doc.frequency.includes("Weekly"), "Set frequency Weekly");
+ assert.ok(cur_frm.doc.reference_doctype.includes("Sales Invoice"), "Set base doctype Sales Invoice");
+ assert.equal(cur_frm.doc.docstatus, 1, "Submitted subscription");
+ assert.equal(cur_frm.doc.next_schedule_date,
+ frappe.datetime.add_days(frappe.datetime.get_today(), 7), "Set schedule date");
+ },
+ () => done()
+ ]);
+});
diff --git a/erpnext/subscription/doctype/subscription/test_subscription.py b/erpnext/subscription/doctype/subscription/test_subscription.py
new file mode 100644
index 0000000..28f8be7
--- /dev/null
+++ b/erpnext/subscription/doctype/subscription/test_subscription.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from frappe.utils import today, add_days, getdate
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.report.financial_statements import get_months
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.subscription.doctype.subscription.subscription import make_subscription_entry
+
+class TestSubscription(unittest.TestCase):
+ def test_daily_subscription(self):
+ qo = frappe.copy_doc(quotation_records[0])
+ qo.submit()
+
+ doc = make_subscription(reference_document=qo.name)
+ self.assertEquals(doc.next_schedule_date, today())
+ make_subscription_entry()
+ frappe.db.commit()
+
+ quotation = frappe.get_doc(doc.reference_doctype, doc.reference_document)
+ self.assertEquals(quotation.subscription, doc.name)
+
+ new_quotation = frappe.db.get_value('Quotation',
+ {'subscription': doc.name, 'name': ('!=', quotation.name)}, 'name')
+
+ new_quotation = frappe.get_doc('Quotation', new_quotation)
+
+ for fieldname in ['customer', 'company', 'order_type', 'total', 'grand_total']:
+ self.assertEquals(quotation.get(fieldname), new_quotation.get(fieldname))
+
+ for fieldname in ['item_code', 'qty', 'rate', 'amount']:
+ self.assertEquals(quotation.items[0].get(fieldname),
+ new_quotation.items[0].get(fieldname))
+
+ def test_monthly_subscription_for_so(self):
+ current_fiscal_year = get_fiscal_year(today(), as_dict=True)
+ start_date = current_fiscal_year.year_start_date
+ end_date = current_fiscal_year.year_end_date
+
+ for doctype in ['Sales Order', 'Sales Invoice']:
+ if doctype == 'Sales Invoice':
+ docname = create_sales_invoice(posting_date=start_date)
+ else:
+ docname = make_sales_order()
+
+ self.monthly_subscription(doctype, docname.name, start_date, end_date)
+
+ def monthly_subscription(self, doctype, docname, start_date, end_date):
+ doc = make_subscription(reference_doctype=doctype, frequency = 'Monthly',
+ reference_document = docname, start_date=start_date, end_date=end_date)
+
+ doc.disabled = 1
+ doc.save()
+ frappe.db.commit()
+
+ make_subscription_entry()
+ docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name})
+ self.assertEquals(len(docnames), 1)
+
+ doc = frappe.get_doc('Subscription', doc.name)
+ doc.disabled = 0
+ doc.save()
+
+ months = get_months(getdate(start_date), getdate(today()))
+ make_subscription_entry()
+
+ docnames = frappe.get_all(doc.reference_doctype, {'subscription': doc.name})
+ self.assertEquals(len(docnames), months)
+
+quotation_records = frappe.get_test_records('Quotation')
+
+def make_subscription(**args):
+ args = frappe._dict(args)
+ doc = frappe.get_doc({
+ 'doctype': 'Subscription',
+ 'reference_doctype': args.reference_doctype or 'Quotation',
+ 'reference_document': args.reference_document or \
+ frappe.db.get_value('Quotation', {'docstatus': 1}, 'name'),
+ 'frequency': args.frequency or 'Daily',
+ 'start_date': args.start_date or add_days(today(), -1),
+ 'end_date': args.end_date or add_days(today(), 1),
+ 'submit_on_creation': args.submit_on_creation or 0
+ }).insert(ignore_permissions=True)
+
+ if not args.do_not_submit:
+ doc.submit()
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/templates/print_formats/includes/taxes.html b/erpnext/templates/print_formats/includes/taxes.html
index b180c1c..b782763 100644
--- a/erpnext/templates/print_formats/includes/taxes.html
+++ b/erpnext/templates/print_formats/includes/taxes.html
@@ -19,7 +19,7 @@
{%- for charge in data -%}
{%- if charge.tax_amount and not charge.included_in_print_rate -%}
<div class="row">
- <div class="col-xs-5 {%- if not doc._align_labels_left %} text-right{%- endif -%}">
+ <div class="col-xs-5 {%- if doc._align_labels_right %} text-right{%- endif -%}">
<label>{{ charge.get_formatted("description") }}</label></div>
<div class="col-xs-7 text-right">
{{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
diff --git a/erpnext/tests/ui/make_fixtures.js b/erpnext/tests/ui/make_fixtures.js
index 0c5b4be..f817c65 100644
--- a/erpnext/tests/ui/make_fixtures.js
+++ b/erpnext/tests/ui/make_fixtures.js
@@ -205,6 +205,18 @@
{title: "Test Term 2"}
]
},
+ "Item Price": {
+ "ITEM-PRICE-00001": [
+ {item_code: 'Test Product 1'},
+ {price_list: '_Test Price List'},
+ {price_list_rate: 100}
+ ],
+ "ITEM-PRICE-00002": [
+ {item_code: 'Test Product 2'},
+ {price_list: '_Test Price List'},
+ {price_list_rate: 200}
+ ]
+ }
});
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index 25cbc2d..9bb520f 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -126,4 +126,5 @@
erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js
erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with_serialize_item.js
erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js
-erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js
\ No newline at end of file
+erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js
+erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js