diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 57e83b0b7af..19f4b569283 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 a59223d3a8c..abf4ac9b15b 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 0850b3dded4..a089090a6d2 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 691ee45b3a7..5051710298c 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 06a4974ea10..180ad8900d5 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 6141db5d3cd..062a2d2c64f 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 4b917ce27b4..c7258a4945f 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 0af17335d86..ba6421937f6 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 afed3e876dc..f16e4b43271 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 bc9d76646b9..efd18b52150 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 13b34242bfd..56181fc8c6e 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 c533e6bbcdd..a51246bcb86 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 16b12eac749..750e65f0c8b 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 df10a541df2..d57b0e2568f 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 00000000000..e9db270b4fd --- /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 3767248e369..3899bbab114 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 ea3ae74dfe7..eed0c15cadf 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 df69063aae6..4321f27f2af 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 00000000000..7097a6dcb2b --- /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 d368d5a5ea6..3b4637274f9 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 d47c5c77013..00000000000 --- 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 00000000000..e69de29bb2d diff --git a/erpnext/docs/assets/img/subscription/subscription.gif b/erpnext/docs/assets/img/subscription/subscription.gif new file mode 100644 index 00000000000..68488053d67 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 00000000000..8b2cdc30294 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 fff4da7d8ef..712ab8eabd3 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 00000000000..e69de29bb2d 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 00000000000..24d75eda95d --- /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 00000000000..e69de29bb2d diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e5d9ef9e4fa..b2c328552f6 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 1edff10ca50..0579cc21191 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 97fec8b7daf..c1d713eb637 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 00000000000..03d8eb41934 --- /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 a333ca82d68..8a47df63716 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 5b137c3bad9..1863fb2a5e2 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 bd9814b54b9..7b2bf05c61c 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 f3ebe810565..1cdd8404288 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 f1c41e560a1..c6297e22ab5 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 44173cc0e1f..31b17970fe9 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 901e236bd2e..00d2121897a 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 94d33f0fb5d..6ed1176a8bf 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 5f904c2e3d5..5f828900d5e 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 a0ed034313a..ffce7ce102d 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 00000000000..57ed19b6965 --- /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 74f51ca72cf..84301f5a86b 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 46d536dacea..e0ee3708f43 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 41f8b8493e5..980f79b2a2d 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 c296d8cb9f9..2e150f70d00 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 482f8929974..bbc97b8d402 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 ed647d6c8be..87cde0d3e2f 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 00000000000..793cad0f3b6 --- /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 19cc44a1191..4447fb8cfab 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 adcea6d76cb..7140dbd8bda 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 9722d8791f6..9ade1afd8a0 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 00000000000..e69de29bb2d diff --git a/erpnext/subscription/doctype/__init__.py b/erpnext/subscription/doctype/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/subscription/doctype/subscription/__init__.py b/erpnext/subscription/doctype/subscription/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/subscription/doctype/subscription/subscription.js b/erpnext/subscription/doctype/subscription/subscription.js new file mode 100644 index 00000000000..75e1473b000 --- /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 00000000000..6cfee1e44f7 --- /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 00000000000..be36211ec27 --- /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 00000000000..6a33638b391 --- /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 00000000000..2872a2147fb --- /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 00000000000..28f8be7257d --- /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