diff --git a/erpnext/accounts/doctype/asset/asset_list.js b/erpnext/accounts/doctype/asset/asset_list.js new file mode 100644 index 0000000000..961ab3ffd9 --- /dev/null +++ b/erpnext/accounts/doctype/asset/asset_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings['Asset'] = { + add_fields: ['image'] +} \ No newline at end of file diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index f8c9c0a841..53421b71bc 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -12,19 +12,42 @@ class InvalidItemAttributeValueError(frappe.ValidationError): pass class ItemTemplateCannotHaveStock(frappe.ValidationError): pass @frappe.whitelist() -def get_variant(template, args, variant=None): - """Validates Attributes and their Values, then looks for an exactly matching Item Variant +def get_variant(template, args=None, variant=None, manufacturer=None, + manufacturer_part_no=None): + """Validates Attributes and their Values, then looks for an exactly + matching Item Variant :param item: Template Item :param args: A dictionary with "Attribute" as key and "Attribute Value" as value """ - if isinstance(args, basestring): - args = json.loads(args) + item_template = frappe.get_doc('Item', template) - if not args: - frappe.throw(_("Please specify at least one attribute in the Attributes table")) + if item_template.variant_based_on=='Manufacturer' and manufacturer: + return make_variant_based_on_manufacturer(item_template, manufacturer, + manufacturer_part_no) + else: + if isinstance(args, basestring): + args = json.loads(args) - return find_variant(template, args, variant) + if not args: + frappe.throw(_("Please specify at least one attribute in the Attributes table")) + return find_variant(template, args, variant) + +def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no): + '''Make and return a new variant based on manufacturer and + manufacturer part no''' + from frappe.model.naming import append_number_if_name_exists + + variant = frappe.new_doc('Item') + + copy_attributes_to_variant(template, variant) + + variant.manufacturer = manufacturer + variant.manufacturer_part_no = manufacturer_part_no + + variant.item_code = append_number_if_name_exists('Item', template.name) + + return variant def validate_item_variant_attributes(item, args=None): if isinstance(item, basestring): @@ -131,6 +154,7 @@ def create_variant(item, args): template = frappe.get_doc("Item", item) variant = frappe.new_doc("Item") + variant.variant_based_on = 'Item Attribute' variant_attributes = [] for d in template.attributes: @@ -147,17 +171,28 @@ def create_variant(item, args): def copy_attributes_to_variant(item, variant): from frappe.model import no_value_fields + + # copy non no-copy fields + + exclude_fields = ["item_code", "item_name", "show_in_website"] + + if item.variant_based_on=='Manufacturer': + # don't copy manufacturer values if based on part no + exclude_fields += ['manufacturer', 'manufacturer_part_no'] + for field in item.meta.fields: if field.fieldtype not in no_value_fields and (not field.no_copy)\ - and field.fieldname not in ("item_code", "item_name", "show_in_website"): + and field.fieldname not in exclude_fields: if variant.get(field.fieldname) != item.get(field.fieldname): variant.set(field.fieldname, item.get(field.fieldname)) variant.variant_of = item.name variant.has_variants = 0 - if variant.attributes: - variant.description += "\n" - for d in variant.attributes: - variant.description += "

" + d.attribute + ": " + cstr(d.attribute_value) + "

" + + if item.variant_based_on=='Item Attribute': + if variant.attributes: + variant.description += "\n" + for d in variant.attributes: + variant.description += "

" + d.attribute + ": " + cstr(d.attribute_value) + "

" def make_variant_item_code(template_item_code, variant): """Uses template's item code and abbreviations to make variant's item code""" diff --git a/erpnext/docs/assets/img/setup/workflow-1.png b/erpnext/docs/assets/img/setup/workflow-1.png index 632e1fbc5a..7fd6f17edf 100644 Binary files a/erpnext/docs/assets/img/setup/workflow-1.png and b/erpnext/docs/assets/img/setup/workflow-1.png differ diff --git a/erpnext/docs/assets/img/setup/workflow-2.png b/erpnext/docs/assets/img/setup/workflow-2.png index 2041860720..1023a39d34 100644 Binary files a/erpnext/docs/assets/img/setup/workflow-2.png and b/erpnext/docs/assets/img/setup/workflow-2.png differ diff --git a/erpnext/docs/assets/img/setup/workflow-3.png b/erpnext/docs/assets/img/setup/workflow-3.png new file mode 100644 index 0000000000..fc29c88008 Binary files /dev/null and b/erpnext/docs/assets/img/setup/workflow-3.png differ diff --git a/erpnext/docs/assets/img/setup/workflow-4.png b/erpnext/docs/assets/img/setup/workflow-4.png new file mode 100644 index 0000000000..ee991f945d Binary files /dev/null and b/erpnext/docs/assets/img/setup/workflow-4.png differ diff --git a/erpnext/docs/assets/img/setup/workflow-5.png b/erpnext/docs/assets/img/setup/workflow-5.png new file mode 100644 index 0000000000..ad360c6e14 Binary files /dev/null and b/erpnext/docs/assets/img/setup/workflow-5.png differ diff --git a/erpnext/docs/assets/img/stock/select-mfg-for-variant.png b/erpnext/docs/assets/img/stock/select-mfg-for-variant.png new file mode 100644 index 0000000000..4da1d6ce9e Binary files /dev/null and b/erpnext/docs/assets/img/stock/select-mfg-for-variant.png differ diff --git a/erpnext/docs/assets/img/stock/set-variant-by-mfg.png b/erpnext/docs/assets/img/stock/set-variant-by-mfg.png new file mode 100644 index 0000000000..2eaa8f0fa1 Binary files /dev/null and b/erpnext/docs/assets/img/stock/set-variant-by-mfg.png differ diff --git a/erpnext/docs/user/manual/en/introduction/the-champion.md b/erpnext/docs/user/manual/en/introduction/the-champion.md index 21a238343f..472cd94753 100644 --- a/erpnext/docs/user/manual/en/introduction/the-champion.md +++ b/erpnext/docs/user/manual/en/introduction/the-champion.md @@ -12,7 +12,7 @@ attitude. Like exercise. -Human body may seem like it does not require exercise today or even tomorrow, +The human body may seem like it does not require exercise today or even tomorrow, but in the long run, if you wish to maintain your body and its health, you should get on the treadmill. diff --git a/erpnext/docs/user/manual/en/setting-up/workflows.md b/erpnext/docs/user/manual/en/setting-up/workflows.md index a4ddc3854c..5593a53a81 100644 --- a/erpnext/docs/user/manual/en/setting-up/workflows.md +++ b/erpnext/docs/user/manual/en/setting-up/workflows.md @@ -4,9 +4,9 @@ ERPNext tracks the multiple permissions before submission. Example of a leave application workflow is given below: -If an user applies for a leave, then his request will be sent to the HR -department. The HR department(HR User) will either reject or approve this -request. Once this process is completed, the user's Manager(leave approver) +If a user applies for a leave, then his request will be sent to the HR +department. The HR department (HR User) will either reject or approve this +request. Once this process is completed, the user's Manager (leave approver) will get an indication that the HR department has Accepted or Rejected. The Manager, who is the approving authority, will either Approve or Reject this request. Accordingly,the user will get his Approved or Rejected status. @@ -44,23 +44,17 @@ workflow transition step that says from submitted you can cancel. #### Example of a Leave Application Process: -Go to the Human Resources Module and click on Leave Application. Apply for a -Leave. +When a Leave Application is saved by Employee, the status of the document changes to "Applied" -When a Leave Application is submitted, the status on the right hand corner of -the page shows as "Applied" - -![Workflow Employee LA]({{docs_base_url}}/assets/old_images/erpnext/workflow-employee-la.png) +![Workflow Employee LA]({{docs_base_url}}/assets/img/setup/workflow-3.png) When the HR User logs in, he can either Approve or Reject. If approved the -status on the right hand corner of the page shows as Approved. However, a blue -band of information is displayed saying approval is pending by leave approver. +status of the document changes to "Approved by HR". However, it is yet to be approved by Leave Approver. -![Leave Approver]({{docs_base_url}}/assets/old_images/erpnext/workflow-hr-user-la.png) +![Leave Approver]({{docs_base_url}}/assets/img/setup/workflow-4.png) -When the leave approver opens the Leave Application page, he should select the -status and convert to Approved or Rejected. +When the Leave Approver opens the Leave Application page, he can finally "Approve" or "Reject" the Leave Application. -![Workflow Leave Approver]({{docs_base_url}}/assets/old_images/erpnext/workflow-leave-approver-la.png) +![Workflow Leave Approver]({{docs_base_url}}/assets/img/setup/workflow-5.png) {next} diff --git a/erpnext/docs/user/manual/en/stock/item/item-variants.md b/erpnext/docs/user/manual/en/stock/item/item-variants.md index cdca6ed961..7514404a6d 100644 --- a/erpnext/docs/user/manual/en/stock/item/item-variants.md +++ b/erpnext/docs/user/manual/en/stock/item/item-variants.md @@ -1,15 +1,28 @@ +# Item Variants + +### What are Variants? + A Item Variant is a version of a Item, such as differing sizes or differing colours (like a _blue_ t-shirt in size _small_ rather then just a t-shirt). -Without Item variants, you would have to treat the _small, medium_ and _large_ versions of a t-shirt as three separate Items; +Without Item variants, you would have to treat the _small, medium_ and _large_ versions of a t-shirt as three separate Items; Item variants let you treat the _small, medium_ and _large_ versions of a t-shirt as variations of the one Item 't-shirt'. +### Using Variants + +Variants can be based on two things + +1. Item Attributes +1. Manufacturers + +### Variants Based on Item Attributes + To use Item Variants in ERPNext, create an Item and check 'Has Variants'. -* The Item shall then be referred to as a so called 'Template'. Such a Template is not identical to a regular 'Item' any longer. For example it (the Template) can not be used directly in any Transactions (Sales Order, Delivery Note, Purchase Invoice) itself. Only the Variants of an Item (_blue_ t-shirt in size _small)_ can be practically used in such. Therefore it would be ideal to decide whether an item 'Has Variants' or not directly when creating it. +* The Item shall then be referred to as a so called 'Template'. Such a Template is not identical to a regular 'Item' any longer. For example it (the Template) can not be used directly in any Transactions (Sales Order, Delivery Note, Purchase Invoice) itself. Only the Variants of an Item (_blue_ t-shirt in size _small)_ can be practically used in such. Therefore it would be ideal to decide whether an item 'Has Variants' or not directly when creating it. Has Variants On selecting 'Has Variants' a table shall appear. Specify the variant attributes for the Item in the table. -In case the attribute has Numeric Values, you can specify the range and increment values here. +In case the attribute has Numeric Values, you can specify the range and increment values here. Valid Attributes @@ -22,3 +35,17 @@ To create 'Item Variants' against a 'Template' select 'Make Variants' Make Variants To learn more about setting Attributes Master check [Item Attributes]({{docs_base_url}}/user/manual/en/stock/setup/item-attribute.html) + +### Variants Based on Manufacturers + +To setup variants based on Manufactueres, in your Item template, set "Variants Based On" as "Manufacturers" + +Setup Item Variant by Manufacturer + +When you make a new Variant, the system will prompt you to select a Manufacturer. You can also optionally put in a Manufacturer Part Number + +Setup Item Variant by Manufacturer + +The naming of the variant will be the name (ID) of the template Item with a number suffix. e.g. "ITEM000" will have variant "ITEM000-1" \ No newline at end of file diff --git a/erpnext/hr/doctype/job_opening/job_opening.json b/erpnext/hr/doctype/job_opening/job_opening.json index 39bc75f053..cde97e5e00 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.json +++ b/erpnext/hr/doctype/job_opening/job_opening.json @@ -1,201 +1,228 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:route", - "beta": 0, - "creation": "2013-01-15 16:13:36", - "custom": 0, - "description": "Description of a Job Opening", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:route", + "beta": 0, + "creation": "2013-01-15 16:13:36", + "custom": 0, + "description": "Description of a Job Opening", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "job_title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Job Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "job_title", + "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": "Job Title", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "publish", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Publish on website", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "publish", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Publish on website", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "publish", - "fieldname": "route", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Route", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "publish", + "fieldname": "route", + "fieldtype": "Data", + "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": "Route", + "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": 1 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nClosed", - "permlevel": 0, - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nClosed", + "permlevel": 0, + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Job profile, qualifications required etc.", - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Job profile, qualifications required etc.", + "fieldname": "description", + "fieldtype": "Text Editor", + "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": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "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 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-bookmark", - "idx": 1, - "image_view": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-12-19 05:54:38.298496", - "modified_by": "Administrator", - "module": "HR", - "name": "Job Opening", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-bookmark", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-03-22 12:36:26.807200", + "modified_by": "Administrator", + "module": "HR", + "name": "Job Opening", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "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": 1, + "role": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Guest", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 0 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "ASC", + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_order": "ASC", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index b8382ecb23..c629e87571 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -148,10 +148,9 @@ class SalarySlip(TransactionBase): }) def get_date_details(self): - if not self.end_date: - date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date) - self.start_date = date_details.start_date - self.end_date = date_details.end_date + date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date) + self.start_date = date_details.start_date + self.end_date = date_details.end_date def check_sal_struct(self, joining_date, relieving_date): cond = '' diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css index 697c078289..a51472feaa 100644 --- a/erpnext/public/css/erpnext.css +++ b/erpnext/public/css/erpnext.css @@ -327,4 +327,3 @@ body[data-route="pos"] .btn-more { body[data-route="pos"] .collapse-btn { cursor: pointer; } - diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 891f37cb28..f149baf16b 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -177,11 +177,11 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( fields: [ {fieldtype:'Read Only', fieldname:'item_code', label: __('Item Code'), in_list_view:1}, - {fieldtype:'Link', fieldname:'bom', options: 'BOM', + {fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1, label: __('Select BOM'), in_list_view:1, get_query: function(doc) { return {filters: {item: doc.item_code}}; }}, - {fieldtype:'Float', fieldname:'pending_qty', + {fieldtype:'Float', fieldname:'pending_qty', reqd: 1, label: __('Qty'), in_list_view:1}, ], get_data: function() { diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index b427b94066..61ec78c651 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -31,10 +31,14 @@ def get_cart_quotation(doc=None): doc = quotation set_cart_count(quotation) + addresses = get_address_docs(party=party) + return { "doc": decorate_quotation_doc(doc), - "addresses": [{"name": address.name, "display": address.display} - for address in get_address_docs(party=party)], + "shipping_addresses": [{"name": address.name, "display": address.display} + for address in addresses if address.address_type == "Shipping"], + "billing_addresses": [{"name": address.name, "display": address.display} + for address in addresses if address.address_type == "Billing"], "shipping_rules": get_applicable_shipping_rules(party) } diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 7068c9972b..8327ea7497 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -261,6 +261,44 @@ $.extend(erpnext.item, { make_variant: function(frm) { var fields = [] + if(frm.doc.variant_based_on==="Item Attribute") { + erpnext.item.show_modal_for_item_attribute_selection(frm); + } else { + erpnext.item.show_modal_for_manufacturers(frm); + } + }, + + show_modal_for_manufacturers: function(frm) { + var dialog = new frappe.ui.Dialog({ + fields: [ + {fieldtype:'Link', options:'Manufacturer', + reqd:1, label:'Manufacturer'}, + {fieldtype:'Data', label:'Manufacturer Part Number', + fieldname: 'manufacturer_part_no'}, + ] + }); + + dialog.set_primary_action(__('Make'), function() { + var data = dialog.get_values(); + if(!data) return; + + // call the server to make the variant + data.template = frm.doc.name; + frappe.call({ + method:"erpnext.controllers.item_variant.get_variant", + args: data, + callback: function(r) { + var doclist = frappe.model.sync(r.message); + dialog.hide(); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }) + + dialog.show(); + }, + + show_modal_for_item_attribute_selection: function(frm) { for(var i=0;i< frm.doc.attributes.length;i++){ var fieldtype, desc; var row = frm.doc.attributes[i]; @@ -371,13 +409,42 @@ $.extend(erpnext.item, { }) }); }, - toggle_attributes: function(frm) { - frm.toggle_display("attributes", frm.doc.has_variants || frm.doc.variant_of); - frm.fields_dict.attributes.grid.toggle_reqd("attribute_value", frm.doc.variant_of ? 1 : 0); - frm.fields_dict.attributes.grid.set_column_disp("attribute_value", frm.doc.variant_of ? 1 : 0); - frm.toggle_enable("attributes", !frm.doc.variant_of); - frm.fields_dict.attributes.grid.toggle_enable("attribute", !frm.doc.variant_of); - frm.fields_dict.attributes.grid.toggle_enable("attribute_value", !frm.doc.variant_of); + toggle_attributes: function(frm) { + if((frm.doc.has_variants || frm.doc.variant_of) + && frm.doc.variant_based_on==='Item Attribute') { + frm.toggle_display("attributes", true); + + var grid = frm.fields_dict.attributes.grid; + + if(frm.doc.variant_of) { + // variant + + // value column is displayed but not editable + grid.set_column_disp("attribute_value", true); + grid.toggle_enable("attribute_value", false); + + grid.toggle_enable("attribute", false); + + // can't change attributes since they are + // saved when the variant was created + frm.toggle_enable("attributes", false); + } else { + // template - values not required! + + // make the grid editable + frm.toggle_enable("attributes", true); + + // value column is hidden + grid.set_column_disp("attribute_value", false); + + // enable the grid so you can add more attributes + grid.toggle_enable("attribute", true); + } + + } else { + // nothing to do with attributes, hide it + frm.toggle_display("attributes", false); + } } }); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 4a5094e34b..db327cc23f 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:item_code", @@ -1218,7 +1219,39 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "", + "default": "Item Attribute", + "depends_on": "has_variants", + "fieldname": "variant_based_on", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Variant Based On", + "length": 0, + "no_copy": 0, + "options": "Item Attribute\nManufacturer", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.has_variants && doc.variant_based_on==='Item Attribute'", "fieldname": "attributes", "fieldtype": "Table", "hidden": 1, @@ -2792,6 +2825,7 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-tag", @@ -2799,12 +2833,11 @@ "image_field": "image", "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2017-02-20 13:26:45.446617", + "modified": "2017-03-21 21:03:10.715674", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 0f0205bae1..4d0c3ac881 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -643,7 +643,7 @@ class Item(WebsiteGenerator): .format(self.stock_uom, template_uom)) def validate_attributes(self): - if self.has_variants or self.variant_of: + if (self.has_variants or self.variant_of) and self.variant_based_on=='Item Attribute': attributes = [] if not self.attributes: frappe.throw(_("Attribute table is mandatory")) @@ -654,7 +654,7 @@ class Item(WebsiteGenerator): attributes.append(d.attribute) def validate_variant_attributes(self): - if self.variant_of: + if self.variant_of and self.variant_based_on=='Item Attribute': args = {} for d in self.attributes: if not d.attribute_value: @@ -675,7 +675,7 @@ def get_timeline_data(doctype, name): from `tabStock Ledger Entry` where item_code=%s and posting_date > date_sub(curdate(), interval 1 year) group by posting_date''', name)) - + for date, count in items.iteritems(): timestamp = get_timestamp(date) out.update({ timestamp: count }) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 15a11186e7..2a8e4344af 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -7,7 +7,7 @@ import frappe from frappe.test_runner import make_test_records from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, - InvalidItemAttributeValueError) + InvalidItemAttributeValueError, get_variant) from frappe.model.rename_doc import rename_doc from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -167,31 +167,66 @@ class TestItem(unittest.TestCase): variant.item_name = "_Test Numeric Variant Large 1.1m" self.assertRaises(InvalidItemAttributeValueError, variant.save) - variant = create_variant("_Test Numeric Template Item", + variant = create_variant("_Test Numeric Template Item", {"Test Size": "Large", "Test Item Length": 1.5}) self.assertEquals(variant.item_code, "_Test Numeric Template Item-L-1.5") variant.item_code = "_Test Numeric Variant-L-1.5" variant.item_name = "_Test Numeric Variant Large 1.5m" variant.save() - - def test_item_merging(self): + + def test_item_merging(self): create_item("Test Item for Merging 1") create_item("Test Item for Merging 2") - - make_stock_entry(item_code="Test Item for Merging 1", target="_Test Warehouse - _TC", + + make_stock_entry(item_code="Test Item for Merging 1", target="_Test Warehouse - _TC", qty=1, rate=100) - make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC", + make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC", qty=1, rate=100) - + rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True) - + self.assertFalse(frappe.db.exists("Item", "Test Item for Merging 1")) - - self.assertTrue(frappe.db.get_value("Bin", + + self.assertTrue(frappe.db.get_value("Bin", {"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse - _TC"})) - - self.assertTrue(frappe.db.get_value("Bin", - {"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"})) + + self.assertTrue(frappe.db.get_value("Bin", + {"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"})) + + def test_item_variant_by_manufacturer(self): + if frappe.db.exists('Item', '_Test Variant Mfg'): + frappe.delete_doc('Item', '_Test Variant Mfg') + if frappe.db.exists('Item', '_Test Variant Mfg-1'): + frappe.delete_doc('Item', '_Test Variant Mfg-1') + if frappe.db.exists('Manufacturer', 'MSG1'): + frappe.delete_doc('Manufacturer', 'MSG1') + + template = frappe.get_doc(dict( + doctype='Item', + item_code='_Test Variant Mfg', + has_variant=1, + item_group='Products', + variant_based_on='Manufacturer' + )).insert() + + manufacturer = frappe.get_doc(dict( + doctype='Manufacturer', + short_name='MSG1' + )).insert() + + variant = get_variant(template.name, manufacturer=manufacturer.name) + self.assertEquals(variant.item_code, '_Test Variant Mfg-1') + self.assertEquals(variant.description, '_Test Variant Mfg') + self.assertEquals(variant.manufacturer, 'MSG1') + variant.insert() + + variant = get_variant(template.name, manufacturer=manufacturer.name, + manufacturer_part_no='007') + self.assertEquals(variant.item_code, '_Test Variant Mfg-2') + self.assertEquals(variant.description, '_Test Variant Mfg') + self.assertEquals(variant.manufacturer, 'MSG1') + self.assertEquals(variant.manufacturer_part_no, '007') + def make_item_variant(): if not frappe.db.exists("Item", "_Test Variant Item-S"): @@ -215,6 +250,5 @@ def create_item(item_code, is_stock_item=None): item.item_name = item_code item.description = item_code item.item_group = "All Item Groups" - item.is_stock_item = is_stock_item or 1 + item.is_stock_item = is_stock_item or 1 item.save() - \ No newline at end of file diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 9bec3960b2..8a0be85214 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -63,7 +63,7 @@ def get_list_context(context=None): 'no_breadcrumbs': True } -def get_issue_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by = None): +def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None): from frappe.www.list import get_list user = frappe.session.user ignore_permissions = False diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js index dbb93a94b1..ee98b23a92 100644 --- a/erpnext/templates/includes/cart.js +++ b/erpnext/templates/includes/cart.js @@ -24,9 +24,17 @@ $.extend(shopping_cart, { if($(this).prop("checked")) { var me = this; + // uncheck other shipping or billing addresses: + if ( $(this).is('input[data-fieldname=customer_address]') ) { + $('input[data-fieldname=customer_address]').not(this).prop('checked', false); + } else { + $('input[data-fieldname=shipping_address_name]').not(this).prop('checked', false); + } + return frappe.call({ type: "POST", method: "erpnext.shopping_cart.cart.update_cart_address", + freeze: true, args: { address_fieldname: $(this).attr("data-fieldname"), address_name: $(this).attr("data-address-name") diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index 29d4f4b606..d83f6092a1 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -7,7 +7,7 @@
{{ _("Shipping Address") }}
- {% for address in addresses %} + {% for address in shipping_addresses %} {{ show_address(address, doc, "shipping_address_name", select_address) }} {% endfor %}
@@ -18,7 +18,7 @@
Billing Address
- {% for address in addresses %} + {% for address in billing_addresses %} {{ show_address(address, doc, "customer_address", select_address) }} {% endfor %}