diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 57e83b0b7a..19f4b56928 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -1337,6 +1337,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1382,7 +1443,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-06-13 14:29:09.794076", + "modified": "2017-08-31 11:21:09.442695", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index a59223d3a8..abf4ac9b15 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -1659,6 +1659,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1730,7 +1791,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-13 14:29:04.244537", + "modified": "2017-08-31 11:20:37.578469", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 0850b3dded..a089090a6d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -46,6 +46,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ cur_frm.add_custom_button(__('Return / Debit Note'), this.make_debit_note, __("Make")); } + + if(!doc.subscription) { + cur_frm.add_custom_button(__('Subscription'), function() { + erpnext.utils.make_subscription(doc.doctype, doc.name) + }, __("Make")) + } } if(doc.docstatus===0) { @@ -343,6 +349,7 @@ frappe.ui.form.on("Purchase Invoice", { 'Payment Entry': 'Payment' } }, + onload: function(frm) { $.each(["warehouse", "rejected_warehouse"], function(i, field) { frm.set_query(field, "items", function() { @@ -370,7 +377,12 @@ frappe.ui.form.on("Purchase Invoice", { erpnext.buying.get_default_bom(frm); } frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); +<<<<<<< HEAD } }) +======= + }, +}) +>>>>>>> develop diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 691ee45b3a..5051710298 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -3440,6 +3440,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -3450,7 +3511,7 @@ "depends_on": "eval:doc.docstatus<2 && !doc.__islocal", "fieldname": "recurring_invoice", "fieldtype": "Section Break", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -3889,8 +3950,8 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-08-16 17:10:30.248741", - "modified_by": "Administrator", + "modified": "2017-08-31 11:22:47.074420", + "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", "name_case": "Title Case", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 06a4974ea1..180ad8900d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -687,7 +687,7 @@ class PurchaseInvoice(BuyingController): if account_type != 'Fixed Asset': frappe.throw(_("Row {0}# Account must be of type 'Fixed Asset'").format(d.idx)) - def on_recurring(self, reference_doc): + def on_recurring(self, reference_doc, subscription_doc): self.due_date = None @frappe.whitelist() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py index 6141db5d3c..062a2d2c64 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 @@ def get_data(): 'Payment Entry': 'reference_name', 'Payment Request': 'reference_name', 'Landed Cost Voucher': 'receipt_document', - 'Purchase Invoice': 'return_against' + 'Purchase Invoice': 'return_against', + 'Subscription': 'reference_document' }, 'internal_links': { 'Purchase Order': ['items', 'purchase_order'], @@ -27,5 +28,9 @@ def get_data(): 'label': _('Returns'), 'items': ['Purchase Invoice'] }, + { + 'label': _('Subscription'), + 'items': ['Subscription'] + }, ] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 4b917ce27b..c7258a4945 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -86,7 +86,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.make_payment_request, __("Make")); } - + if(!doc.subscription) { + cur_frm.add_custom_button(__('Subscription'), function() { + erpnext.utils.make_subscription(doc.doctype, doc.name) + }, __("Make")) + } } // Show buttons only when pos view is active diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 0af17335d8..ba6421937f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -4273,6 +4273,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -4283,7 +4344,7 @@ "depends_on": "eval:doc.docstatus<2 && !doc.__islocal", "fieldname": "recurring_invoice", "fieldtype": "Section Break", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -4786,8 +4847,8 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-08-10 18:02:44.286951", - "modified_by": "Administrator", + "modified": "2017-08-31 11:23:08.675028", + "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", "name_case": "Title Case", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index afed3e876d..f16e4b4327 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -107,7 +107,7 @@ class SalesInvoice(SellingController): def on_submit(self): self.validate_pos_paid_amount() - if not self.recurring_id: + if not self.subscription: frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self) @@ -819,7 +819,7 @@ class SalesInvoice(SellingController): for dn in set(updated_delivery_notes): frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified) - def on_recurring(self, reference_doc): + def on_recurring(self, reference_doc, subscription_doc): for fieldname in ("c_form_applicable", "c_form_no", "write_off_amount"): self.set(fieldname, reference_doc.get(fieldname)) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index bc9d76646b..efd18b5215 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 @@ def get_data(): 'Journal Entry': 'reference_name', 'Payment Entry': 'reference_name', 'Payment Request': 'reference_name', - 'Sales Invoice': 'return_against' + 'Sales Invoice': 'return_against', + 'Subscription': 'reference_document', }, 'internal_links': { 'Sales Order': ['items', 'sales_order'] @@ -26,5 +27,9 @@ def get_data(): 'label': _('Returns'), 'items': ['Sales Invoice'] }, + { + 'label': _('Subscription'), + 'items': ['Subscription'] + }, ] } \ 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 13b34242bf..56181fc8c6 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 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(not frappe.db.sql("""select name from `tabJournal Entry Account` where reference_name=%s""", si.name)) - def test_recurring_invoice(self): - from erpnext.controllers.tests.test_recurring_document import test_recurring_document - test_recurring_document(self, test_records) - def test_serialized(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index c533e6bbcd..a51246bcb8 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -13,6 +13,7 @@ frappe.ui.form.on("Purchase Order", { 'Stock Entry': 'Material to Supplier' } }, + onload: function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); @@ -20,8 +21,7 @@ frappe.ui.form.on("Purchase Order", { frm.set_indicator_formatter('item_code', function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) - - } + }, }); erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({ @@ -86,8 +86,13 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(flt(doc.per_billed)==0 && doc.status != "Delivered") { cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __("Make")); } - cur_frm.page.set_inner_btn_group_as_primary(__("Make")); + if(!doc.subscription) { + cur_frm.add_custom_button(__('Subscription'), function() { + erpnext.utils.make_subscription(doc.doctype, doc.name) + }, __("Make")) + } + cur_frm.page.set_inner_btn_group_as_primary(__("Make")); } }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 16b12eac74..750e65f0c8 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -2948,6 +2948,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -2958,7 +3019,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, @@ -3427,8 +3488,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-16 16:57:51.320375", - "modified_by": "Administrator", + "modified": "2017-08-31 11:22:30.190589", + "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", "owner": "Administrator", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py b/erpnext/buying/doctype/purchase_order/purchase_order_dashboard.py index df10a541df..d57b0e2568 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 @@ def get_data(): 'fieldname': 'purchase_order', 'non_standard_fieldnames': { 'Journal Entry': 'reference_name', - 'Payment Entry': 'reference_name' + 'Payment Entry': 'reference_name', + 'Subscription': 'reference_document' }, 'internal_links': { 'Material Request': ['items', 'material_request'], @@ -23,11 +24,11 @@ def get_data(): }, { 'label': _('Reference'), - 'items': ['Material Request', 'Supplier Quotation', 'Project'] + 'items': ['Material Request', 'Supplier Quotation', 'Project', 'Subscription'] }, { 'label': _('Sub-contracting'), 'items': ['Stock Entry'] - } + }, ] } 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 0000000000..e9db270b4f --- /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 3767248e36..3899bbab11 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -22,7 +22,9 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext cur_frm.page.set_inner_btn_group_as_primary(__("Make")); cur_frm.add_custom_button(__("Quotation"), this.make_quotation, __("Make")); - + cur_frm.add_custom_button(__('Subscription'), function() { + erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name) + }, __("Make")) } else if (this.frm.doc.docstatus===0) { diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index ea3ae74dfe..eed0c15cad 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -2051,6 +2051,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -2247,7 +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 df69063aae..4321f27f2a 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 @@ from frappe import _ def get_data(): return { 'fieldname': 'supplier_quotation', + 'non_standard_fieldnames': { + 'Subscription': 'reference_document' + }, 'internal_links': { 'Material Request': ['items', 'material_request'], 'Request for Quotation': ['items', 'request_for_quotation'], @@ -17,6 +20,10 @@ def get_data(): 'label': _('Reference'), 'items': ['Material Request', 'Request for Quotation', 'Project'] }, + { + 'label': _('Subscription'), + 'items': ['Subscription'] + }, ] } 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 0000000000..7097a6dcb2 --- /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 d368d5a5ea..3b4637274f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,6 @@ from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_d 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 @@ -59,13 +58,6 @@ class AccountsController(TransactionBase): self.validate_party() self.validate_currency() - if self.meta.get_field("is_recurring"): - if self.amended_from and self.recurring_id == self.amended_from: - self.recurring_id = None - if not self.get("__islocal"): - validate_recurring_document(self) - convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) - if self.doctype == 'Purchase Invoice': self.validate_paid_amount() @@ -90,11 +82,6 @@ class AccountsController(TransactionBase): else: frappe.db.set(self,'paid_amount',0) - def on_update_after_submit(self): - if self.meta.get_field("is_recurring"): - validate_recurring_document(self) - convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date")) - def set_missing_values(self, for_validate=False): if frappe.flags.in_test: for fieldname in ["posting_date","transaction_date"]: diff --git a/erpnext/controllers/tests/test_recurring_document.py b/erpnext/controllers/tests/test_recurring_document.py deleted file mode 100644 index d47c5c7701..0000000000 --- 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 0000000000..e69de29bb2 diff --git a/erpnext/docs/assets/img/subscription/subscription.gif b/erpnext/docs/assets/img/subscription/subscription.gif new file mode 100644 index 0000000000..68488053d6 Binary files /dev/null and b/erpnext/docs/assets/img/subscription/subscription.gif differ diff --git a/erpnext/docs/assets/img/subscription/subscription.png b/erpnext/docs/assets/img/subscription/subscription.png new file mode 100644 index 0000000000..8b2cdc3029 Binary files /dev/null and b/erpnext/docs/assets/img/subscription/subscription.png differ diff --git a/erpnext/docs/user/manual/en/index.txt b/erpnext/docs/user/manual/en/index.txt index fff4da7d8e..712ab8eabd 100644 --- a/erpnext/docs/user/manual/en/index.txt +++ b/erpnext/docs/user/manual/en/index.txt @@ -9,6 +9,7 @@ manufacturing 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 0000000000..e69de29bb2 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 0000000000..24d75eda95 --- /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. + +Subscription + +#### 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 + +Subscription + +**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 0000000000..e69de29bb2 diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e5d9ef9e4f..b2c328552f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -184,7 +184,7 @@ doc_events = { 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/modules.txt b/erpnext/modules.txt index 1edff10ca5..0579cc2119 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -14,4 +14,5 @@ Hub Node 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 97fec8b7da..c1d713eb63 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -438,9 +438,10 @@ erpnext.patches.v8_6.rename_bom_update_tool 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 erpnext.patches.v8_10.add_due_date_to_gle erpnext.patches.v8_10.update_gl_due_date_for_pi_and_si erpnext.patches.v8_10.add_payment_terms_field_to_supplier erpnext.patches.v8_10.change_default_customer_credit_days erpnext.patches.v8_10.add_payment_terms_field_to_supplier_type -erpnext.patches.v8_10.change_default_supplier_type_credit_days \ No newline at end of file +erpnext.patches.v8_10.change_default_supplier_type_credit_days 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 0000000000..03d8eb4193 --- /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/public/js/utils.js b/erpnext/public/js/utils.js index a333ca82d6..8a47df6371 100644 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -127,6 +127,20 @@ $.extend(erpnext.utils, { } }, + 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 5b137c3bad..1863fb2a5e 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -10,12 +10,15 @@ frappe.ui.form.on('Quotation', { '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 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({ 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 bd9814b54b..7b2bf05c61 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -2431,6 +2431,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -2727,8 +2788,8 @@ "istable": 0, "max_attachments": 1, "menu_index": 0, - "modified": "2017-08-16 15:31:23.079739", - "modified_by": "Administrator", + "modified": "2017-08-31 11:22:15.268846", + "modified_by": "Administrator", "module": "Selling", "name": "Quotation", "owner": "Administrator", diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index f3ebe81056..1cdd840428 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -108,6 +108,9 @@ class Quotation(SellingController): 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 f1c41e560a..c6297e22ab 100644 --- a/erpnext/selling/doctype/quotation/quotation_dashboard.py +++ b/erpnext/selling/doctype/quotation/quotation_dashboard.py @@ -3,9 +3,17 @@ from frappe import _ 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 44173cc0e1..31b17970fe 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation.js @@ -50,4 +50,4 @@ QUnit.test("test: quotation", function (assert) { }, () => 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 901e236bd2..00d2121897 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -141,6 +141,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( 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 94d33f0fb5..6ed1176a8b 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -3271,6 +3271,67 @@ "set_only_once": 0, "unique": 0 }, + { + "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, @@ -3281,7 +3342,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, @@ -3751,7 +3812,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-16 16:00:08.404180", + "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 5f904c2e3d..5f828900d5 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.utils import get_fetch_values 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 @@ class SalesOrder(SellingController): 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 a0ed034313..ffce7ce102 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 @@ def get_data(): '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 @@ def get_data(): }, { '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 0000000000..57ed19b696 --- /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 74f51ca72c..84301f5a86 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 @@ QUnit.test("test sales order", function(assert) { {'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/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 46d536dace..e0ee3708f4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -166,6 +166,12 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( __("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 41f8b8493e..980f79b2a2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -3251,6 +3251,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription Section", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subscription", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subscription", + "length": 0, + "no_copy": 1, + "options": "Subscription", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -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 c296d8cb9f..2e150f70d0 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 @@ def get_data(): '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 @@ def get_data(): '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 482f892997..bbc97b8d40 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 @@ QUnit.test("test delivery note", function(assert) { ]); }); - diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index ed647d6c8b..87cde0d3e2 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 0000000000..793cad0f3b --- /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 19cc44a119..4447fb8cfa 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -48,7 +48,7 @@ frappe.ui.form.on("Purchase Receipt", { 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 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend 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 adcea6d76c..7140dbd8bd 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -2611,6 +2611,67 @@ "set_only_once": 0, "unique": 0 }, + { + "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, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index 9722d8791f..9ade1afd8a 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 @@ def get_data(): '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 @@ def get_data(): '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 0000000000..e69de29bb2 diff --git a/erpnext/subscription/doctype/__init__.py b/erpnext/subscription/doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subscription/doctype/subscription/__init__.py b/erpnext/subscription/doctype/subscription/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/subscription/doctype/subscription/subscription.js b/erpnext/subscription/doctype/subscription/subscription.js new file mode 100644 index 0000000000..75e1473b00 --- /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 0000000000..6cfee1e44f --- /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 0000000000..be36211ec2 --- /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 0000000000..6a33638b39 --- /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 0000000000..2872a2147f --- /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 0000000000..28f8be7257 --- /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