From 95d8fd38f51eed7cb2fa81cd35e87d052df405ae Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 4 Sep 2017 11:14:04 +0530 Subject: [PATCH] User Progress (#10336) * [user-progress] first cut * [user-progress] Add users slide, remove taxes, make sample data * wip tests * [setup-wiz] UI test * [user-progress] notif test, docs trim * wip * [user-progress] Setup Progress single to update action states, fixtures * setup progress actions patch * rename sales_target field patch * [progress] wip reform slide data * [progress] remove slide data * [setup] add roles for GST doctypes, remove commit from fixtures --- .travis.yml | 1 + .../en/setting-up/setup-wizard/index.md | 2 +- .../en/setting-up/setup-wizard/index.txt | 7 +- .../setup-wizard/step-10-suppliers.md | 12 - .../setting-up/setup-wizard/step-11-item.md | 16 - .../step-6-letterhead-and-logo.md | 23 -- .../setup-wizard/step-7-add-users.md | 7 - .../setup-wizard/step-8-tax-details.md | 21 -- .../setup-wizard/step-9-customer-names.md | 22 -- erpnext/hooks.py | 4 + erpnext/patches.txt | 4 +- .../setup-wizard => patches/v8_9}/__init__.py | 0 .../v8_9/add_setup_progress_actions.py | 37 +++ .../v8_9/rename_company_sales_target_field.py | 7 + erpnext/public/images/illustrations/shop.jpg | Bin 0 -> 4437 bytes erpnext/public/images/illustrations/shop2.jpg | Bin 0 -> 22091 bytes erpnext/public/js/setup_wizard.js | 269 ++-------------- .../doctype/gst_hsn_code/gst_hsn_code.json | 27 +- .../doctype/gst_hsn_code/test_gst_hsn_code.js | 23 ++ .../doctype/gst_settings/gst_settings.json | 27 +- .../doctype/gst_settings/test_gst_settings.js | 23 ++ erpnext/regional/india/setup.py | 12 +- erpnext/setup/doctype/company/company.json | 304 +++++++++--------- erpnext/setup/doctype/company/company.py | 18 +- .../doctype/company/company_dashboard.py | 2 +- .../setup/doctype/setup_progress/__init__.py | 0 .../doctype/setup_progress/setup_progress.js | 8 + .../setup_progress/setup_progress.json | 123 +++++++ .../doctype/setup_progress/setup_progress.py | 51 +++ .../setup_progress/test_setup_progress.js | 23 ++ .../doctype/setup_progress_action/__init__.py | 0 .../setup_progress_action.json | 192 +++++++++++ .../setup_progress_action.py | 9 + .../setup/setup_wizard/install_fixtures.py | 22 ++ erpnext/setup/setup_wizard/sample_data.py | 27 +- erpnext/setup/setup_wizard/setup_wizard.py | 269 ++-------------- .../setup/setup_wizard/test_setup_wizard.py | 57 ++++ erpnext/startup/notifications.py | 4 +- erpnext/tests/test_notifications.py | 38 +-- erpnext/utilities/activation.py | 7 +- erpnext/utilities/user_progress.py | 241 ++++++++++++++ erpnext/utilities/user_progress_utils.py | 214 ++++++++++++ 42 files changed, 1361 insertions(+), 792 deletions(-) delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-10-suppliers.md delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-11-item.md delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-6-letterhead-and-logo.md delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-7-add-users.md delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-8-tax-details.md delete mode 100644 erpnext/docs/user/manual/en/setting-up/setup-wizard/step-9-customer-names.md rename erpnext/{docs/user/manual/en/setting-up/setup-wizard => patches/v8_9}/__init__.py (100%) create mode 100644 erpnext/patches/v8_9/add_setup_progress_actions.py create mode 100644 erpnext/patches/v8_9/rename_company_sales_target_field.py create mode 100644 erpnext/public/images/illustrations/shop.jpg create mode 100644 erpnext/public/images/illustrations/shop2.jpg create mode 100644 erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js create mode 100644 erpnext/regional/doctype/gst_settings/test_gst_settings.js create mode 100644 erpnext/setup/doctype/setup_progress/__init__.py create mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.js create mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.json create mode 100644 erpnext/setup/doctype/setup_progress/setup_progress.py create mode 100644 erpnext/setup/doctype/setup_progress/test_setup_progress.js create mode 100644 erpnext/setup/doctype/setup_progress_action/__init__.py create mode 100644 erpnext/setup/doctype/setup_progress_action/setup_progress_action.json create mode 100644 erpnext/setup/doctype/setup_progress_action/setup_progress_action.py create mode 100644 erpnext/setup/setup_wizard/test_setup_wizard.py create mode 100644 erpnext/utilities/user_progress.py create mode 100644 erpnext/utilities/user_progress_utils.py diff --git a/.travis.yml b/.travis.yml index a70062fea3..92c15e0319 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,4 +58,5 @@ script: - bench reinstall --yes - bench execute erpnext.setup.setup_wizard.utils.complete - bench execute erpnext.setup.utils.enable_all_roles_and_domains + - bench --verbose run-setup-wizard-ui-test - bench run-ui-tests --app erpnext diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.md index 244d893c7b..c05c4af5b6 100644 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.md +++ b/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.md @@ -1,6 +1,6 @@ # Setup Wizard -The Setup Wizard helps you quickly setup your ERPnext by helping you create your company, Items, Customer, Suppliers and will also setup a basic website with this data. +The Setup Wizard helps you quickly setup ERPnext as per your locale and sets up your organisation. Here is a quick overview of the steps: diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.txt b/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.txt index eb7655826e..b2f680a388 100644 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.txt +++ b/erpnext/docs/user/manual/en/setting-up/setup-wizard/index.txt @@ -1,10 +1,5 @@ step-1-language step-2-currency-and-timezone step-3-user-details +step-4-two-factor-authentication step-5-company-details -step-6-letterhead-and-logo -step-7-add-users -step-8-tax-details -step-9-customer-names -step-10-suppliers -step-11-item diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-10-suppliers.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-10-suppliers.md deleted file mode 100644 index 364b4d6982..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-10-suppliers.md +++ /dev/null @@ -1,12 +0,0 @@ -# Step 10: Suppliers - -Enter a few of your Suppliers' names. - -Suppliers - ---- - -To understand Suppliers in detail visit [Supplier Master](/docs/user/manual/en/buying/supplier.html) - -{next} diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-11-item.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-11-item.md deleted file mode 100644 index 42d7e3d26d..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-11-item.md +++ /dev/null @@ -1,16 +0,0 @@ -# Step 11: Item Names - -In this final step, please enter the names of the Items you buy or sell. - -Add Items - -Please set the group of the item (Product / Service) and unit of measure. Don't worry you will be able to edit all of this later. - ---- - -## Thats it! - -Once you are done with the setup wizard you will see the familiar desktop page. - -{next} diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-6-letterhead-and-logo.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-6-letterhead-and-logo.md deleted file mode 100644 index 0286a3ee23..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-6-letterhead-and-logo.md +++ /dev/null @@ -1,23 +0,0 @@ -# Step 6: Letterhead and Logo - -Attach Company Letterhead and Company Logo. - -Company Logo and Letterhead - ---- - -### Letterhead - -A letterhead is the heading at the top of a sheet of letter paper (stationery). That heading usually consists of a name and an address, and a logo or corporate design. - -Click on the box ‘Attach Letterhead’ . Select the image file from the place it is stored and click enter. - -You may choose to skip this step if your letterhead is not ready. - -To select letterhead later through the setup module, read [Letter-head](/docs/user/manual/en/setting-up/print/letter-head.html) - -#### To "attach as web-link" - -For any attachments in ERPNext, you can also attach as a web-link. If you are using other tools like Dropbox or Google Docs to manage your files, you can set its public link. - -{next} diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-7-add-users.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-7-add-users.md deleted file mode 100644 index c92721c91b..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-7-add-users.md +++ /dev/null @@ -1,7 +0,0 @@ -# Step 7: Add Users - -Add other users and assign them roles based on their job responsibilities. - -Users - -{next} diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-8-tax-details.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-8-tax-details.md deleted file mode 100644 index dae88e4432..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-8-tax-details.md +++ /dev/null @@ -1,21 +0,0 @@ -# Step 8: Tax Details - -Enter any three types of taxes which you regularly pay. This wizard will create a tax master which will calculate the taxes as per the tax-type. - -Tax Details - -Just set the tax name and the standard percentage levied. - ---- - -Some examples of tax types are given below. - -#### VAT - -A value added tax (VAT) is a form of consumption tax. From the perspective of the buyer, it is a tax on the purchase price. From that of the seller, it is a tax only on the value added to a product, material, or a service. From an accounting point of view, by the stage of its manufacture or distribution. The manufacturer remits to the government the difference between these two amounts, and retains the rest for themselves to offset the taxes they had previously paid on the inputs. - -The purpose of VAT is to generate tax revenues to the government similar to the corporate income tax or the personal income tax. For Example: When you shop at a departmental store and avail discount on the products, the store charges you 5% extra on the total bill as the VAT. - -To setup VAT in the setup wizard , simply enter the percentage amount levied by your government. To setup VAT at a later stage read [setting-up-taxes](/docs/user/manual/en/setting-up/setting-up-taxes.html) - -{next} diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-9-customer-names.md b/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-9-customer-names.md deleted file mode 100644 index e3489433fc..0000000000 --- a/erpnext/docs/user/manual/en/setting-up/setup-wizard/step-9-customer-names.md +++ /dev/null @@ -1,22 +0,0 @@ -# Step 9: Customers - -Enter your Customer names and the contact person from that organisation. - - -Customers - ---- - -#### Difference between a customer name and a contact name - -A customer name is the name of the organisation and a contact name is the name of the person from that organisation. - -For Example: If American Power Mills is an organisation name and their founder Shiv Agarwal has installed ERPNext on his system. Then, - -Customer Name: American Power Mills - -Contact Name: Shiv Agarwal - -To understand Customer in detail visit [Customer Details](/docs/user/manual/en/CRM/customer.html) - -{next} diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 7e65fc9262..e5d9ef9e4f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -27,6 +27,8 @@ doctype_js = { # setup wizard setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_complete" +setup_wizard_success = "erpnext.setup.setup_wizard.setup_wizard.setup_success" +setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test" before_install = "erpnext.setup.install.check_setup_wizard_not_completed" after_install = "erpnext.setup.install.after_install" @@ -34,6 +36,8 @@ after_install = "erpnext.setup.install.after_install" boot_session = "erpnext.startup.boot.boot_session" notification_config = "erpnext.startup.notifications.get_notification_config" get_help_messages = "erpnext.utilities.activation.get_help_messages" +get_user_progress_slides = "erpnext.utilities.user_progress.get_user_progress_slides" +update_and_get_user_progress = "erpnext.utilities.user_progress_utils.update_default_domain_actions_and_get_state" on_session_creation = "erpnext.shopping_cart.utils.set_cart_count" on_logout = "erpnext.shopping_cart.utils.clear_cart_count" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7ec2753801..c14cbab5f6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -434,4 +434,6 @@ erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager erpnext.patches.v8_5.remove_project_type_property_setter erpnext.patches.v8_7.add_more_gst_fields erpnext.patches.v8_7.fix_purchase_receipt_status -erpnext.patches.v8_6.rename_bom_update_tool \ No newline at end of file +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 diff --git a/erpnext/docs/user/manual/en/setting-up/setup-wizard/__init__.py b/erpnext/patches/v8_9/__init__.py similarity index 100% rename from erpnext/docs/user/manual/en/setting-up/setup-wizard/__init__.py rename to erpnext/patches/v8_9/__init__.py diff --git a/erpnext/patches/v8_9/add_setup_progress_actions.py b/erpnext/patches/v8_9/add_setup_progress_actions.py new file mode 100644 index 0000000000..25698cc167 --- /dev/null +++ b/erpnext/patches/v8_9/add_setup_progress_actions.py @@ -0,0 +1,37 @@ + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(): + """Add setup progress actions""" + frappe.reload_doc("setup", "doctype", "setup_progress") + frappe.reload_doc("setup", "doctype", "setup_progress_action") + + actions = [ + {"action_name": _("Add Company"), "action_doctype": "Company", "min_doc_count": 1, "is_completed": 1, + "domains": '[]' }, + {"action_name": _("Add Customers"), "action_doctype": "Customer", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Suppliers"), "action_doctype": "Supplier", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Products"), "action_doctype": "Item", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Programs"), "action_doctype": "Program", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Instructors"), "action_doctype": "Instructor", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Courses"), "action_doctype": "Course", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Rooms"), "action_doctype": "Room", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Users"), "action_doctype": "User", "min_doc_count": 4, "is_completed": 0, + "domains": '[]' } + ] + + setup_progress = frappe.get_doc("Setup Progress", "Setup Progress") + for action in actions: + setup_progress.append("actions", action) + + setup_progress.save(ignore_permissions=True) + diff --git a/erpnext/patches/v8_9/rename_company_sales_target_field.py b/erpnext/patches/v8_9/rename_company_sales_target_field.py new file mode 100644 index 0000000000..8c54283ed7 --- /dev/null +++ b/erpnext/patches/v8_9/rename_company_sales_target_field.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + frappe.reload_doc("setup", "doctype", "company") + rename_field("Company", "sales_target", "monthly_sales_target") diff --git a/erpnext/public/images/illustrations/shop.jpg b/erpnext/public/images/illustrations/shop.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f92f7dbd4ebf41a7c4c603b4a6e526d01ba73539 GIT binary patch literal 4437 zcmbVP2UJtb)}AB~sz#6!AQ+@b??_P~AX0*~fOHXQDsZuY(j^oTMWmw?g-DT(fG8q1 zkSZbyNV`BNLa3tj5_10Ny}x(8x7NSb|K6UpXZBfV?>+O)*?Z0&XaE`m`-~0n1^@;F z;0(P1XdJ}p2YQ|bz|<5-0RWf*Ba9Ou=n{%aks@dONi z>p0NIpLB-KbnCxI1PS(!M-u$E_BILeTMnjc185PbTb#dm<)YvDi8X0|pL8&V!1&$o&KtCgICmN)bs9 znMICOHE>&g{w}KQ>Kn`2}~}R_X{gUFmoMCdRW!KBBE^ho!iy-Gb@j% z%DC8%pU{3G`~Lxp{I8Jx2KEoGKY<=#Mk1MzjLb|-Oe`$Stn6HS*xA_F`8fBZxCHsJ z2L$;s7$H$PaUo$D5e!B`O;ScdQAI@sE3ToXuB0iatfB62FLpc76@*7dJv65DgqgQN~Krd+jE2EE-#K)s^qFUm(s$ zY-c9wGGpgbt&3FS3arK5&Qx~t33}gu6R%K}OA;7%kALe2fxaTRoc5F_zwDY)wxr>e ztM*(MDy-dX64|c`S$6~q2M!J$KKkG$Q{_iofu!YSe~cn4e&*uW;BFpxRr5>SN~cuC zLk!`az#=O{X-}+i(&-TgieZw0cbZ05@i#xitvPS=QtfGZbiJV2x63Cor^^T?E2ZrB zOW`4K#e`0Cimhh)$_6jixU<`HV9?g|bW4k2xj?kbuzj;83a!I}wqz3O3odfEkwPdi zBMl$3yIz-kmF)$EKkIV=!e0!7KL4}Nk}M@phE3v& z**!%P&G=1UqTX)Dg34}2cKlmHvbVPV5OW*}b;OnDuy!G7d#@>8d}xH3=X<4=VYVTy z!&U*fb<&dMwY-IFmqE?|4$6`qz8t@MmoUB15+}0u0r5-r}tX)0A>4reTM%auW{fzN$27* z2qaJ(xTR(?owXE=yWXlQKB>i(Hs6tbqDRAMn(sbBAA%suc#j)u-I#6~+IVyDgp4>(BVMnwOe zs930*9wLuaZkE_pf~$wZvHE(LA0LPT!mp1kYBU?~0VjLK14Re|2GvFl`#y^&KYXIbsGX(W8_VR6bRhP+r`lWlOS-J1-F@5=w6$MT>jSC?hC(R z=;1dKg{{xgZG9wd#v(_#bHeTfTY$Zw;RN%?^U~R*+n6cafUGX()&5;|2(Ws$`WJHK zrLB3-GOZSmCstjuXqg-ExNzxO;9{qpRZ{Du&a3TowvN$|4GWA3{G5BN)`PG1*-um$R7i(#hrKcmz##V-2x( zxHv#Q#ao8NNx;LaAKq@iBQmy#Noq0ol@iZYE|r!>9o&dXfa}j@Uk_j!swwx_Z#XTd zqm^J05@qixY?PlT4UCQkf%v?6o3c~oO+{hzMVGE^V`r_*OlR5(wBtStN{}r39*x`E zx<{2HHSm;1ZEAV-InBAsnm*}pgl!>7NI_9$9=QTRYU3mBt_#ZjPY>a`rzW$B54;`^ zV|DG%(Qsl^;sfN>Fis8E{iC{=G7}SluS~G?y|jTYZns_5hV}I1LoO-Ra)T{+M3t5o zOTL}x4gZ5SY8{F*FtjM`%KVq=7t*4&5pZe+LpYM}BWmKPg{gpV;-doT;o}81>gfq} z8EQPIiY5y~dow+zdb?}X6hr4ZkE)-N=Cs2Q$y!IPc$^f4VkNBejqHx3yNa_Tm5i3G z1ZJ6=j)V>@oGMA^5T&K^Ysifs;~9uFi)7ll{?kZ&$)BkmPd-I61Ppy zsLc57lIOg>dFQZ+syX~2wV(&buMUy>1Bk|C!g7SJYqw%!a`0YH&twP`6E{qOOT$Ik zVc#G#5{q>@k4s%>Rm;h!(Fz%Zk18ve>a1ufCCBq3=yr__s^<&w?ToNu2;}yac{$HL zgh0hCbp}2`xu{a}yN38xOJJei4XxuBdMZw`=g2-^o2a@ly}V>;^d(FBef;sL$Jj+! z*Ym@lt0VId=*~&QCsd+MUoN=a#=S_tTq~%tpgT#9jg6hYYW>;J?8Tiwj=j5aHf`Pg zdr-UUHK3XEqx_ZTz4{b%qX8*pIGb4DyR9VFeh>GugZdV=mfisNb_}nJZZ!Mzha#o@ zW&A{{&S}$fZUCy}F)e(dz|nh*vO)#rdm^hV@Csf{s~lt4O(qDOe(EjdFYi$b4|EzM zlB;z1g(ehNx$o}0?h%pHO*Rk+cwAX-&8L5$@gAC@!jk_bBk|AlGwDXFVsADxU7JeY zmid<$9)mFuC!d~OvQpg$GdpO(r8@hFqf$c)4 zUSa1yxWw!}ai}ZK8PS7)(Cnc@dwHzvwh+24(Qhl^d+FVc$T5tx$m3CiIb2scD?T#$Zu1MN zL#a|z=@No?ElJJ^4c;pkDqa`qmPlmAdOaJvcIODaT>Y}K9oLc{715WUuO)$y*T18G zbL-gZY^zrfYw3o86q}vqosL&o;+hf7c;9Jt!TyqB;czi7YZputvGpC1%yweR6au(C z5D1k8c8m75@)*_ro%TPa(?l4OjbU%G#`XUw*k-nWH6UDRv#;gkxTdJX!!xawcMG@l zCox)nNJJ|i0&je1+@~By-LOMSL^A!E)wz0<_{qS0XEkSBhq1TnB`2eO0Jjjwes|0@ zX+hykRUMH;VEBQdQ2XVD=05~ZbZs-YLm%C%#A?4f zUpE*q=bLK7&fAtyJFc_uhBEJS@k3@YyE)_7{vU-oJ>6x^g+Z64Sh83FR$|d?hiO+8 zx6xKsVETlmJN3c)bL6LnGB?OR?;}GibuBb5g@mj`O6x7)RtM<2!e%ji?2O&8Hnpup zPh!SY%(UFXAT{@!j@nV)v|0_y;I)|AIZsyh3S{>YrSFFHgqG^zsf0klTEUKt^T@8} zvhjw%^{nR|d?uAHm7;o2U%!5OAvd1urfPG`WXjsCW#7#KsPC^kfP3+@L0hnbP|pmB zfe>*bR)PeNBk0qwZT~kPlAubSCo$xGFUSo?HIgzJ9#}i)R$gp6xoUy1eXO$Zwz*f2mqK)<9UMV0?uTmKDDD2RXOFU)~wARtUyOA!%8DG?DuMJIc6OB*vF zAkB~z4@gy1xkbC#De{3tXK|+)Ez;BMpmJKSAQ@y;Q9;u3a4;o7R74@vXi}tj!bnME zAtgj`FjO>9DZy{xrPq(0pIoomYiG}s^~{Tlvd*fiGIQWPQ{s>i@O}UgSeR(MZ4BT& zTTl1>&K?+85ePRPB$9OYjuFq=+Sxx<;Pr<-N(5-5OCV{Z`MEdm#{jlF&8>|BD4s2n zuWNuWNXZ)#2rkP4lM4jMhJzX-JQiwm=#kQ#xCjim&xpO@mfd`F2!B)4NoI6M9btF_ z2(es>w2=XbHLL5rkk?0vhm}$SS0p^m52_M7?m#csmy`jBQ(F;ld~a_~jyKF$DekBK z@WX(e?|XwZ!Taany1@o5u*c6`6OroV$C>+kmyDD0L4km0PWoDcy=5Dhh1>o(pujvU z9eX&22tQSw(p>PK#IKI-OOE#d!}97*$8Uy37a@nG4+IFVOY*Nd<%uwpjA?$*j6JOa zvHc&hXCs*(D3p)gwIE!pk4=7DG-OlD?@js0yw-67?|z=GzMHt%xZVQjNF{}!+o8Yf zJhqR)n?;{^bV{;82R(O!JWac{qsCuPv_EI@B(@w}u!V172-N@INf{q8mxg z%NQ51;jY1X-S@6KL+dz@da4E8=jw(#L;ZsCLWc2F039WA|^9nC`)L&Fi($y)O<}UP?iE{sC>+Ly7DXdfRpF&{60XLZvqNq1o<{2F|M~ zQ_`UW>Gwi~G~s0IWW>gFeyWU7_lTwzzgFPk-w^0NfOGiEX_8GOf5m{HknDn}J>K{8 zL&zd>yK{EN-X8pkH7M{>f7|f2=C*da=6EHu2Swgm{kgJnnkV>;;mmIcJfdVB+ADDy zu#x>$6oe$MMPmeD%B1v5<_QHK1TJQbJd*@4YM-qoE1vr?M}JOU=e2*j`DIoAIP>-% zvmUTY_iDL+>S1;FYX7tpz7jYeh3rg!*F+Q(wkF6Q0Y6|`idq2^EJQC6ZI6A)eXgRA zxAN{Q*th6xfhh5O`vFX8%q+}Szn3}A-F~tj(eU?u2#uXt&O$lxjTeYq062pK;CFzj333#8o&i5$UWDlj+ZSk^ z;XI*;8(O79pM-`OVQHeR1WX%xxg%V`-iDd%lfI#5Lt+fB?qRlR~H2xHH=_=ZN4IFG?Vgc%Lp85Jw;vW{>X@-lre zIi{neKc+j${wl?LgnLA1iF*$rGj6X()^w?cUctJIiWwm{ac(eM>s*5e?8+KpICA2` z%7c{!-pF9+0^JbafYl;u#8~$~ACf!3y7T7ZF$Am*>Fi6rL3uNO<9=iJUE)qz)!3u?^cwTyDC;Ftt-B(467U~ZRdN6KBroRWCmzwYsYQGa3}sn zeMY-Lzero!jSwwlRLVM&jwZ|{R!7_nHyJX+7RR>4M!{ytBw>ALWn%?p&9kKX9rD}d z_v^yQ!pXu;>2*m?QBgih>0%Md{P6to;_~9JMY=hbg~@rHdC0}Q{9fswbl*_mS;8s9 zzP;i2A;p2Y5yt*-Mi|BmCQoK^=2T`^MtP?FbkTI#bddCgj9Qjw#%UH7MjB=wW&*<* zi=J8DnZSCeru2HS2Gja2V=UwM@rmim!Hen43GHF-zK)@h@zUv&>C_SR>6h$l3Lb(^ zU@t;1axc0MO2|-=?7>iy%tH_f$WdGhlro4i3JdI0x)~(;beZhQ)JsAQXbp~45DtV^ zBsa`gt{e6nE_`%++gzYrn_Ti-Eq(DFy{_-B^{(kIg17W{z+2$k!`sID$$R@-%sa_D z!~N@B-3{XUem#ki#B;0?ur#n5FhPJ#fO>$3kiHOXzer$8Ac-)laHX)Xu*JWMr3Q-y z=LE;WuENZQ*GHs>4@Ig*cA=9+T1DJo(4pg@4n|Z&f=8l7K1U25NFVqfydQKU*9SKS z_lg#X_L8bA%Q1^KYC35~YD-H{OIIpi)UZZ1w*vwJHBS~;LRcCZvKjIj2945A8=<=^i^h)_^NJ>%io#jrOYOCqBUTbd> zb31m7y3u_^cO?bndPn{H`}+5~`qTH}MGOX|2R%s0ih{)HMl?-W4rLFGQ0!0?l3_S}kc0dG39<$=+76leQbT6Y&y#!CiZFXBged?|yxqzahEqypDBobLze5 z5pN_$OG4+B<%Qur;d|zt_4IvOc$s*~d|r9tKjgbEI(Hti7|~edm~>lFm}FSBpD~#I z>H1j(I`l8}FA=QjqY1zfk{4R;4>o+=`->PF-xvR)m^%kr@MVN-G;VbGvHW^^l}C}4 zFgZ|g$G7i-BztBp?^gW#s9~&Csk88*D76T$$etiy0MRL*N1T_O&76CM@#R5iN@zBy zChQFXkMHTFer8|BQ88OW_Yx_9L`tIcAm(5)P8t6f?_X9S_I=rbIMK49P|};?x8evB z@lom|%f#{V!BMtYj1oiTPZdvPH09ZnZ|O(rOX)PZSvdoqeUHM+;;r$nB%CsD`Dar~ z3ti3_9y}*cN)?)>Pq?p)H5L>tbS<`Z?{&*{0jHN^-WjziI&K*c_J=F?To3(iu72mT z*_xbJo*!NVj@}OcD#Aa86?0V%dx0+@mBcrNPlb6HUkp8Mrhtl4%{I_0D<=6h$;rRah2rwRjYeRh!)e+y+Ig_|(*GWN$)j+HQDpPjTb(v3jqqR4lH4 zNs9LgQAo~P0?@eZ`cytW*~K|2$kj>P>*Z-1i2Tfamjc%WK+Znp@!_&! zc|UADc>Kf<&P5ru?%!kRGnnf`bboRqvo-#u{$_h|*`VD1eS^8Gd5_b;v3xjj__}-1 zd+lrSdb1pSIy5?dH~y!h_;u*DeX(m1jpczQrYW?^hrrb@{V(Q=qYM2z%2!$7Y+>H5 z_x$$)-z~4ebIPa7;bz3`)o$IV;^%yaVK-w7W>;HBa?4bYZckyy!u#U+>66tHwlAH% zo=x|D+Irn8PCpKg5h=6fr5cbB&D9}jUn>m|q8^apmB=pD?-u8}UGM;Q6?hJ!Ki@!bWzd`-c>m*%U)Ft`xpXODnbudpBe@HMe%_ z606-Mj~a?x;?{V%qze``^0g?<>vOI zos5_IE3lh1a%Wg_S!`O(D?fp~L1JFc&kRtHAf*FuIoF%9q;a`H`m?VZvjHd3r_7L| zsfwxxU?rd}Jzp~|h&+rHp=yYVKq+dK%PMs*`78(3@k-O_<$fV~vx3fY`&@)#Cp(+} znd)%!JY;H^1y&1oUBH>iBIA*C7Axmi7Vy zLt~q|sLsrwS8dtg>zVwEE@acyZQwSywxZm&+)HtHWzOkV|8VYvXTz54+(bByPI+Z$tx-F$TdnEcV&9)b6aw9v$@!5Sa{wG zK26552et;vE@pEL&U&|UT66t=p657L>=Ak`KB2S->Hg;Xvc5N&=y{r$Sbu@%<2V2j z$4wd986P^%xXT5ihCr1-EyG)B6>`V?s-e)s^+6IrIizL5i=w89pbPneAH~E#YXzCL z+i~xa`JCp_ho*&6f_{vojK~acj~bOUmD)+ZNX<|=XuW4~*^3Tc+4PJ&Fr3zsoGAqI_i%qRSd(HVCeX)9s)%}t^c*^c~#RE%IV z$;MMxV3;>0*_HL{AtH=oRNiMsle0Uy-QmP*#rvf!yM1fcB5ig)M`IF!ijwB4ol%L^ z%kg3%>mZHnt0az|BZW=>ocafl((5$)C-9P?D#K25M^>n4bwckPyKdvNBLL!xZ+OJ# zHI6NXjiZH?uWiG#OGZJ$}@-Pc{MARew+4JD#>G-$LE)C^XBV%U}D|@yYS=ajGzT9R>YJ z)N%DH^ydxs1W$+gkoiUZ#eXF6?Dw1h9pQTZ(%AoygtnTZAn>tLzADkOVgS-CVm=4E zhSN`kg$XUoz>ZE4lr?NX{-!jhSgq))gr+2cVrzM1Y55!DTz{dJQI*l03D5k+tif#A z6vf2C+~6>MA9v5{p!R@jfBJOtcy=d&D4rIROca?NasS_wv)T z^2gha((}9Ss=b%PH!qi`zdKKL59zFZF>1ql=p%C0*FbHTt=IhL4d_}BaSa*3s>uL` zvVi1CD@$SH;YYR(&eudkTutmNdLnimjU5#$(C4$~8|_6ab2ZjKArcXmb0~U<9@ji< zf%hyor+}(p%p;xxm*|I^C+U%?o#RTJ0}#F^{XQD2z`?RC17(SsVa)bO|HETV&%-W3 zq48=n!8P#S{5dZC0{b9lg|SReg*KTSp88;5&QR1VtR=4`6y=$C!uEPOT2-qd(AnTB z^VD*g!{YW~opGUi0gD}q)9^Z+8J)?e9n0G`x3Tn6>T^isSGGg4J4(@E$B%WPe~Wav zz3tZGca`zEH-4HIcCEh@ch@_CmEs}t$nMGPLw=HU+&YYwHT}H%4biIQFacy*;8A)g z)jR_duN8HY@d{*E0@jxa<=>6%AEyDlRt_T=F#4TKfSBoyJeQpWeh0#oA8wv`(uX#k ziQuowTGR!R*$3o~utEqS3AZ5rIOuRjnu*{lh{6c=IM`8-;f*`}0I`=B4rClKM0`BP z80xPy3T7hB$j|_%$-+uvJt)L!tIJ!97B`%Apm1n__y#Q$>tRM|LA(Jel znyPxdglUCv!Cr{Sl=7A_l3R*24PZV8+V<^`^2GS742WAgz1_c?B9w8l$G<-6g zC$p8*MTkamO`#9otH-C)g)sr1eGd%(rZ`{QbYB9vjZCxD^{SX{iuqsIsCVVs|^`55T zJmK`BU89SBoBvJX3KGjchQ9P=M{mJ)#d&$fWy%(vQKq-8mAa9|SJB7bQR> zJ%ee4_C@Vr@YWuZP}Pu?%oYkHj7TAtTpVK@TbM*TQa`dl4Mpor#-|ieJXiK9d65e; zFRLz{ruVCXvTcmbt8L0n=*bKyET~LKxaUveT$C}-IeARYR)<&#ILk=2bz9*dCEC0% zRB3wD&?woyw^Mk1xQcSPeMY={x}Ui3xm-NtId8@8LoP=&CBQ{<;%_Fq0?lCCVkD-1 zkX{-C?!b&%iwICO$^B5Z5smPIZpm(TZ`#e#Z{6 z5;IO?97d2Sn&7P4s=F&U?P1qmb}V{b8PIFr5P4R>w#<6c8LKh7Zp!J1vv-+%MtzEb z|HS;{X8kfYHa&Tc(_m;!0t#+?`AfRQFU*TG?3f`_x|7_QTx%dh&;q z4AAR?c=I15#MlDb-UAf{mJ&oq#F_}w&PQN`twxaOGrNNlj!P$$l7wxHo$nhm#%8qE z#IwP6#iR)3iNqPc9l$_=3RWB-48ay7B9p}ue9xB7yTJd0{R~>jcF!jVLMLDgn}f4W z{ACG26Ypv zh4MlBCVtQZ=w*uwD=OHU$i2(wG& zj}l5rsDkdc?0xr`RJ4ZF@#sW?)EKq1FiErM^w~;%CGQX^W{f-4r``eYLG_gYr394; zq!Sj>q~l@@q-dn$W*)iRJ8fHdOD__EwwAhM5@z0Z3`xFQ>Ia0OG?gjK%UJYGNsrHu zePwDm`q~4V?WYi@M7>7+k=CZaOmj)m(STFmFu1H@uCDE+^1Qsl8`vIXvv#TYHSqfP zhJux*b*cruS%+^3u;YdMEn4W+cq*Lt2(;J~aEdip%D^sO+ueel!+gy*p|~qcU$+zfdKry<`A@S1ssT{rQu!=6X+8Q zXoZD?T|J6}eMM%@^pFjRPH6Mz(k6!u0EF7LE~(2deYHJ2C zB6PR4v2*5j=Og|v3GRRM|BxAo3I9t3V9iIYDW^y%V((-|$VSgf&q&Mtt%q ztt=}3zu^Dw_=tZ401n&?3~p|2^lmKl_D&WIOk7-C42;YS%*=HEB;OjYbau`p z|E=Wz)+1`>Y~p0;0I;;TBm9qEBV&6P03R{&e+>Qa%YzM{zng89{7k$I|1aAAlILakPoDlq-u}BN z|3&>*6@D0AhW}k*ewaCBmq8#Poe}c>FL*tT+IZrl$!o60=1DqdXu5aguijSy|K%xN z5OHkCee=%Y9$}frL~aFoHupyh`}%ITndM%8f!4glCrGpN-N>79q~tYO*JsKxUh|{; za(EiIXL9x~RT-_9&6`?mNV=MhethvV^+fs{1=c>#WV-)NY{pM-$7*|_S3W~_D}i3X zEP;(~VB+%Sect>w9BhKERuDjyP%IKapIJSO4!Hv{J;5uA0P0mmt(YsK8G_I{v?-IbBI(zyt62Ch2A+qeO?9AP_g9RHiZg$w^q8xi4i$jyKi9q?ntv+UPy=SDp=H<<;q6$5e_MA^7?|!Sb8vZ>~`!9v;DcP8o!jAiHi# z29fip=dD-26Fbfc`7%{6M>73+_oys$FQfgiaQtN0Ur*WVi^%6haTRg+P^5|HbTGjJ z?~vK)>5%@b`6Pucli_wR#owiyq2+m-H9D;}j*jy%A%Q3@rQ`Z7BsTXH(XbrE9kG8h z=_UcJMvw=w)PKN2@H2_M3V4r=D~00C&uFna;Ih$=z1p$)8occX4fBVy8)7w$y*0ey zk_7N--MEHNAH*h)9}u0DF`$EZ;Vn;@)oIbsn!?p8M5y*T^thGgk+y=`mh*J!Wqct* z#k)X~nSfQ<)0)1z`6zB+G3bN0w3gAN2{A;3*1kv4$H%89_#D?TBv3V!qKr)v40(jMih5ss`5s z5T4lNV5#WhsprMOa)TbSIqxqCOoavr7oO%MO`F6VHX7tRjn-R^w)}6{n?k>XH?(W) zuB{f!oD0^)2qCnRyRk_0TOwVRrE-Zc*i}NHXv?g=6pm@)S1RH@C$07<%fwYkttjU= z?EMhV4d~e-(aKYCMzL{Sw=b7(zPzNO#j{wLPwbJJ)NI|6hb_2C8VKf2 zwPyCTO`$!kShdzD)kQerdTduyGC6dcmsxmyK(f#JYw7CNgN6o4+J|~_*PuJOq8th5 zzirBef`dwfk;n;?BMiJ-vuCBA>ODAywm=xe;AUF(TZGUKmE4mOs|3(Zls5@Zqc?U5 zyH}$$UzYbojwM$ggVr8{)~Ql!MYXV32+Z2FC}tWJCJcf2x~>n`R}AFa1*?66vz^c?&9uVhb!(@AF0K6UR_AGpW)wAYfSX+HrqyTl>pguYdp=%5 z+2AYhoH&_vo0xe(m7c3Rnyl4W1)e>Z)n{uq6?|XPQmK-!C;dJaE_kw_9T$HZSda^Se$KX`n+H7QWlnQ++cG_qA(c+rFfEf+N_R1xVsGXhB8$%>`jYN3 z-c;bYin8x*=>5Cd+r;BWpk?nmoXY=qEt9Yw1cQd%Q=Pbm{KT&PH`({Y$H&X?&o@EK z=kY3Ba3kOM;p)qNhzzo*B`8>vQc-5;bQOI#1qD6S>(8LyPhRRfLT`(;z*mOt)pE5Z zIMgrt81=Fs!C$@2<~3H!p4+r}Ld6rCixzQL^CPeyv*iyx?*c!+&016#8fy_>Z-@fd z$u0L!nvQ0UPOBZv$9q06a6a&Seva$>OkVBynjawZ)<8Cj>dq;u5f!;{2a9T#HOJY=qVppl3mGSrp)T-(iG-&r+Zp5ft|2%*zOgqU7 zP_Y|-3tV1TUTJA*vGFUjllyz$gdot;BttXCBP4t8sZ6WIBI^q8XkotcV$G&qus~Wx zauH9jH=92)ejkJ72&$@XR5%FokoVM>W1e!Jey*Bb2^j`^=LQuvthKWtd+5TsdQc*x z?(?+9-r`#0WIpZW(5MT_$5ok{8N7k78ZS~BTHlf|ZB6)ekfBB6zxR@N!~Ba@8)3c> z(pW9#j3H{PiUMvsa%^vVqZVr@vuFwicH#OHE;BO~?Kfcb594q1y&Xw#)VboFMz)Oa z$sAC?i&mTYNanKM=UD5LR8fEDQsFJ48myW^#dx5bUtIr!`4#aFVXjtDK=jlG=!NOX;eilA5ikF&IKLFZ~cJ&%V~ z3A)oAKPy`}cx_Sw>aR3aen$Z$-esrsKC^Av0@HWhaVI+TlxnJG;9*uFzA5#k#iH0; zO~24*h(gu0DJ{BCOAU&kH4$w-Ji&{+!9X#p>v(o?A7r9a#02K!XxqW=U)S;4$29Zl zVjjokvutulRExQYqMge>R1O2T>Fq$8r;LSjM+(g@tYe_eq<)b}X_{a&(=#s?US4(}$h6dhT z{WR=kCoImc-?D{)xH_9#{{^MVG3;9K7UNl3G7(l-Ycd zeF~U}U&hJeF86Z){TlV84pGUSaNh+b1TAk2#<&%-6`V`eEhYKvpCe@v`dfSDaQol{ z8M%}n%@ab*1j)|&U#dCEy%XK?J_RfHKmRPs?;X2?Owe&gBA%u%JpS22Dt1y;4 z29I+qR`bl+4$tAF^61DH!ZFAhgampbpL(@{d5H-V%KX(f9Q!KUCEseAY!;eLC zwBAR>Fj`RTyBm-;*bi%PmW7H?p&x7hO(5uwz@}3gMK09Ar;0_}p4~^|u#AhtGd1no ztct5w@^twWiquDIK&@7t&d?C0*`)1~1ztEHB`3C@hwi9Um)#r^M9Bx(*QXYublh?Q z^HCx@5DEK5fM-1|^D5^J+q+OzW3v$kC-K=BO};EhIWf=QPX}xCft5V=l2i&82}P`s z{%0wFO(%DrN0K+8=dj?#;#^*^+`nqM`(JxIVP1N49tT8kJVYhuZ~QJB1S-Thr=#0l z6zkAFfL!c?xlH9*4{O+|C@D|)foq{KS7o!~Y`)u6!veA5$jWAJG&nn0syjU)R`32Z zB*_zz#g{^cJFF${g)Dg13u4{d)o$kzVg@@>=BHAjzXBDF{*Q^ECE+{bWePlUm|&fb z{sS|pl)?>tY1AwtcS5g8O9Tlk&~k7&V6t>_By8>^s?NVx1IW`qC4K8LNrtK5w{W6h z%1to!FNlPIyc|XFch9r36lPsqPOci#G_ILg)l8d>w*yVI!$k6-G*VX9cz{_f7_GD| z<$;LE;TJ+g+?(3X+^sKHFx+S0`M!-l{eg0pIQ#ls;^nHd=-)xIXg5jB6O*M8eoJv_ zfR!xab{ubEKNCF`<~iyHDbfSlakjF3{dlZ7`1djwWTBoYS-6usx8GeD3@MNGDS&dj zAvhoEg=SrIVY~;xg}PTKVNOO3XKr6#CX+~EhWiXfMy0o)XBBIwC=Um%Fp%`e-&)=H zh|mNsW9)Pd>6H={&kQO7*rlkU&Rv0-YyiG6fbsUnBfo!7G&?pxdUP`{HI6QEV2q*g zF$DV?qU`PycLd2pl%T-V9Ad$Wk+UogRFXu_Pl5~F3qSWpfLe)Gpg=UGq$hzvwHjYM z?PavSzVx2s;f1RO1Of{(f?|&2WGIIazOO^m3)vskj&B&TkXLl<7q5=vK(FiRNxY@Z z!9XDlQrLIsR{l34t|%r_e5&EE8MCfbnMlV0k~)nd@jDLfptLeUSLZ3sxdLrUy4 zz(I!3fCL|%dd zs{NgKSw{jJsZP-u1UQ+h5IyD`v#wlc^Jd#Ks0C@(PXoGR2VQ_jxN&FYxzlpLFlb_> zv~8v_xN*5Bpa?2^Vqwl^;`DkFZ3*XzT<{UzQ7$W0lCOC8J2k>9 z5Ay)xWR!a}0Xe*_{TaC#c0i?1nUEdHJv}#sSD;Wjl^#??w2p|7E)zX7ar#+-3=Y?K zVEEd(926%elYsrfE1Y^1QZbQ#{K9_exTJy#v7k1!T{=-w1Daaj8b<9lC=!sJM(2#* z&C61z&UC`s7E5fOQsaO&!+7T^GWSGM1boh|I$YsLRaQIn;9j_?D%3x0LH%CrNfFu> z7S94>i&(tq%8gWZsb@C|M(Tm@JHXPfjZ9xSzr;t0kw+o5;DHykCW$|R5}vXy99+P0 zT3r+a!zCGulf38`w>(rP{b2gJHxINRxFBflY&BcwhgUlwB8((tK~ODD#R+$$x*W8n zANMy_!YALK(iJbmH{Q9>+eCf~y47hf4^AUz7txU;{pHmqOB@Q$9hNm~0{>hJDkd{p zsF@i8!)ITL-XpR1>{z6>O5A&FoThaJnm6ln>C>3o2$fr_53fe&j>p?sZ6S;MZ`*CrJVGhr68PqRAF5BLZ z5+J3|a+yHUd>n4J#toIQ`YfHe z@i^&N1~f^Q{%MIxndOml(t@_Z;R#+0Z%t-m9fbYSB#_NCC$rLeL2{VS*5>-J!T}X~`Mp#QCSqHG zWmR2-jB+(%qA@-RMb6v_WPXKKc!Ew00)%zI4M*nGZ(6cUm@Ro*q1ueU`k!Xv?+%M> z+f*s`8@27w$g-N=mKx?thRLc1hON^*5IHc`+cRU!2p&+9%bP|aq*9n4juWS+8I0E} ztpDWvf`hUoS4>h3lRoWW#BOgfhiF`RY0Y+rL9~p^I?3AHj+bUIN<6+&;;L4e1%ND;F`kRulGj_$ z4i z(zQKAbs#rDEq_3@7%6nL_)N^` zm-y3oX$Q}=36VzS!*46i+70iFod>NPYFw_QX%vv`z9tm}{_y;KdU-irw&YNm+h$Pt z#pcor0$_PL2)0^Z`b+gX+D<(0ad~3%j44kyDdKD(i(bKLMZi0~v>AefuTHorzjh zvP{9j>7T8r%YbkCHP|ZH`mE74_7cv!WNlquKg?GYs=q$_1 zYFW40gcNv>Py^KGoxhRcW=%VOe3tdr^ z0+J>VBX)d*jY^e}|2l{gX#7=M$3v)17<4K7{xXxh?ej8)zTa_5uWt;RFrL-+pgWIF zG_w2kwQF+JcnQJt=&*(BxLkwra%}%oSHpAWeGF(N&^>f|NTTE8jhi! znj;u44ZB6{)Af88z_!0eFkdE|n~O*vGqtg9R>kjY+}it%z8Q#Ug>(~xsn);r80Gb9 zd)N=!*{0Y6;^b-OILwTTl0GP03-vXl2Z3yu^4$sk*{sw%dVHCskhifunRZQ#UQ<4T zk+LYfHb8Ztk2>(gjW55YGZP~fn2oRUdNPTzQeotjZuvYs7^ZHh)Ee2o{mybp+t%h? zrNgBA)A4-VyYbk$yVm=`@Uv`1OPIGch8ni+Gzs=BN}cJYbv^jtn0CAlZS` ziEM1Z>l<#{vx#{gut}78MEL!%6(kG zav`X(SBxB`sObdPVBPu+n7rxrHug&Fxs=l>osF{4xVoUo(=l;dwEMnqeK|Myk;oG0 zgx6c-AF98ONqf0lk>e3uS(O^&W%f>8w?d=uow_OyAR1(|dOvWcCA52W_} zQJBacGE9aGPT$ek6B}COpXVv^2B{{Se@&t=NJKLQ22QJGX(ATX3<I^H>RCI3H&qYChdbEO9~m@5vyxkTE#!J7p{{8Sq3b28x88bMVGdpmD?8m$gg%3A zG4_g9^k)kGNW3>d@_!l2N+yo^MYxQ1gDD>C{0RWTn67 zXlDtriIj)r-v-jV5_gwVk0R@t`-r%)>2^jYB-3ZTmhwVWm~-IagR%oGhg{{nU5iq3>T`f`_lNvwb1 zGgu@0jYPMF2~nEk_TNZSXtTeW67H~Jc72E|6x$46BGh6yLs*#tmsDtwmIlPn$&tDf zZ@n8&BncS?lXCIW*NupVwIXnaG$~F;Xgk=&OmqvW$Fde~DTUoar^Ep&sg7hbOB^6} z`@nO!CgpMWhoas@Tq05Ct?#T12P&8vq1(Nze(60Uczy+?m~l2;9cjQ2F2$xQpg}uQBW<)x6hQN;=71AECW&Cc6qadb`G7;SV4ewAK{C2S zMeJ{^mTMMFS)^sWwlfajyoX}n+w2*}U+s_W7470g)*^K``2ExoSN#+QhubVXRD0OK z{c=H%#dy)%)|`hP?FfTw`s=*xs#TyWw|5sEM}v-aE^Yt z{yqmANa)TaJbz#%N0{T!B(^GSnI%N$1UtkKC*D7ce+?_=~YH&|%)AP;RL*~=LtS&a?w zEG_4Zq&z>fd>Q>#Z=X;eZ1IQ#VWA2ax(TaK69F`<%o zO}G>}r|MF&EMK2e$XaJ+TU%053xPVp0qhP37MjFe3yj(9KSPMEK)2z6pzkGHCX5BM z&To7JV1tf|xJlmJfhsw0+&4o0P@551gu7L;zk<)tKJUBJDG$&S0R!?JV~g1oPNi$p z0A08bCf?;+;#TX2jwPbe&fi+>3Qpxv8ZLXX{ZiTZi&y0TUf7}VO3H5>*{JDwxLqWW z(}Jnk4EOXGYamcXA>>=2PTz4|&D38n9TZ*XY+Ey~+uj=7R|a`!7{WU2t^CO2+Co_3 z*M3rq5HZ10{YfK8YLTFZYPh&?@iMh>7aEG|Jgrb9%D%QN`<(3S*#t1*NFN;9D)@zct4ZVShS0Enes&jY@{dxdc zlVUK?_-FWc+|i6|j~+K0+ZG#~|65!_xoCmJNna!nCh!NvBQq;kBS`FQ#U@jUam^6HZ{niL8u^h`Y~nlC)9v@h}$T&EVU6m!@QPIb-i( z;w3 ze4i|@qEPXAiL_fIauu)7e>!{yyQRKudHn*FGRZcNDz%!lLRijEq|j!P56i7LU7-Z_ z;E%U)cGv$@5%`rIKzQ2H9CV&&T^f_;TpgGIjI^R2^o>=_z)d6;IL}*r#7h&YimIYUbhFW)T-K!N(oH$kU1f&6aI)j# z=@7SNcQ?GxAv}s#Z#te=3^#8dQmR~04z@RUM^*c^;=|F%Pqy`@3UYYdcFdWw?il>u zg2K+T60pLzk-#DPYi=DNWNHXeXKvG<)5E1pf*crmE1o8*%Gz|Wyr`JhSi&PbAQX^V zzp1)Ki?wXyH=@6UWM!MV+4!gB3=)M3ol?}ns6PS~ za(P>xolmbwWVN>5ZWMfj{N&9KBvoq8d9qwOzu!;4`zt!#>^dtGSlPFjNM9t-A|thme-)6acB-)aR_JwVFaIF+;-PZY`*pSh!>)JTH*U1*J!f( zLDYaB&28h!ipQq{vTp|h?h2uR0+(AI|8=*vjFaK!I3ogofB*a8Zv_I96F}wzeK3JQ zY=TH4XUedQ9m30_mci8v6(I|y>>4y&B~SY-Kr>RTq>EhKo5!H+SW+U{rk^swvDVG= z^W9EWUnO#|K_K45onH`XVmRs5%rGm-uMZi_0STP$vP!yL4GY|PX(*tu?*tpdJ4W1PVI8gysKF{RwGT|tmLCHjCktwS(4 zJnkNDxF&iIh;ktBg(!jr?2tfTxN;Is(r-{=%pr !3IWNXfk(BK92aH^_b9X2Pf5%Y)=~r=-<#{H1Sa#Ob3e}@Yr32l-VB|vb`9TNzlN_pX4D>Z4#ydT~>VOLfh{x9u zTjQznC*6;*tG+wk>^EU(-iTeVv%|O#ghFjn9SW!CNpQKH_IfAs1ZnvPX{_j~zoF}Z z+d=E8^|)(3_2nL-zlCB~pJV>700kBL>g0^C@i|h=XuC$DU->$ue~HAbTy#j|$os`l zL-17q3`Db%ngjdbH-`%KDW4qKz<&63-pNDSw;o_#!k_Yc)eBGk^wZ#pX=0ea>n8?;e3FZ`gW4w|Rg6>J+I;GV6P745 zDdBKAo@wMKqzdIrEJN4i1~D^@HWp*iq_Ds&0+%jdkJv%w`qh_T*+fUE+R^Dh~c9sg&8{2w9>8fw^1W6|R2fX5!Dj*stXqEet6$pF2M_J5@4mGq zOf2^Z%V7dpw#YAEzGRoKTtf}WtOzWM!14R-`)jrMdrdSpVoB_k5ZXn~mK^xur=MeA zyJK~b--0;zx3}!y{`dvou$kdQ2{k^`k@c?`$N*%4uZu|s?Y8nrTW0gdH#$HOe9(2p zGFTJHJ&iVqJHA2-nhCC+xd_I&dNQ#8AnGkj5iuO!UQtxN+R4)tO2 zP{`W{tgyp*>&7kLjn(&mmif3-Wj=i1DU>ReAcw1%Atm6sy#4mojZMz6BJKfH;IM%6Ok7Ad`Xp=uaWFnO61b6q?fLBh!{0+ zc8*OAsy;32zj38wlws_M+Y#9ZP_n9bibZWF84*NuK+Y_?XzQ!z`H}$VAvC=Xc3j9! zDMK{tC=209*%A)I;_L*fN$Tx4=lk#swQIP*ew2IqZ%&A<?c#i zke0X~Ggcm&8AXNTx{?l$ew`1I>#&9i^OTOW65GG|uD2tYLM&jcfbiyxIihE8UdRuf zBOz%=*biOzB-Pi~_5iQ|GyU>6CB*wGTudZ!|44B2D%`zBd;+N>li-7(%)Y*{!ArL zBymqSO1@7@7(gnJb9j#n^l6v9-%(pK%mLz?&?EKL_vTaW0rw~q4ihFZX!EeE9gG^y>2e^}vJ3g3r2hOby#=jIi-=~-AG90Ye zZFXG>&T_g<2R5f%bbNb=I){m8Wgu=XBy~uozabQ)@3*gDymP(-aUbrO#%%nHvDzhz ze=%(I8RPql_|XK(yKd<0?{oT3w?{a^*KD#aPh8xIJ0{zYJU9?=pP!CQsr~RbfKphJ zs@%hWZ&1v8Px_8xiP{&y(Bd8T^0ZXjlz>b{XU}w8=Z+8b-ux+y( zy)1AtO&49gJb{c|Hk}U4XG=a%;$;MV%e)QpycMcbY(I_~Fa7jKBqs<7FEaJuW}g#0 zy$N)5Vo-k~?qH@Ol^f8Ds1>KNub z!J~(dH~^KiOB|k;(?tV)kxLg5ss~kweb+h8`ubTcB#qiHaV*7V@QgrC1^wu~VhEZA z#Z80wCM#6!R}gn#BQUHaot`q{qrYmk%IQp<{pQ*lH|0h2vcgruT2+Zpmc)TG4g~(z z#2ra#z)v%_!L_feSFhOG`Zs7Gft+eE6RHc5CG%TCGuAw%@hBqegXr?u!w9S-s#?C zp^RwQ;!DErhzEpHpg1wYAau{v&$t6iE!wyDwr!s)e0AB6DQ2uRTV!xBLsMoCBZVDw ziwC<~)_RKQ$pNjkwRLO~F8WN~<0vw+iuEu{*I|r>Tdxvg0^Y*fHnc(U3gxZuBCdmB z550wao+>%~brQ1=#SA8NJX4iUtZSc!{7_dk(d&a;^A_&lTgogFh%!l>$!D{Fs22p2k^h0#b#kv zXEB9ySQ6F%}EVb+#93)bAL+Oy{mxkdlf*3Vx8uk$`Ybj(&bl|^3FDrTpah2Mae ztm|}xKl~=7tc3ATIbmq&PoI0*(9Lva_rE)Ft1^vp#0gDNSK!9a5l`-Y`>pLB>{-3K zhq0GMo8@ZL>iSi`GFE^?%jvG*0i0oXYr`Jieu(y?Y71Ok)vcQffVq0@8eE0x56EyM z((^dCrZYKY^tdn$oZy%qdalzSCnfMCP1M2Oz5_&f3&au#TNgfr#|Mw@+eT;8X3k*1 zvjqJ4*Ma(EWmoxiOjbjp!H>Q-f4~Lo60`5ja>eFWF-*Eyvq!gX*vf^Aws>|OYX&VV z^6l3woL;kplma4lviVbFtQlMFiB1emmBblEQhux+%W&+ushpK8#lH-UhlBnKpcX$`qqE`~}YmE*E?hiiPd z@7=U|uV%9s=it&9)a(Fb@fFb>_|ziO;Z2wbPX?1P3=6Kgz>NvyY`bQcR#xoX>;=1f z>%LVn&vE7A6+|H)8UaUC?5v7ZF0}i zKi>;+(?{ppwd#Q{8fW0TA0bYt@DB3b{_eh;B5v+)Gd1lns=u~2Exz`_2O7>+XSb9&z&!wT7+C4~Wp+h2{IvJ^T@e22v5Xb-L_B zBErpWzKE~|Pt>+I?|f&?e9IQjF56D;0Akmyto0CZhQQqFm)hBDryJ_G2+rL=t@z-z#?(|(=w+}x5OL{WPsP*KoyVi9`+1Y;hfb?H|i5Y^Atc=a= z404%o*r-$P#H~-=aS#Zw2~5Ckwt$d83;h_lB0z;}KYH}QYH;j@^%dL2n7@P%sdoiw zCuTfO+)%NH5uODPvg0Vm>f(mV*BO>l)*d&vxSX7|D-igtZ|+zb!}qhZe6}31hn$un zt_rhz@boUWQtFnSFI#S=Ad}$`H)4)v`g+S=V9uksiS2O|4@--yzAkW4#Ti>{2}K^^ zz8B(Fxn)F;vn(Q|F1m*)w0y2@#hE2cFf4O_uVnSzUAy(=?`2zAcHTj;4fLNMe zvvZd}vLb3Led<{vjJQLbPQ4e@DOvI4v3sb$Kgs}jum`95~P{Fe9h@f*wEe%ZbqYI3ZBsU6l`B#MQf)m3Uf5J-oDTy{)S4 zyxg(HwN-nu_1M0C_>C=GUPcpBW?|Z5K>g zU+2P5rU*9TOAtFiJ-c7uvHZER?Pp}Z7+JTCI3mi`n?HtOn?!kh`p1b~D?>3JaOQdg zIPi93&&sRww!5*1F`$y&d2-Vh&d=g6x(yW>>`=ob1PzS1QHI7I6#k;UkH|7>ix-yd z1-5ER>|SU2!bYdXK-33;j=}x+Lfq*N$}e?NQ+nt!(+c)Q7WwQ4lcO7042@??9hH*i zunq@AW-9YGQwEB(Q?>%psZfRt@u+zMlz7iDfVH^ni1VDfS3n@ZEsy}efY~)3Nv{3+ zzI7@PS24?ij~z!~uGuror?dlkzoPh&Xes%`wN+79M{Ciz)I0k&KeJ-3t&ZKg^_}UX zM>C7_u1S&1o+TMWVWgxmu+;8up<~tNzAen4CSIzrS-4^A5O)Sfs+vL%4`p|S#^KO+ z4uKqHNmQ}~D})Y&tWhYHkI0cXe)*$SOg>yX2;AHce(uyQO|Vb;@r=#T2-NF4)P?{VA8)s^${ddM>=-R;QOd07vS~> zn+F)lZ(0SW*_Q$a&FK5y3+Gk`HF|x~rxzNQ6{jXlTfv!3Feq;r(}7oer!k z@ZHCL3za|Vx27n$A_uEFU9>Gwze1Gc)E$O^2u2qaBw~mNwEHV#p*kNTplYkV&WEwO zXfC9u)~uQp7s7S3=&#-auwCc)T4KV~yc}VuYgI&^8H{cgRx!GX^{WQ6gtReTR;QYP zy*^cZgi{Gc5;Y)>79vT$%n<;Hy9aTf1j&K#_^Ch=aGl#7U5uk~rB0 z6p&;kcXxM>18)1SPgbCqgBI%&4GU;1oCyJOX)J^f5mjHl7I-}%Jfl}-hjbc5(wIQl zVnVRoFtf^!ok!}~;Eak6P5405&JUThq*Kv3p5oG8U%yN*2C;=fL1YsK*(O_sLDE69 z)@92>YUtzK_e$LTg$jhZcICS5q3(IMv*&8T0_PCQ%t5orc3EF^&?uNgwZ`Y-bWD~r zD7sgZLG8D8?jk_aRWgWMoSxPTNAg^{a>?#L+pM zh8UtVI7`U5_wL@oR(Hn_w+RL!MxEzDqP2n_asnmLc!%&}8^$4+zk$;X(L#V0q2?h- zF(>U(YxdB_O|?WzIDD03x03IrA&$}p$`^*Sz5$;gxnE}-Fe*=F8@l+G&A}{XOh4p4 zp19wO@%hl;Fbdmz<+s5%BO4rJZ8d7vYaZAx2Wzy`w79a$2{RU*Y_}Kr(|PZrrkX6% z4%4HWtdyrP$50?a%x|dyi}9dm--7Of_q`TFqAaZhIR}t;M5m=M*^$tZ$Q_ zx>E>z-w=3@#H~Rp0uL_w!${LoL!&l0=eoQM-iHT8lFT2m9KA#_C>?+5KNpY|FN=(-`{?|teLx2x=WtkzAFd^)9Dcr6 z;*M2C!0M@g;dFr-%|AM-g`)TNby^$?J$;@+;1v+~LF9qRCE|$H3f}oaT3&jSA^D0< zokpKRKoIyrhX8rkPI5;^X?0J5}ig zIGp~zD+GRU;*NY-PmyzYkD>3{ozwiM5Eww<{{xK6+WZVQeo+7b002ovPDHLkV1nV= Bvo!z! literal 0 HcmV?d00001 diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index d551885700..320d871849 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -7,7 +7,11 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { } }; -var erpnext_slides = [ +frappe.setup.on("before_load", function () { + erpnext.setup.slides_settings.map(frappe.setup.add_slide); +}); + +erpnext.setup.slides_settings = [ { // Domain name: 'domain', @@ -18,14 +22,14 @@ var erpnext_slides = [ fieldname: 'domain', label: __('Domain'), fieldtype: 'Select', options: [ { "label": __("Distribution"), "value": "Distribution" }, - { "label": __("Education"), "value": "Education" }, { "label": __("Manufacturing"), "value": "Manufacturing" }, { "label": __("Retail"), "value": "Retail" }, - { "label": __("Services"), "value": "Services" } + { "label": __("Services"), "value": "Services" }, + { "label": __("Education"), "value": "Education" } ], reqd: 1 }, ], - help: __('Select the nature of your business.'), + // help: __('Select the nature of your business.'), onload: function (slide) { slide.get_input("domain").on("change", function () { frappe.setup.domain = $(this).val(); @@ -40,7 +44,7 @@ var erpnext_slides = [ domains: ["all"], icon: "fa fa-bookmark", title: __("The Brand"), - help: __('Upload your letter head and logo. (you can edit them later).'), + // help: __('Upload your letter head and logo. (you can edit them later).'), fields: [ { fieldtype: "Attach Image", fieldname: "attach_logo", @@ -79,6 +83,12 @@ var erpnext_slides = [ slide.get_field("company_abbr").set_value(""); } }); + }, + validate: function() { + if (!this.values.company_abbr) { + return false; + } + return true; } }, { @@ -87,9 +97,9 @@ var erpnext_slides = [ domains: ["all"], title: __("Your Organization"), icon: "fa fa-building", - help: (frappe.setup.domain === 'Education' ? - __('The name of the institute for which you are setting up this system.') : - __('The name of your company for which you are setting up this system.')), + // help: (frappe.setup.domain === 'Education' ? + // __('The name of the institute for which you are setting up this system.') : + // __('The name of your company for which you are setting up this system.')), fields: [ { fieldname: 'company_tagline', @@ -189,213 +199,6 @@ var erpnext_slides = [ slide.form.fields_dict.fy_end_date.set_value(year_end_date); }); } - }, - - { - // Users - name: 'users', - domains: ["all"], - title: __("Add Users"), - help: __("Add users to your organization, other than yourself"), - add_more: 1, - max_count: 3, - fields: [ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"user_fullname", - label:__("Full Name"), static: 1}, - {fieldtype:"Data", fieldname:"user_email", label:__("Email ID"), - placeholder:__("user@example.com"), options: "Email", static: 1}, - {fieldtype:"Column Break"}, - {fieldtype: "Check", fieldname: "user_sales", - label:__("Sales"), "default": 1, static: 1, - hidden: frappe.setup.domain==='Education' ? 1 : 0}, - {fieldtype: "Check", fieldname: "user_purchaser", - label:__("Purchaser"), "default": 1, static: 1, - hidden: frappe.setup.domain==='Education' ? 1 : 0}, - {fieldtype: "Check", fieldname: "user_accountant", - label:__("Accountant"), "default": 1, static: 1, - hidden: frappe.setup.domain==='Education' ? 1 : 0}, - ] - }, - - { - // Sales Target - name: 'Goals', - domains: ['manufacturing', 'services', 'retail', 'distribution'], - title: __("Set your Target"), - help: __("Set a sales target you'd like to achieve."), - fields: [ - {fieldtype:"Currency", fieldname:"sales_target", label:__("Monthly Sales Target")}, - ] - }, - - { - // Taxes - name: 'taxes', - domains: ['manufacturing', 'services', 'retail', 'distribution'], - icon: "fa fa-money", - title: __("Add Taxes"), - help: __("List your tax heads (e.g. VAT, Customs etc; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."), - add_more: 1, - max_count: 3, - mandatory_entry: 0, - fields: [ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"tax", label:__("Tax"), - placeholder:__("e.g. VAT")}, - {fieldtype:"Column Break"}, - {fieldtype:"Float", fieldname:"tax_rate", label:__("Rate (%)"), placeholder:__("e.g. 5")} - ] - }, - - { - // Customers - name: 'customers', - domains: ['manufacturing', 'services', 'retail', 'distribution'], - icon: "fa fa-group", - title: __("Add Customers"), - help: __("List a few of your customers. They could be organizations or individuals."), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"customer", label:__("Customer"), - placeholder:__("Customer Name")}, - {fieldtype:"Column Break"}, - {fieldtype:"Data", fieldname:"customer_contact", - label:__("Contact Name"), placeholder:__("Contact Name")} - ], - }, - - { - // Suppliers - name: 'suppliers', - domains: ['manufacturing', 'services', 'retail', 'distribution'], - icon: "fa fa-group", - title: __("Your Suppliers"), - help: __("List a few of your suppliers. They could be organizations or individuals."), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"supplier", label:__("Supplier"), - placeholder:__("Supplier Name")}, - {fieldtype:"Column Break"}, - {fieldtype:"Data", fieldname:"supplier_contact", - label:__("Contact Name"), placeholder:__("Contact Name")}, - ] - }, - - { - // Products - name: 'products', - domains: ['manufacturing', 'services', 'retail', 'distribution'], - icon: "fa fa-barcode", - title: __("Your Products or Services"), - help: __("List your products or services that you buy or sell. Make sure to check the Item Group, Unit of Measure and other properties when you start."), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"item", label:__("Item"), - placeholder:__("A Product or Service")}, - {fieldtype:"Select", label:__("Group"), fieldname:"item_group", - options:[__("Products"), __("Services"), - __("Raw Material"), __("Consumable"), __("Sub Assemblies")], - "default": __("Products"), static: 1}, - {fieldtype:"Select", fieldname:"item_uom", label:__("UOM"), - options:[__("Unit"), __("Nos"), __("Box"), __("Pair"), __("Kg"), __("Set"), - __("Hour"), __("Minute"), __("Litre"), __("Meter"), __("Gram")], - "default": __("Unit"), static: 1}, - {fieldtype: "Check", fieldname: "is_sales_item", - label:__("We sell this Item"), default: 1, static: 1}, - {fieldtype: "Check", fieldname: "is_purchase_item", - label:__("We buy this Item"), default: 1, static: 1}, - {fieldtype:"Column Break"}, - {fieldtype:"Currency", fieldname:"item_price", label:__("Rate"), static: 1}, - {fieldtype:"Attach Image", fieldname:"item_img", label:__("Attach Image"), is_private: 0, static: 1}, - ], - get_item_count: function() { - return this.item_count; - } - }, - - { - // Program - name: 'program', - domains: ["education"], - title: __("Program"), - help: __("Example: Masters in Computer Science"), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"program", label:__("Program"), placeholder: __("Program Name")}, - ], - }, - - { - // Course - name: 'course', - domains: ["education"], - title: __("Course"), - help: __("Example: Basic Mathematics"), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"course", label:__("Course"), placeholder: __("Course Name")}, - ] - }, - - { - // Instructor - name: 'instructor', - domains: ["education"], - title: __("Instructor"), - help: __("People who teach at your organisation"), - add_more: 1, - max_count: 5, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"instructor", label:__("Instructor"), placeholder: __("Instructor Name")}, - ] - }, - - { - // Room - name: 'room', - domains: ["education"], - title: __("Room"), - help: __("Classrooms/ Laboratories etc where lectures can be scheduled."), - add_more: 1, - max_count: 3, - mandatory_entry: 1, - fields: [ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"room", label:__("Room")}, - {fieldtype:"Column Break"}, - {fieldtype:"Int", fieldname:"room_capacity", label:__("Room") + " Capacity", static: 1}, - ] - }, - - { - // last slide: Sample Data - name: 'bootstrap', - domains: ["all"], - title: __("Sample Data"), - fields: [{fieldtype: "Section Break"}, - {fieldtype: "Check", fieldname: "add_sample_data", - label: __("Add a few sample records"), "default": 1}, - {fieldtype: "Check", fieldname: "setup_website", - label: __("Setup a simple website for my organization"), "default": 1} - ] } ]; @@ -422,23 +225,19 @@ erpnext.setup.fiscal_years = { "United Kingdom": ["04-01", "03-31"], }; -frappe.setup.on("before_load", function () { - erpnext_slides.map(frappe.setup.add_slide); -}); - -var test_values_edu = { - "language": "english", - "domain": "Education", - "country": "India", - "timezone": "Asia/Kolkata", - "currency": "INR", - "first_name": "Tester", - "email": "test@example.com", - "password": "test", - "company_name": "Hogwarts", - "company_abbr": "HS", - "company_tagline": "School for magicians", - "bank_account": "Gringotts Wizarding Bank", - "fy_start_date": "2016-04-01", - "fy_end_date": "2017-03-31" -} +// var test_values_edu = { +// "language": "english", +// "domain": "Education", +// "country": "India", +// "timezone": "Asia/Kolkata", +// "currency": "INR", +// "first_name": "Tester", +// "email": "test@example.com", +// "password": "test", +// "company_name": "Hogwarts", +// "company_abbr": "HS", +// "company_tagline": "School for magicians", +// "bank_account": "Gringotts Wizarding Bank", +// "fy_start_date": "2016-04-01", +// "fy_end_date": "2017-03-31" +// } diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json index 23cf082362..7b3a8d6b72 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json @@ -84,13 +84,34 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-30 20:12:57.903983", - "modified_by": "Administrator", + "modified": "2017-08-31 14:38:52.220743", + "modified_by": "ewdszx@ed.ews", "module": "Regional", "name": "GST HSN Code", "name_case": "", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "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": 0, + "write": 1 + } + ], "quick_entry": 1, "read_only": 0, "read_only_onload": 0, diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js new file mode 100644 index 0000000000..24c5fd355f --- /dev/null +++ b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.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: GST HSN Code", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new GST HSN Code + () => frappe.tests.make('GST HSN Code', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 61af138e44..04065e29df 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -83,13 +83,34 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-28 16:20:21.206397", - "modified_by": "Administrator", + "modified": "2017-08-31 14:39:15.625952", + "modified_by": "ewdszx@ed.ews", "module": "Regional", "name": "GST Settings", "name_case": "", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], "quick_entry": 1, "read_only": 0, "read_only_onload": 0, diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.js b/erpnext/regional/doctype/gst_settings/test_gst_settings.js new file mode 100644 index 0000000000..00fcca6f32 --- /dev/null +++ b/erpnext/regional/doctype/gst_settings/test_gst_settings.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: GST Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new GST Settings + () => frappe.tests.make('GST Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 0c59ba003a..46afeece1b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -39,12 +39,12 @@ def add_hsn_sac_codes(): hsn_codes = json.loads(f.read()) create_hsn_codes(hsn_codes, code_field="hsn_code") - + # SAC Codes with open(os.path.join(os.path.dirname(__file__), 'sac_code_data.json'), 'r') as f: sac_codes = json.loads(f.read()) create_hsn_codes(sac_codes, code_field="sac_code") - + def create_hsn_codes(data, code_field): for d in data: if not frappe.db.exists("GST HSN Code", d[code_field]): @@ -54,8 +54,6 @@ def create_hsn_codes(data, code_field): hsn_code.name = d[code_field] hsn_code.db_insert() - frappe.db.commit() - def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', 'GST Itemised Sales Register', 'GST Itemised Purchase Register'): @@ -101,7 +99,7 @@ def make_custom_fields(): dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', fieldtype='Data', insert_after='export_type', print_hide=1) ] - + purchase_invoice_gst_fields = [ dict(fieldname='supplier_gstin', label='Supplier GSTIN', fieldtype='Data', insert_after='supplier_address', @@ -110,7 +108,7 @@ def make_custom_fields(): fieldtype='Data', insert_after='shipping_address', options='shipping_address.gstin', print_hide=1) ] - + sales_invoice_gst_fields = [ dict(fieldname='customer_gstin', label='Customer GSTIN', fieldtype='Data', insert_after='shipping_address', @@ -122,7 +120,7 @@ def make_custom_fields(): fieldtype='Data', insert_after='company_address', options='company_address.gstin', print_hide=1) ] - + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index c338a81a8e..15e6b4b5b9 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -195,6 +195,157 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sales_settings", + "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": "Sales", + "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": "sales_monthly_history", + "fieldtype": "Small Text", + "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": "Sales Monthly History", + "length": 0, + "no_copy": 1, + "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": "monthly_sales_target", + "fieldtype": "Currency", + "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": "Monthly Sales Target", + "length": 0, + "no_copy": 0, + "options": "default_currency", + "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_goals", + "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": "total_monthly_sales", + "fieldtype": "Currency", + "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": "Total Monthly Sales", + "length": 0, + "no_copy": 1, + "options": "default_currency", + "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, @@ -500,157 +651,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_settings", - "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": "Sales", - "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": "sales_monthly_history", - "fieldtype": "Small Text", - "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": "Sales Monthly History", - "length": 0, - "no_copy": 1, - "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": "sales_target", - "fieldtype": "Currency", - "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": "Sales Target", - "length": 0, - "no_copy": 1, - "options": "default_currency", - "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_goals", - "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": "total_monthly_sales", - "fieldtype": "Currency", - "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": "Total Monthly Sales", - "length": 0, - "no_copy": 1, - "options": "default_currency", - "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, @@ -1991,7 +1991,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-08-03 16:17:31.206886", + "modified": "2017-08-31 11:48:56.278568", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index b945ee4104..d3503cc1fa 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -76,7 +76,10 @@ class Company(Document): self.create_default_accounts() self.create_default_warehouses() - self.install_country_fixtures() + if cint(frappe.db.get_single_value('System Settings', 'setup_complete')): + # In the case of setup, fixtures should be installed after setup_success + # This also prevents db commits before setup is successful + install_country_fixtures(self.name) if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): self.create_default_cost_center() @@ -95,12 +98,6 @@ class Company(Document): frappe.clear_cache() - def install_country_fixtures(self): - path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(self.country)) - if os.path.exists(path.encode("utf-8")): - frappe.get_attr("erpnext.regional.{0}.setup.setup" - .format(self.country.lower()))(self) - def create_default_warehouses(self): for wh_detail in [ {"warehouse_name": _("All Warehouses"), "is_group": 1}, @@ -311,6 +308,13 @@ def get_name_with_abbr(name, company): return " - ".join(parts) +def install_country_fixtures(company): + company_doc = frappe.get_doc("Company", company) + path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(company_doc.country)) + if os.path.exists(path.encode("utf-8")): + frappe.get_attr("erpnext.regional.{0}.setup.setup" + .format(company_doc.country.lower()))(company_doc) + def update_company_current_month_sales(company): current_month_year = formatdate(today(), "MM-yyyy") diff --git a/erpnext/setup/doctype/company/company_dashboard.py b/erpnext/setup/doctype/company/company_dashboard.py index da7f2b582a..d6e250b142 100644 --- a/erpnext/setup/doctype/company/company_dashboard.py +++ b/erpnext/setup/doctype/company/company_dashboard.py @@ -9,7 +9,7 @@ def get_data(): 'graph_method': "frappe.utils.goal.get_monthly_goal_graph_data", 'graph_method_args': { 'title': 'Sales', - 'goal_value_field': 'sales_target', + 'goal_value_field': 'monthly_sales_target', 'goal_total_field': 'total_monthly_sales', 'goal_history_field': 'sales_monthly_history', 'goal_doctype': 'Sales Invoice', diff --git a/erpnext/setup/doctype/setup_progress/__init__.py b/erpnext/setup/doctype/setup_progress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.js b/erpnext/setup/doctype/setup_progress/setup_progress.js new file mode 100644 index 0000000000..5c78bd5075 --- /dev/null +++ b/erpnext/setup/doctype/setup_progress/setup_progress.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Setup Progress', { + refresh: function() { + + } +}); diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.json b/erpnext/setup/doctype/setup_progress/setup_progress.json new file mode 100644 index 0000000000..2f886afe3b --- /dev/null +++ b/erpnext/setup/doctype/setup_progress/setup_progress.json @@ -0,0 +1,123 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-08-27 21:01:42.032109", + "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": "actions_sb", + "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": "Actions", + "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": "actions", + "fieldtype": "Table", + "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": "Actions", + "length": 0, + "no_copy": 0, + "options": "Setup Progress Action", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-08-28 17:44:43.100932", + "modified_by": "Administrator", + "module": "Setup", + "name": "Setup Progress", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/setup_progress/setup_progress.py b/erpnext/setup/doctype/setup_progress/setup_progress.py new file mode 100644 index 0000000000..26eecd9634 --- /dev/null +++ b/erpnext/setup/doctype/setup_progress/setup_progress.py @@ -0,0 +1,51 @@ +# -*- 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, json +from frappe.model.document import Document + +class SetupProgress(Document): + pass + +def get_setup_progress(): + if not getattr(frappe.local, "setup_progress", None): + frappe.local.setup_progress = frappe.get_doc("Setup Progress", "Setup Progress") + + return frappe.local.setup_progress + +def get_action_completed_state(action_name): + return [d.is_completed for d in get_setup_progress().actions + if d.action_name == action_name][0] + +def update_action_completed_state(action_name): + action_table_doc = [d for d in get_setup_progress().actions + if d.action_name == action_name][0] + update_action(action_table_doc) + +def update_action(action_table_doc): + if not action_table_doc.is_completed and frappe.db.count(action_table_doc.action_doctype) >= action_table_doc.min_doc_count: + action_table_doc.is_completed = 1 + action_table_doc.save() + +def update_domain_actions(domain): + for d in get_setup_progress().actions: + domains = json.loads(d.domains) + if domains == [] or domain in domains: + update_action(d) + +def get_domain_actions_state(domain): + state = {} + for d in get_setup_progress().actions: + domains = json.loads(d.domains) + if domains == [] or domain in domains: + state[d.action_name] = d.is_completed + return state + +@frappe.whitelist() +def set_action_completed_state(action_name): + action_table_doc = [d for d in get_setup_progress().actions + if d.action_name == action_name][0] + action_table_doc.is_completed = 1 + action_table_doc.save() diff --git a/erpnext/setup/doctype/setup_progress/test_setup_progress.js b/erpnext/setup/doctype/setup_progress/test_setup_progress.js new file mode 100644 index 0000000000..9e84e0cb15 --- /dev/null +++ b/erpnext/setup/doctype/setup_progress/test_setup_progress.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: Setup Progress", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Setup Progress + () => frappe.tests.make('Setup Progress', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/setup/doctype/setup_progress_action/__init__.py b/erpnext/setup/doctype/setup_progress_action/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json new file mode 100644 index 0000000000..030fd99a33 --- /dev/null +++ b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.json @@ -0,0 +1,192 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-08-27 21:00:40.715360", + "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": "action_name", + "fieldtype": "Data", + "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": "Action Name", + "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": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "action_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": "Action 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": 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": "min_doc_count", + "fieldtype": "Int", + "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": "Min Doc Count", + "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": "domains", + "fieldtype": "Code", + "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": "Domains", + "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": "is_completed", + "fieldtype": "Check", + "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": "Is Completed", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-08-28 17:44:58.008526", + "modified_by": "Administrator", + "module": "Setup", + "name": "Setup Progress Action", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py new file mode 100644 index 0000000000..24af94347e --- /dev/null +++ b/erpnext/setup/doctype/setup_progress_action/setup_progress_action.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class SetupProgressAction(Document): + pass diff --git a/erpnext/setup/setup_wizard/install_fixtures.py b/erpnext/setup/setup_wizard/install_fixtures.py index ea6da04641..53e58a15af 100644 --- a/erpnext/setup/setup_wizard/install_fixtures.py +++ b/erpnext/setup/setup_wizard/install_fixtures.py @@ -20,6 +20,28 @@ def install(country=None): { 'doctype': 'Domain', 'domain': _('Services')}, { 'doctype': 'Domain', 'domain': _('Education')}, + # Setup Progress + {'doctype': "Setup Progress", "actions": [ + {"action_name": _("Add Company"), "action_doctype": "Company", "min_doc_count": 1, "is_completed": 1, + "domains": '[]' }, + {"action_name": _("Add Customers"), "action_doctype": "Customer", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Suppliers"), "action_doctype": "Supplier", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Products"), "action_doctype": "Item", "min_doc_count": 1, "is_completed": 0, + "domains": '["Manufacturing", "Services", "Retail", "Distribution"]' }, + {"action_name": _("Add Programs"), "action_doctype": "Program", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Instructors"), "action_doctype": "Instructor", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Courses"), "action_doctype": "Course", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Rooms"), "action_doctype": "Room", "min_doc_count": 1, "is_completed": 0, + "domains": '["Education"]' }, + {"action_name": _("Add Users"), "action_doctype": "User", "min_doc_count": 4, "is_completed": 0, + "domains": '[]' } + ]}, + # address template {'doctype':"Address Template", "country": country}, diff --git a/erpnext/setup/setup_wizard/sample_data.py b/erpnext/setup/setup_wizard/sample_data.py index cfc6726d1c..bc26e09677 100644 --- a/erpnext/setup/setup_wizard/sample_data.py +++ b/erpnext/setup/setup_wizard/sample_data.py @@ -10,26 +10,27 @@ import random, os, json from frappe import _ from markdown2 import markdown -def make_sample_data(args): +def make_sample_data(domain, make_dependent = False): """Create a few opportunities, quotes, material requests, issues, todos, projects to help the user get started""" - items = frappe.get_all("Item", {'is_sales_item': 1}) - customers = frappe.get_all("Customer") - warehouses = frappe.get_all("Warehouse") + if make_dependent: + items = frappe.get_all("Item", {'is_sales_item': 1}) + customers = frappe.get_all("Customer") + warehouses = frappe.get_all("Warehouse") - if items and customers: - for i in range(3): - customer = random.choice(customers).name - make_opportunity(items, customer) - make_quote(items, customer) + if items and customers: + for i in range(3): + customer = random.choice(customers).name + make_opportunity(items, customer) + make_quote(items, customer) - make_projects(args.get('domain')) + if items and warehouses: + make_material_request(frappe.get_all("Item")) + + make_projects(domain) import_email_alert() - if items and warehouses: - make_material_request(frappe.get_all("Item")) - frappe.db.commit() def make_opportunity(items, customer): diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index 4dec3d75f7..40d11e5bdc 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -15,6 +15,7 @@ from .sample_data import make_sample_data from erpnext.accounts.doctype.account.account import RootNotEditable from frappe.core.doctype.communication.comment import add_info_comment from erpnext.setup.setup_wizard.domainify import setup_domain +from erpnext.setup.doctype.company.company import install_country_fixtures def setup_complete(args=None): if frappe.db.sql("select name from tabCompany"): @@ -25,24 +26,16 @@ def setup_complete(args=None): create_price_lists(args) create_fiscal_year_and_company(args) create_sales_tax(args) - create_users(args) + create_employee_for_self(args) set_defaults(args) create_territories() create_feed_and_todo() create_email_digest() create_letter_head(args) - create_taxes(args) - create_items(args) - create_customers(args) - create_suppliers(args) if args.get('domain').lower() == 'education': create_academic_year() create_academic_term() - create_program(args) - create_course(args) - create_instructor(args) - create_room(args) if args.get('setup_website'): website_maker(args) @@ -58,16 +51,19 @@ def setup_complete(args=None): frappe.db.commit() frappe.clear_cache() - if args.get("add_sample_data"): - try: - make_sample_data(args) - frappe.clear_cache() - except: - # clear message - if frappe.message_log: - frappe.message_log.pop() + try: + make_sample_data(args.get('domain')) + frappe.clear_cache() + except: + # clear message + if frappe.message_log: + frappe.message_log.pop() - pass + pass + +def setup_success(args=None): + company = frappe.db.sql("select name from tabCompany", as_dict=True)[0]["name"] + install_country_fixtures(company) def create_fiscal_year_and_company(args): if (args.get('fy_start_date')): @@ -91,8 +87,7 @@ def create_fiscal_year_and_company(args): 'country': args.get('country'), 'create_chart_of_accounts_based_on': 'Standard Template', 'chart_of_accounts': args.get('chart_of_accounts'), - 'domain': args.get('domain'), - 'sales_target': args.get('sales_target') + 'domain': args.get('domain') }).insert() #Enable shopping cart @@ -259,22 +254,7 @@ def create_sales_tax(args): tax_data.get('account_name'), tax_data.get('tax_rate'), sales_tax) -def get_country_wise_tax(country): - data = {} - with open (os.path.join(os.path.dirname(__file__), "data", "country_wise_tax.json")) as countrywise_tax: - data = json.load(countrywise_tax).get(country) - - return data - -def create_taxes(args): - for i in xrange(1,6): - if args.get("tax_" + str(i)): - # replace % in case someone also enters the % symbol - tax_rate = cstr(args.get("tax_rate_" + str(i)) or "").replace("%", "") - account_name = args.get("tax_" + str(i)) - - make_tax_account_and_template(args.get("company_name") , account_name, tax_rate) - +# Tax utils start def make_tax_account_and_template(company, account_name, tax_rate, template_name=None): try: if not isinstance(account_name, (list, tuple)): @@ -292,15 +272,6 @@ def make_tax_account_and_template(company, account_name, tax_rate, template_name except RootNotEditable: pass -def get_tax_account_group(company): - tax_group = frappe.db.get_value("Account", - {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) - if not tax_group: - tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability", - "account_type": "Tax", "company": company}) - - return tax_group - def make_tax_account(company, account_name, tax_rate): tax_group = get_tax_account_group(company) if tax_group: @@ -345,115 +316,23 @@ def make_sales_and_purchase_tax_templates(accounts, template_name=None): doc = frappe.get_doc(purchase_tax_template) doc.insert(ignore_permissions=True) -def create_items(args): - for i in xrange(1,6): - item = args.get("item_" + str(i)) - if item: - item_group = _(args.get("item_group_" + str(i))) - is_sales_item = args.get("is_sales_item_" + str(i)) - is_purchase_item = args.get("is_purchase_item_" + str(i)) - is_stock_item = item_group!=_("Services") - default_warehouse = "" - if is_stock_item: - default_warehouse = frappe.db.get_value("Warehouse", filters={ - "warehouse_name": _("Finished Goods") if is_sales_item else _("Stores"), - "company": args.get("company_name") - }) +def get_tax_account_group(company): + tax_group = frappe.db.get_value("Account", + {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) + if not tax_group: + tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability", + "account_type": "Tax", "company": company}) - try: - frappe.get_doc({ - "doctype":"Item", - "item_code": item, - "item_name": item, - "description": item, - "show_in_website": 1, - "is_sales_item": is_sales_item, - "is_purchase_item": is_purchase_item, - "is_stock_item": is_stock_item and 1 or 0, - "item_group": item_group, - "stock_uom": _(args.get("item_uom_" + str(i))), - "default_warehouse": default_warehouse - }).insert() + return tax_group - if args.get("item_img_" + str(i)): - item_image = args.get("item_img_" + str(i)).split(",") - if len(item_image)==3: - filename, filetype, content = item_image - fileurl = save_file(filename, content, "Item", item, decode=True).file_url - frappe.db.set_value("Item", item, "image", fileurl) +# Tax utils end - if args.get("item_price_" + str(i)): - item_price = flt(args.get("item_price_" + str(i))) +def get_country_wise_tax(country): + data = {} + with open (os.path.join(os.path.dirname(__file__), "data", "country_wise_tax.json")) as countrywise_tax: + data = json.load(countrywise_tax).get(country) - if is_sales_item: - price_list_name = frappe.db.get_value("Price List", {"selling": 1}) - make_item_price(item, price_list_name, item_price) - - if is_purchase_item: - price_list_name = frappe.db.get_value("Price List", {"buying": 1}) - make_item_price(item, price_list_name, item_price) - - except frappe.NameError: - pass - -def make_item_price(item, price_list_name, item_price): - frappe.get_doc({ - "doctype": "Item Price", - "price_list": price_list_name, - "item_code": item, - "price_list_rate": item_price - }).insert() - - -def create_customers(args): - for i in xrange(1,6): - customer = args.get("customer_" + str(i)) - if customer: - try: - doc = frappe.get_doc({ - "doctype":"Customer", - "customer_name": customer, - "customer_type": "Company", - "customer_group": _("Commercial"), - "territory": args.get("country"), - "company": args.get("company_name") - }).insert() - - if args.get("customer_contact_" + str(i)): - create_contact(args.get("customer_contact_" + str(i)), - "Customer", doc.name) - except frappe.NameError: - pass - -def create_suppliers(args): - for i in xrange(1,6): - supplier = args.get("supplier_" + str(i)) - if supplier: - try: - doc = frappe.get_doc({ - "doctype":"Supplier", - "supplier_name": supplier, - "supplier_type": _("Local"), - "company": args.get("company_name") - }).insert() - - if args.get("supplier_contact_" + str(i)): - create_contact(args.get("supplier_contact_" + str(i)), - "Supplier", doc.name) - except frappe.NameError: - pass - -def create_contact(contact, party_type, party): - """Create contact based on given contact name""" - contact = contact .split(" ") - - contact = frappe.get_doc({ - "doctype":"Contact", - "first_name":contact[0], - "last_name": len(contact) > 1 and contact[1] or "" - }) - contact.append('links', dict(link_doctype=party_type, link_name=party)) - contact.insert() + return data def create_letter_head(args): if args.get("attach_letterhead"): @@ -497,7 +376,7 @@ def login_as_first_user(args): if args.get("email") and hasattr(frappe.local, "login_manager"): frappe.local.login_manager.login_as(args.get("email")) -def create_users(args): +def create_employee_for_self(args): if frappe.session.user == 'Administrator': return @@ -512,50 +391,7 @@ def create_users(args): emp.flags.ignore_mandatory = True emp.insert(ignore_permissions = True) - for i in xrange(1,5): - email = args.get("user_email_" + str(i)) - fullname = args.get("user_fullname_" + str(i)) - if email: - if not fullname: - fullname = email.split("@")[0] - - parts = fullname.split(" ", 1) - - user = frappe.get_doc({ - "doctype": "User", - "email": email, - "first_name": parts[0], - "last_name": parts[1] if len(parts) > 1 else "", - "enabled": 1, - "user_type": "System User" - }) - - # default roles - user.append_roles("Projects User", "Stock User", "Support Team") - - if args.get("user_sales_" + str(i)): - user.append_roles("Sales User", "Sales Manager", "Accounts User") - if args.get("user_purchaser_" + str(i)): - user.append_roles("Purchase User", "Purchase Manager", "Accounts User") - if args.get("user_accountant_" + str(i)): - user.append_roles("Accounts Manager", "Accounts User") - - user.flags.delay_emails = True - - if not frappe.db.get_value("User", email): - user.insert(ignore_permissions=True) - - # create employee - emp = frappe.get_doc({ - "doctype": "Employee", - "employee_name": fullname, - "user_id": email, - "status": "Active", - "company": args.get("company_name") - }) - emp.flags.ignore_mandatory = True - emp.insert(ignore_permissions = True) - +# Schools def create_academic_term(): at = ["Semester 1", "Semester 2", "Semester 3"] ay = ["2013-14", "2014-15", "2015-16", "2016-17", "2017-18"] @@ -578,46 +414,3 @@ def create_academic_year(): academic_year.save() except frappe.DuplicateEntryError: pass - -def create_program(args): - for i in xrange(1,6): - if args.get("program_" + str(i)): - program = frappe.new_doc("Program") - program.program_code = args.get("program_" + str(i)) - program.program_name = args.get("program_" + str(i)) - try: - program.save() - except frappe.DuplicateEntryError: - pass - -def create_course(args): - for i in xrange(1,6): - if args.get("course_" + str(i)): - course = frappe.new_doc("Course") - course.course_code = args.get("course_" + str(i)) - course.course_name = args.get("course_" + str(i)) - try: - course.save() - except frappe.DuplicateEntryError: - pass - -def create_instructor(args): - for i in xrange(1,6): - if args.get("instructor_" + str(i)): - instructor = frappe.new_doc("Instructor") - instructor.instructor_name = args.get("instructor_" + str(i)) - try: - instructor.save() - except frappe.DuplicateEntryError: - pass - -def create_room(args): - for i in xrange(1,6): - if args.get("room_" + str(i)): - room = frappe.new_doc("Room") - room.room_name = args.get("room_" + str(i)) - room.seating_capacity = args.get("room_capacity_" + str(i)) - try: - room.save() - except frappe.DuplicateEntryError: - pass diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py new file mode 100644 index 0000000000..aed6698bb4 --- /dev/null +++ b/erpnext/setup/setup_wizard/test_setup_wizard.py @@ -0,0 +1,57 @@ +# 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, time +from frappe.utils.selenium_testdriver import TestDriver + +def run_setup_wizard_test(): + driver = TestDriver() + frappe.db.set_default('in_selenium', '1') + + driver.login('#page-setup-wizard') + print('Running Setup Wizard Test...') + + # Language slide + driver.set_select("language", "English (United Kingdom)") + driver.wait_for_ajax(True) + driver.wait_till_clickable(".next-btn").click() + + # Region slide + driver.wait_for_ajax(True) + driver.set_select("country", "India") + driver.wait_for_ajax(True) + driver.wait_till_clickable(".next-btn").click() + + # Profile slide + driver.set_field("full_name", "Joe Davis") + driver.set_field("email", "joe@example.com") + driver.set_field("password", "somethingrandom") + driver.wait_till_clickable(".next-btn").click() + + # Brand slide + driver.set_select("domain", "Manufacturing") + driver.wait_till_clickable(".next-btn").click() + + # Org slide + driver.set_field("company_name", "Acme Corp") + driver.wait_till_clickable(".next-btn").click() + driver.set_field("company_tagline", "Build Tools for Builders") + driver.set_field("bank_account", "BNL") + driver.wait_till_clickable(".complete-btn").click() + + # Wait for desk (Lock wait timeout error) + # driver.wait_for('#page-desktop', timeout=200) + + console = driver.get_console() + if frappe.flags.tests_verbose: + for line in console: + print(line) + print('-' * 40) + time.sleep(1) + + frappe.db.set_default('in_selenium', None) + driver.close() + + return True \ No newline at end of file diff --git a/erpnext/startup/notifications.py b/erpnext/startup/notifications.py index 5b0ce391a1..b32f41e0c0 100644 --- a/erpnext/startup/notifications.py +++ b/erpnext/startup/notifications.py @@ -60,8 +60,8 @@ def get_notification_config(): "targets": { "Company": { - "filters" : { "sales_target": ( ">", 0 ) }, - "target_field" : "sales_target", + "filters" : { "monthly_sales_target": ( ">", 0 ) }, + "target_field" : "monthly_sales_target", "value_field" : "total_monthly_sales" } } diff --git a/erpnext/tests/test_notifications.py b/erpnext/tests/test_notifications.py index 752badfed0..467ddd3b6d 100644 --- a/erpnext/tests/test_notifications.py +++ b/erpnext/tests/test_notifications.py @@ -9,31 +9,31 @@ from frappe.test_runner import make_test_objects class TestNotifications(unittest.TestCase): def setUp(self): - test_records = [ + test_records_company = [ { - "abbr": "_TC6", - "company_name": "_Test Company 6", - "country": "India", - "default_currency": "INR", - "doctype": "Company", - "domain": "Manufacturing", - "sales_target": 2000, - "chart_of_accounts": "Standard" + "abbr": "_TC6", + "company_name": "_Test Company 6", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "monthly_sales_target": 2000, + "chart_of_accounts": "Standard" }, { - "abbr": "_TC7", - "company_name": "_Test Company 7", - "country": "United States", - "default_currency": "USD", - "doctype": "Company", - "domain": "Retail", - "sales_target": 10000, - "total_monthly_sales": 1000, - "chart_of_accounts": "Standard" + "abbr": "_TC7", + "company_name": "_Test Company 7", + "country": "United States", + "default_currency": "USD", + "doctype": "Company", + "domain": "Retail", + "monthly_sales_target": 10000, + "total_monthly_sales": 1000, + "chart_of_accounts": "Standard" }, ] - make_test_objects('Company', test_records=test_records, reset=True) + make_test_objects('Company', test_records=test_records_company, reset=True) def test_get_notifications_for_targets(self): ''' diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py index 1ae92c121f..b694ea609b 100644 --- a/erpnext/utilities/activation.py +++ b/erpnext/utilities/activation.py @@ -1,3 +1,6 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + import frappe, erpnext from frappe import _ @@ -51,9 +54,8 @@ def get_help_messages(): if get_level() > 6: return [] - messages = [] - domain = frappe.db.get_value('Company', erpnext.get_default_company(), 'domain') + messages = [] message_settings = [ frappe._dict( @@ -138,7 +140,6 @@ def get_help_messages(): ) ] - for m in message_settings: if not m.domain or domain in m.domain: m.count = frappe.db.count(m.doctype) diff --git a/erpnext/utilities/user_progress.py b/erpnext/utilities/user_progress.py new file mode 100644 index 0000000000..482179beee --- /dev/null +++ b/erpnext/utilities/user_progress.py @@ -0,0 +1,241 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe, erpnext +from frappe import _ +from erpnext.setup.doctype.setup_progress.setup_progress import get_action_completed_state + +def get_slide_settings(): + defaults = frappe.defaults.get_defaults() + domain = frappe.db.get_value('Company', erpnext.get_default_company(), 'domain') + company = defaults.get("company") or '' + # Initial state of slides + return [ + frappe._dict( + action_name='Add Company', + title=_("Setup Company") if domain != 'Education' else _("Setup Institution"), + help=_('Setup your ' + ('company' if domain != 'Education' else 'institution') + ' and brand.'), + # image_src="/assets/erpnext/images/illustrations/shop.jpg", + fields=[], + done_state_title=_("You added " + company), + done_state_title_route=["Form", "Company", company], + help_links=[ + { + "label": _("Chart of Accounts"), + "url": ["https://erpnext.org/docs/user/manual/en/accounts/chart-of-accounts"] + }, + { + "label": _("Opening Balances"), + "video_id": "U5wPIvEn-0c" + } + ] + ) + , + frappe._dict( + action_name='Add Customers', + domains=('Manufacturing', 'Services', 'Retail', 'Distribution'), + icon="fa fa-group", + title=_("Add Customers"), + help=_("List a few of your customers. They could be organizations or individuals."), + fields=[ + {"fieldtype":"Section Break"}, + {"fieldtype":"Data", "fieldname":"customer", "label":_("Customer"), + "placeholder":_("Customer Name")}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Data", "fieldname":"customer_contact", + "label":_("Contact Name"), "placeholder":_("Contact Name")} + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_customers", + done_state_title=_("Go to Customers"), + done_state_title_route=["List", "Customer"], + help_links=[ + { + "label": _('Learn More'), + "url": ["https://erpnext.org/docs/user/manual/en/CRM/customer.html"] + } + ] + ), + frappe._dict( + action_name='Add Suppliers', + domains=('Manufacturing', 'Services', 'Retail', 'Distribution'), + icon="fa fa-group", + title=_("Your Suppliers"), + help=_("List a few of your suppliers. They could be organizations or individuals."), + fields=[ + {"fieldtype":"Section Break"}, + {"fieldtype":"Data", "fieldname":"supplier", "label":_("Supplier"), + "placeholder":_("Supplier Name")}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Data", "fieldname":"supplier_contact", + "label":_("Contact Name"), "placeholder":_("Contact Name")}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_suppliers", + done_state_title=_("Go to Suppliers"), + done_state_title_route=["List", "Supplier"], + help_links=[ + { + "label": _('Learn More'), + "url": ["https://erpnext.org/docs/user/manual/en/buying/supplier"] + }, + { + "label": _('Customers and Suppliers'), + "video_id": "zsrrVDk6VBs" + }, + ] + ), + frappe._dict( + action_name='Add Products', + domains=['Manufacturing', 'Services', 'Retail', 'Distribution'], + icon="fa fa-barcode", + title=_("Your Products or Services"), + help=_("List your products or services that you buy or sell."), + fields=[ + {"fieldtype":"Section Break", "show_section_border": 1}, + {"fieldtype":"Data", "fieldname":"item", "label":_("Item"), + "placeholder":_("A Product")}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Select", "fieldname":"item_uom", "label":_("UOM"), + "options":[_("Unit"), _("Nos"), _("Box"), _("Pair"), _("Kg"), _("Set"), + _("Hour"), _("Minute"), _("Litre"), _("Meter"), _("Gram")], + "default": _("Unit"), "static": 1}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Currency", "fieldname":"item_price", "label":_("Rate"), "static": 1} + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_items", + done_state_title=_("Go to Items"), + done_state_title_route=["List", "Item"], + help_links=[ + { + "label": _("Explore Sales Cycle"), + "video_id": "1eP90MWoDQM" + }, + ] + ), + + # School slides begin + frappe._dict( + action_name='Add Programs', + domains=("Education"), + title=_("Program"), + help=_("Example: Masters in Computer Science"), + fields=[ + {"fieldtype":"Section Break", "show_section_border": 1}, + {"fieldtype":"Data", "fieldname":"program", "label":_("Program"), "placeholder": _("Program Name")}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_program", + done_state_title=_("Go to Programs"), + done_state_title_route=["List", "Program"], + help_links=[ + { + "label": _("Student Application"), + "video_id": "l8PUACusN3E" + }, + ] + + ), + frappe._dict( + action_name='Add Courses', + domains=["Education"], + title=_("Course"), + help=_("Example: Basic Mathematics"), + fields=[ + {"fieldtype":"Section Break", "show_section_border": 1}, + {"fieldtype":"Data", "fieldname":"course", "label":_("Course"), "placeholder": _("Course Name")}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_course", + done_state_title=_("Go to Courses"), + done_state_title_route=["List", "Course"], + help_links=[ + { + "label": _('Add Students'), + "route": ["List", "Student"] + } + ] + ), + frappe._dict( + action_name='Add Instructors', + domains=["Education"], + title=_("Instructor"), + help=_("People who teach at your organisation"), + fields=[ + {"fieldtype":"Section Break", "show_section_border": 1}, + {"fieldtype":"Data", "fieldname":"instructor", "label":_("Instructor"), "placeholder": _("Instructor Name")}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_instructor", + done_state_title=_("Go to Instructors"), + done_state_title_route=["List", "Instructor"], + help_links=[ + { + "label": _('Student Batches'), + "route": ["List", "Student Batch"] + } + ] + ), + frappe._dict( + action_name='Add Rooms', + domains=["Education"], + title=_("Room"), + help=_("Classrooms/ Laboratories etc where lectures can be scheduled."), + fields=[ + {"fieldtype":"Section Break", "show_section_border": 1}, + {"fieldtype":"Data", "fieldname":"room", "label":_("Room")}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Int", "fieldname":"room_capacity", "label":_("Room Capacity"), "static": 1}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_room", + done_state_title=_("Go to Rooms"), + done_state_title_route=["List", "Room"], + help_links=[] + ), + # School slides end + + frappe._dict( + action_name='Add Users', + title=_("Add Users"), + help=_("Add users to your organization, other than yourself."), + fields=[ + {"fieldtype":"Section Break"}, + {"fieldtype":"Data", "fieldname":"user_email", "label":_("Email ID"), + "placeholder":_("user@example.com"), "options": "Email", "static": 1}, + {"fieldtype":"Column Break"}, + {"fieldtype":"Data", "fieldname":"user_fullname", + "label":_("Full Name"), "static": 1}, + ], + add_more=1, max_count=3, mandatory_entry=1, + submit_method="erpnext.utilities.user_progress_utils.create_users", + done_state_title=_("Go to Users"), + done_state_title_route=["List", "User"], + help_links=[ + { + "label": _('Learn More'), + "url": ["https://erpnext.org/docs/user/manual/en/setting-up/users-and-permissions"] + }, + { + "label": _('Users and Permissions'), + "video_id": "8Slw1hsTmUI" + }, + ] + ) + ] + +def get_user_progress_slides(): + slides = [] + slide_settings = get_slide_settings() + + domain = frappe.db.get_value('Company', erpnext.get_default_company(), 'domain') + + for s in slide_settings: + if not s.domains or (domain and domain in s.domains): + s.mark_as_done_method = "erpnext.setup.doctype.setup_progress.setup_progress.set_action_completed_state" + s.done = get_action_completed_state(s.action_name) or 0 + slides.append(s) + + return slides + diff --git a/erpnext/utilities/user_progress_utils.py b/erpnext/utilities/user_progress_utils.py new file mode 100644 index 0000000000..1c9c9e8aa1 --- /dev/null +++ b/erpnext/utilities/user_progress_utils.py @@ -0,0 +1,214 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe, erpnext + +import json +from frappe import _ +from frappe.utils import flt +from erpnext.setup.doctype.setup_progress.setup_progress import update_domain_actions, get_domain_actions_state + +@frappe.whitelist() +def create_customers(args_data): + args = json.loads(args_data) + defaults = frappe.defaults.get_defaults() + for i in xrange(1,4): + customer = args.get("customer_" + str(i)) + if customer: + try: + doc = frappe.get_doc({ + "doctype":"Customer", + "customer_name": customer, + "customer_type": "Company", + "customer_group": _("Commercial"), + "territory": defaults.get("country"), + "company": defaults.get("company") + }).insert() + + if args.get("customer_contact_" + str(i)): + create_contact(args.get("customer_contact_" + str(i)), + "Customer", doc.name) + except frappe.NameError: + pass + +@frappe.whitelist() +def create_suppliers(args_data): + args = json.loads(args_data) + defaults = frappe.defaults.get_defaults() + for i in xrange(1,4): + supplier = args.get("supplier_" + str(i)) + if supplier: + try: + doc = frappe.get_doc({ + "doctype":"Supplier", + "supplier_name": supplier, + "supplier_type": _("Local"), + "company": defaults.get("company") + }).insert() + + if args.get("supplier_contact_" + str(i)): + create_contact(args.get("supplier_contact_" + str(i)), + "Supplier", doc.name) + except frappe.NameError: + pass + +def create_contact(contact, party_type, party): + """Create contact based on given contact name""" + contact = contact .split(" ") + + contact = frappe.get_doc({ + "doctype":"Contact", + "first_name":contact[0], + "last_name": len(contact) > 1 and contact[1] or "" + }) + contact.append('links', dict(link_doctype=party_type, link_name=party)) + contact.insert() + +@frappe.whitelist() +def create_items(args_data): + args = json.loads(args_data) + defaults = frappe.defaults.get_defaults() + for i in xrange(1,4): + item = args.get("item_" + str(i)) + if item: + default_warehouse = "" + default_warehouse = frappe.db.get_value("Warehouse", filters={ + "warehouse_name": _("Finished Goods"), + "company": defaults.get("company_name") + }) + + try: + frappe.get_doc({ + "doctype":"Item", + "item_code": item, + "item_name": item, + "description": item, + "show_in_website": 1, + "is_sales_item": 1, + "is_purchase_item": 1, + "is_stock_item": 1, + "item_group": "Products", + "stock_uom": _(args.get("item_uom_" + str(i))), + "default_warehouse": default_warehouse + }).insert() + + if args.get("item_price_" + str(i)): + item_price = flt(args.get("item_price_" + str(i))) + + price_list_name = frappe.db.get_value("Price List", {"selling": 1}) + make_item_price(item, price_list_name, item_price) + price_list_name = frappe.db.get_value("Price List", {"buying": 1}) + make_item_price(item, price_list_name, item_price) + + except frappe.NameError: + pass + +def make_item_price(item, price_list_name, item_price): + frappe.get_doc({ + "doctype": "Item Price", + "price_list": price_list_name, + "item_code": item, + "price_list_rate": item_price + }).insert() + +# Schools +@frappe.whitelist() +def create_program(args_data): + args = json.loads(args_data) + for i in xrange(1,4): + if args.get("program_" + str(i)): + program = frappe.new_doc("Program") + program.program_code = args.get("program_" + str(i)) + program.program_name = args.get("program_" + str(i)) + try: + program.save() + except frappe.DuplicateEntryError: + pass + +@frappe.whitelist() +def create_course(args_data): + args = json.loads(args_data) + for i in xrange(1,4): + if args.get("course_" + str(i)): + course = frappe.new_doc("Course") + course.course_code = args.get("course_" + str(i)) + course.course_name = args.get("course_" + str(i)) + try: + course.save() + except frappe.DuplicateEntryError: + pass + +@frappe.whitelist() +def create_instructor(args_data): + args = json.loads(args_data) + for i in xrange(1,4): + if args.get("instructor_" + str(i)): + instructor = frappe.new_doc("Instructor") + instructor.instructor_name = args.get("instructor_" + str(i)) + try: + instructor.save() + except frappe.DuplicateEntryError: + pass + +@frappe.whitelist() +def create_room(args_data): + args = json.loads(args_data) + for i in xrange(1,4): + if args.get("room_" + str(i)): + room = frappe.new_doc("Room") + room.room_name = args.get("room_" + str(i)) + room.seating_capacity = args.get("room_capacity_" + str(i)) + try: + room.save() + except frappe.DuplicateEntryError: + pass + +@frappe.whitelist() +def create_users(args_data): + if frappe.session.user == 'Administrator': + return + args = json.loads(args_data) + defaults = frappe.defaults.get_defaults() + for i in xrange(1,4): + email = args.get("user_email_" + str(i)) + fullname = args.get("user_fullname_" + str(i)) + if email: + if not fullname: + fullname = email.split("@")[0] + + parts = fullname.split(" ", 1) + + user = frappe.get_doc({ + "doctype": "User", + "email": email, + "first_name": parts[0], + "last_name": parts[1] if len(parts) > 1 else "", + "enabled": 1, + "user_type": "System User" + }) + + # default roles + user.append_roles("Projects User", "Stock User", "Support Team") + user.flags.delay_emails = True + + if not frappe.db.get_value("User", email): + user.insert(ignore_permissions=True) + + # create employee + emp = frappe.get_doc({ + "doctype": "Employee", + "employee_name": fullname, + "user_id": email, + "status": "Active", + "company": defaults.get("company") + }) + emp.flags.ignore_mandatory = True + emp.insert(ignore_permissions = True) + +# Ennumerate the setup hooks you're going to need, apart from the slides + +@frappe.whitelist() +def update_default_domain_actions_and_get_state(): + domain = frappe.db.get_value('Company', erpnext.get_default_company(), 'domain') + update_domain_actions(domain) + return get_domain_actions_state(domain)