├── .gitignore ├── MANIFEST.in ├── README.md ├── license.txt ├── requirements.txt ├── setup.py └── spp ├── __init__.py ├── config ├── __init__.py ├── desktop.py └── docs.py ├── docs └── .gitkeep ├── hooks.py ├── modules.txt ├── patches.txt ├── public ├── .gitkeep ├── css │ └── .gitkeep └── js │ └── .gitkeep ├── simplified_production_process ├── __init__.py └── doctype │ ├── __init__.py │ ├── simplified_production_items │ ├── __init__.py │ ├── simplified_production_items.json │ └── simplified_production_items.py │ ├── simplified_production_material_request │ ├── __init__.py │ ├── simplified_production_material_request.json │ └── simplified_production_material_request.py │ ├── simplified_production_sales_orders │ ├── __init__.py │ ├── simplified_production_sales_orders.json │ └── simplified_production_sales_orders.py │ └── simplified_production_tool │ ├── __init__.py │ ├── simplified_production_tool.js │ ├── simplified_production_tool.json │ ├── simplified_production_tool.py │ ├── test_simplified_production_tool.js │ └── test_simplified_production_tool.py └── templates ├── __init__.py └── pages └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | spp/docs/current -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include spp *.css 8 | recursive-include spp *.csv 9 | recursive-include spp *.html 10 | recursive-include spp *.ico 11 | recursive-include spp *.js 12 | recursive-include spp *.json 13 | recursive-include spp *.md 14 | recursive-include spp *.png 15 | recursive-include spp *.py 16 | recursive-include spp *.svg 17 | recursive-include spp *.txt 18 | recursive-exclude spp *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simplified Production Process 2 | 3 | Simplified production process was suggested first by @tara_antonius and we talked about it there: 4 | https://discuss.erpnext.com/t/manufacturing-process/17013 5 | 6 | SPP makes stock entries from BOMs to track a manufacture process without production orders. 7 | 8 | The code comes from Production Planning Tool. 9 | 10 | #### Please contribute 11 | 12 | Get orders from Material Request is not working for now. 13 | 14 | 15 | #### How to use 16 | 17 | Select some ordes with the filters, click on "get items", and then "manufacture items" 18 | Stock entries will be created and submited. 19 | 20 | Raw material source warehouse and Finished products destination warehouse are set from default warehouses settings inside 'Manufacturing Settings'. Leave one of this field or both fields empty and the default item's warehouse will be used. 21 | 22 | 23 | #### Installation 24 | 25 | ```shell 26 | bench get-app spp https://github.com/hiousi/erpnext-spp 27 | bench --site site1.local install-app spp ` 28 | ``` 29 | 30 | #### License 31 | 32 | MIT -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | frappe -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | import re, ast 4 | 5 | with open('requirements.txt') as f: 6 | install_requires = f.read().strip().split('\n') 7 | 8 | # get version from __version__ variable in spp/__init__.py 9 | _version_re = re.compile(r'__version__\s+=\s+(.*)') 10 | 11 | with open('spp/__init__.py', 'rb') as f: 12 | version = str(ast.literal_eval(_version_re.search( 13 | f.read().decode('utf-8')).group(1))) 14 | 15 | setup( 16 | name='spp', 17 | version=version, 18 | description='SPP makes stock entries from BOMs', 19 | author='hiousi', 20 | packages=find_packages(), 21 | zip_safe=False, 22 | include_package_data=True, 23 | install_requires=install_requires 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /spp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | __version__ = '0.0.1' 5 | 6 | -------------------------------------------------------------------------------- /spp/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/config/__init__.py -------------------------------------------------------------------------------- /spp/config/desktop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from frappe import _ 4 | 5 | def get_data(): 6 | return [ 7 | { 8 | "module_name": "Simplified Production Process", 9 | "color": "grey", 10 | "icon": "octicon octicon-file-directory", 11 | "type": "module", 12 | "label": _("Simplified Production Process") 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /spp/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/spp" 6 | # docs_base_url = "https://[org_name].github.io/spp" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Simplified Production Process" 12 | -------------------------------------------------------------------------------- /spp/docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/docs/.gitkeep -------------------------------------------------------------------------------- /spp/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from . import __version__ as app_version 4 | 5 | app_name = "spp" 6 | app_title = "Simplified Production Process" 7 | app_publisher = "o10c" 8 | app_description = "SPP makes stock entries from BOMs" 9 | app_icon = "octicon octicon-tools" 10 | app_color = "grey" 11 | app_email = "o10c@yahoo.fr" 12 | app_license = "MIT" 13 | 14 | # Includes in 15 | # ------------------ 16 | 17 | # include js, css files in header of desk.html 18 | # app_include_css = "/assets/spp/css/spp.css" 19 | # app_include_js = "/assets/spp/js/spp.js" 20 | 21 | # include js, css files in header of web template 22 | # web_include_css = "/assets/spp/css/spp.css" 23 | # web_include_js = "/assets/spp/js/spp.js" 24 | 25 | # include js in page 26 | # page_js = {"page" : "public/js/file.js"} 27 | 28 | # include js in doctype views 29 | # doctype_js = {"doctype" : "public/js/doctype.js"} 30 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 31 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 32 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 33 | 34 | # Home Pages 35 | # ---------- 36 | 37 | # application home page (will override Website Settings) 38 | # home_page = "login" 39 | 40 | # website user home page (by Role) 41 | # role_home_page = { 42 | # "Role": "home_page" 43 | # } 44 | 45 | # Website user home page (by function) 46 | # get_website_user_home_page = "spp.utils.get_home_page" 47 | 48 | # Generators 49 | # ---------- 50 | 51 | # automatically create page for each record of this doctype 52 | # website_generators = ["Web Page"] 53 | 54 | # Installation 55 | # ------------ 56 | 57 | # before_install = "spp.install.before_install" 58 | # after_install = "spp.install.after_install" 59 | 60 | # Desk Notifications 61 | # ------------------ 62 | # See frappe.core.notifications.get_notification_config 63 | 64 | # notification_config = "spp.notifications.get_notification_config" 65 | 66 | # Permissions 67 | # ----------- 68 | # Permissions evaluated in scripted ways 69 | 70 | # permission_query_conditions = { 71 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 72 | # } 73 | # 74 | # has_permission = { 75 | # "Event": "frappe.desk.doctype.event.event.has_permission", 76 | # } 77 | 78 | # Document Events 79 | # --------------- 80 | # Hook on document methods and events 81 | 82 | # doc_events = { 83 | # "*": { 84 | # "on_update": "method", 85 | # "on_cancel": "method", 86 | # "on_trash": "method" 87 | # } 88 | # } 89 | 90 | # Scheduled Tasks 91 | # --------------- 92 | 93 | # scheduler_events = { 94 | # "all": [ 95 | # "spp.tasks.all" 96 | # ], 97 | # "daily": [ 98 | # "spp.tasks.daily" 99 | # ], 100 | # "hourly": [ 101 | # "spp.tasks.hourly" 102 | # ], 103 | # "weekly": [ 104 | # "spp.tasks.weekly" 105 | # ] 106 | # "monthly": [ 107 | # "spp.tasks.monthly" 108 | # ] 109 | # } 110 | 111 | # Testing 112 | # ------- 113 | 114 | # before_tests = "spp.install.before_tests" 115 | 116 | # Overriding Whitelisted Methods 117 | # ------------------------------ 118 | # 119 | # override_whitelisted_methods = { 120 | # "frappe.desk.doctype.event.event.get_events": "spp.event.get_events" 121 | # } 122 | 123 | -------------------------------------------------------------------------------- /spp/modules.txt: -------------------------------------------------------------------------------- 1 | Simplified Production Process -------------------------------------------------------------------------------- /spp/patches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/patches.txt -------------------------------------------------------------------------------- /spp/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/public/.gitkeep -------------------------------------------------------------------------------- /spp/public/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/public/css/.gitkeep -------------------------------------------------------------------------------- /spp/public/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/public/js/.gitkeep -------------------------------------------------------------------------------- /spp/simplified_production_process/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/doctype/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_items/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/doctype/simplified_production_items/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_items/simplified_production_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_guest_to_view": 0, 4 | "allow_import": 0, 5 | "allow_rename": 0, 6 | "autoname": "hash", 7 | "beta": 0, 8 | "creation": "2017-08-24 14:53:21.646002", 9 | "custom": 0, 10 | "docstatus": 0, 11 | "doctype": "DocType", 12 | "document_type": "", 13 | "editable_grid": 1, 14 | "engine": "InnoDB", 15 | "fields": [ 16 | { 17 | "allow_bulk_edit": 0, 18 | "allow_on_submit": 0, 19 | "bold": 0, 20 | "collapsible": 0, 21 | "columns": 0, 22 | "fieldname": "item_code", 23 | "fieldtype": "Link", 24 | "hidden": 0, 25 | "ignore_user_permissions": 0, 26 | "ignore_xss_filter": 0, 27 | "in_filter": 0, 28 | "in_global_search": 0, 29 | "in_list_view": 1, 30 | "in_standard_filter": 0, 31 | "label": "Item Code", 32 | "length": 0, 33 | "no_copy": 0, 34 | "options": "Item", 35 | "permlevel": 0, 36 | "precision": "", 37 | "print_hide": 0, 38 | "print_hide_if_no_value": 0, 39 | "read_only": 0, 40 | "remember_last_selected_value": 0, 41 | "report_hide": 0, 42 | "reqd": 1, 43 | "search_index": 0, 44 | "set_only_once": 0, 45 | "unique": 0 46 | }, 47 | { 48 | "allow_bulk_edit": 0, 49 | "allow_on_submit": 0, 50 | "bold": 0, 51 | "collapsible": 0, 52 | "columns": 0, 53 | "fieldname": "bom_no", 54 | "fieldtype": "Link", 55 | "hidden": 0, 56 | "ignore_user_permissions": 0, 57 | "ignore_xss_filter": 0, 58 | "in_filter": 0, 59 | "in_global_search": 0, 60 | "in_list_view": 0, 61 | "in_standard_filter": 0, 62 | "label": "BOM", 63 | "length": 0, 64 | "no_copy": 0, 65 | "options": "BOM", 66 | "permlevel": 0, 67 | "precision": "", 68 | "print_hide": 0, 69 | "print_hide_if_no_value": 0, 70 | "read_only": 0, 71 | "remember_last_selected_value": 0, 72 | "report_hide": 0, 73 | "reqd": 1, 74 | "search_index": 0, 75 | "set_only_once": 0, 76 | "unique": 0 77 | }, 78 | { 79 | "allow_bulk_edit": 0, 80 | "allow_on_submit": 0, 81 | "bold": 0, 82 | "collapsible": 0, 83 | "columns": 0, 84 | "fieldname": "planned_qty", 85 | "fieldtype": "Float", 86 | "hidden": 0, 87 | "ignore_user_permissions": 0, 88 | "ignore_xss_filter": 0, 89 | "in_filter": 0, 90 | "in_global_search": 0, 91 | "in_list_view": 1, 92 | "in_standard_filter": 0, 93 | "label": "Planned Qty", 94 | "length": 0, 95 | "no_copy": 0, 96 | "permlevel": 0, 97 | "precision": "", 98 | "print_hide": 0, 99 | "print_hide_if_no_value": 0, 100 | "read_only": 0, 101 | "remember_last_selected_value": 0, 102 | "report_hide": 0, 103 | "reqd": 1, 104 | "search_index": 0, 105 | "set_only_once": 0, 106 | "unique": 0 107 | }, 108 | { 109 | "allow_bulk_edit": 0, 110 | "allow_on_submit": 0, 111 | "bold": 0, 112 | "collapsible": 0, 113 | "columns": 0, 114 | "fieldname": "plannad_start_date", 115 | "fieldtype": "Datetime", 116 | "hidden": 0, 117 | "ignore_user_permissions": 0, 118 | "ignore_xss_filter": 0, 119 | "in_filter": 0, 120 | "in_global_search": 0, 121 | "in_list_view": 1, 122 | "in_standard_filter": 0, 123 | "label": "Planned Start Date", 124 | "length": 0, 125 | "no_copy": 0, 126 | "permlevel": 0, 127 | "precision": "", 128 | "print_hide": 0, 129 | "print_hide_if_no_value": 0, 130 | "read_only": 0, 131 | "remember_last_selected_value": 0, 132 | "report_hide": 0, 133 | "reqd": 1, 134 | "search_index": 0, 135 | "set_only_once": 0, 136 | "unique": 0 137 | }, 138 | { 139 | "allow_bulk_edit": 0, 140 | "allow_on_submit": 0, 141 | "bold": 0, 142 | "collapsible": 0, 143 | "columns": 0, 144 | "fieldname": "column_break_5", 145 | "fieldtype": "Column Break", 146 | "hidden": 0, 147 | "ignore_user_permissions": 0, 148 | "ignore_xss_filter": 0, 149 | "in_filter": 0, 150 | "in_global_search": 0, 151 | "in_list_view": 0, 152 | "in_standard_filter": 0, 153 | "length": 0, 154 | "no_copy": 0, 155 | "permlevel": 0, 156 | "precision": "", 157 | "print_hide": 0, 158 | "print_hide_if_no_value": 0, 159 | "read_only": 0, 160 | "remember_last_selected_value": 0, 161 | "report_hide": 0, 162 | "reqd": 0, 163 | "search_index": 0, 164 | "set_only_once": 0, 165 | "unique": 0 166 | }, 167 | { 168 | "allow_bulk_edit": 0, 169 | "allow_on_submit": 0, 170 | "bold": 0, 171 | "collapsible": 0, 172 | "columns": 0, 173 | "fieldname": "warehouse", 174 | "fieldtype": "Link", 175 | "hidden": 0, 176 | "ignore_user_permissions": 0, 177 | "ignore_xss_filter": 0, 178 | "in_filter": 0, 179 | "in_global_search": 0, 180 | "in_list_view": 0, 181 | "in_standard_filter": 0, 182 | "label": "Warehouse", 183 | "length": 0, 184 | "no_copy": 0, 185 | "options": "Warehouse", 186 | "permlevel": 0, 187 | "precision": "", 188 | "print_hide": 0, 189 | "print_hide_if_no_value": 0, 190 | "read_only": 0, 191 | "remember_last_selected_value": 0, 192 | "report_hide": 0, 193 | "reqd": 0, 194 | "search_index": 0, 195 | "set_only_once": 0, 196 | "unique": 0 197 | }, 198 | { 199 | "allow_bulk_edit": 0, 200 | "allow_on_submit": 0, 201 | "bold": 0, 202 | "collapsible": 0, 203 | "columns": 0, 204 | "fieldname": "sales_order", 205 | "fieldtype": "Link", 206 | "hidden": 0, 207 | "ignore_user_permissions": 0, 208 | "ignore_xss_filter": 0, 209 | "in_filter": 0, 210 | "in_global_search": 0, 211 | "in_list_view": 0, 212 | "in_standard_filter": 0, 213 | "label": "Sales Order", 214 | "length": 0, 215 | "no_copy": 0, 216 | "options": "Sales Order", 217 | "permlevel": 0, 218 | "precision": "", 219 | "print_hide": 0, 220 | "print_hide_if_no_value": 0, 221 | "read_only": 1, 222 | "remember_last_selected_value": 0, 223 | "report_hide": 0, 224 | "reqd": 0, 225 | "search_index": 0, 226 | "set_only_once": 0, 227 | "unique": 0 228 | }, 229 | { 230 | "allow_bulk_edit": 0, 231 | "allow_on_submit": 0, 232 | "bold": 0, 233 | "collapsible": 0, 234 | "columns": 0, 235 | "fieldname": "material_request", 236 | "fieldtype": "Link", 237 | "hidden": 0, 238 | "ignore_user_permissions": 0, 239 | "ignore_xss_filter": 0, 240 | "in_filter": 0, 241 | "in_global_search": 0, 242 | "in_list_view": 0, 243 | "in_standard_filter": 0, 244 | "label": "Material Request", 245 | "length": 0, 246 | "no_copy": 0, 247 | "options": "Material Request", 248 | "permlevel": 0, 249 | "precision": "", 250 | "print_hide": 0, 251 | "print_hide_if_no_value": 0, 252 | "read_only": 1, 253 | "remember_last_selected_value": 0, 254 | "report_hide": 0, 255 | "reqd": 0, 256 | "search_index": 0, 257 | "set_only_once": 0, 258 | "unique": 0 259 | }, 260 | { 261 | "allow_bulk_edit": 0, 262 | "allow_on_submit": 0, 263 | "bold": 0, 264 | "collapsible": 0, 265 | "columns": 0, 266 | "fieldname": "pending_qty", 267 | "fieldtype": "Float", 268 | "hidden": 0, 269 | "ignore_user_permissions": 0, 270 | "ignore_xss_filter": 0, 271 | "in_filter": 0, 272 | "in_global_search": 0, 273 | "in_list_view": 1, 274 | "in_standard_filter": 0, 275 | "label": "Pending Qty", 276 | "length": 0, 277 | "no_copy": 0, 278 | "permlevel": 0, 279 | "precision": "", 280 | "print_hide": 0, 281 | "print_hide_if_no_value": 0, 282 | "read_only": 1, 283 | "remember_last_selected_value": 0, 284 | "report_hide": 0, 285 | "reqd": 0, 286 | "search_index": 0, 287 | "set_only_once": 0, 288 | "unique": 0, 289 | "width": "100px" 290 | }, 291 | { 292 | "allow_bulk_edit": 0, 293 | "allow_on_submit": 0, 294 | "bold": 0, 295 | "collapsible": 0, 296 | "columns": 0, 297 | "fieldname": "stock_uom", 298 | "fieldtype": "Link", 299 | "hidden": 0, 300 | "ignore_user_permissions": 0, 301 | "ignore_xss_filter": 0, 302 | "in_filter": 0, 303 | "in_global_search": 0, 304 | "in_list_view": 0, 305 | "in_standard_filter": 0, 306 | "label": "UOM", 307 | "length": 0, 308 | "no_copy": 0, 309 | "options": "UOM", 310 | "permlevel": 0, 311 | "precision": "", 312 | "print_hide": 0, 313 | "print_hide_if_no_value": 0, 314 | "read_only": 1, 315 | "remember_last_selected_value": 0, 316 | "report_hide": 0, 317 | "reqd": 1, 318 | "search_index": 0, 319 | "set_only_once": 0, 320 | "unique": 0, 321 | "width": "80px" 322 | }, 323 | { 324 | "allow_bulk_edit": 0, 325 | "allow_on_submit": 0, 326 | "bold": 0, 327 | "collapsible": 0, 328 | "columns": 0, 329 | "fieldname": "description", 330 | "fieldtype": "Text Editor", 331 | "hidden": 0, 332 | "ignore_user_permissions": 0, 333 | "ignore_xss_filter": 0, 334 | "in_filter": 0, 335 | "in_global_search": 0, 336 | "in_list_view": 1, 337 | "in_standard_filter": 0, 338 | "label": "Description", 339 | "length": 0, 340 | "no_copy": 0, 341 | "permlevel": 0, 342 | "precision": "", 343 | "print_hide": 0, 344 | "print_hide_if_no_value": 0, 345 | "read_only": 0, 346 | "remember_last_selected_value": 0, 347 | "report_hide": 0, 348 | "reqd": 0, 349 | "search_index": 0, 350 | "set_only_once": 0, 351 | "unique": 0 352 | }, 353 | { 354 | "allow_bulk_edit": 0, 355 | "allow_on_submit": 0, 356 | "bold": 0, 357 | "collapsible": 0, 358 | "columns": 0, 359 | "fieldname": "material_request_item", 360 | "fieldtype": "Data", 361 | "hidden": 0, 362 | "ignore_user_permissions": 0, 363 | "ignore_xss_filter": 0, 364 | "in_filter": 0, 365 | "in_global_search": 0, 366 | "in_list_view": 0, 367 | "in_standard_filter": 0, 368 | "label": "material_request_item", 369 | "length": 0, 370 | "no_copy": 0, 371 | "permlevel": 0, 372 | "precision": "", 373 | "print_hide": 0, 374 | "print_hide_if_no_value": 0, 375 | "read_only": 0, 376 | "remember_last_selected_value": 0, 377 | "report_hide": 0, 378 | "reqd": 0, 379 | "search_index": 0, 380 | "set_only_once": 0, 381 | "unique": 0 382 | } 383 | ], 384 | "has_web_view": 0, 385 | "hide_heading": 0, 386 | "hide_toolbar": 0, 387 | "idx": 0, 388 | "image_view": 0, 389 | "in_create": 0, 390 | "is_submittable": 0, 391 | "issingle": 0, 392 | "istable": 1, 393 | "max_attachments": 0, 394 | "modified": "2017-10-13 18:26:25.386906", 395 | "modified_by": "Administrator", 396 | "module": "Simplified Production Process", 397 | "name": "Simplified Production Items", 398 | "name_case": "", 399 | "owner": "Administrator", 400 | "permissions": [], 401 | "quick_entry": 1, 402 | "read_only": 0, 403 | "read_only_onload": 0, 404 | "show_name_in_global_search": 0, 405 | "sort_field": "modified", 406 | "sort_order": "DESC", 407 | "track_changes": 1, 408 | "track_seen": 0 409 | } -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_items/simplified_production_items.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017, o10c and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.model.document import Document 8 | 9 | class SimplifiedProductionItems(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_material_request/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/doctype/simplified_production_material_request/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_material_request/simplified_production_material_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_guest_to_view": 0, 4 | "allow_import": 0, 5 | "allow_rename": 0, 6 | "beta": 0, 7 | "creation": "2017-08-24 15:33:26.975390", 8 | "custom": 0, 9 | "docstatus": 0, 10 | "doctype": "DocType", 11 | "document_type": "", 12 | "editable_grid": 1, 13 | "engine": "InnoDB", 14 | "fields": [ 15 | { 16 | "allow_bulk_edit": 0, 17 | "allow_on_submit": 0, 18 | "bold": 0, 19 | "collapsible": 0, 20 | "columns": 0, 21 | "fieldname": "material_request", 22 | "fieldtype": "Link", 23 | "hidden": 0, 24 | "ignore_user_permissions": 0, 25 | "ignore_xss_filter": 0, 26 | "in_filter": 0, 27 | "in_global_search": 0, 28 | "in_list_view": 0, 29 | "in_standard_filter": 0, 30 | "label": "Material Request", 31 | "length": 0, 32 | "no_copy": 0, 33 | "options": "Material Request", 34 | "permlevel": 0, 35 | "precision": "", 36 | "print_hide": 0, 37 | "print_hide_if_no_value": 0, 38 | "read_only": 0, 39 | "remember_last_selected_value": 0, 40 | "report_hide": 0, 41 | "reqd": 0, 42 | "search_index": 0, 43 | "set_only_once": 0, 44 | "unique": 0 45 | }, 46 | { 47 | "allow_bulk_edit": 0, 48 | "allow_on_submit": 0, 49 | "bold": 0, 50 | "collapsible": 0, 51 | "columns": 0, 52 | "fieldname": "column_break_2", 53 | "fieldtype": "Column Break", 54 | "hidden": 0, 55 | "ignore_user_permissions": 0, 56 | "ignore_xss_filter": 0, 57 | "in_filter": 0, 58 | "in_global_search": 0, 59 | "in_list_view": 0, 60 | "in_standard_filter": 0, 61 | "length": 0, 62 | "no_copy": 0, 63 | "permlevel": 0, 64 | "precision": "", 65 | "print_hide": 0, 66 | "print_hide_if_no_value": 0, 67 | "read_only": 0, 68 | "remember_last_selected_value": 0, 69 | "report_hide": 0, 70 | "reqd": 0, 71 | "search_index": 0, 72 | "set_only_once": 0, 73 | "unique": 0 74 | }, 75 | { 76 | "allow_bulk_edit": 0, 77 | "allow_on_submit": 0, 78 | "bold": 0, 79 | "collapsible": 0, 80 | "columns": 0, 81 | "fieldname": "material_request_date", 82 | "fieldtype": "Date", 83 | "hidden": 0, 84 | "ignore_user_permissions": 0, 85 | "ignore_xss_filter": 0, 86 | "in_filter": 0, 87 | "in_global_search": 0, 88 | "in_list_view": 0, 89 | "in_standard_filter": 0, 90 | "label": "Material Request Date", 91 | "length": 0, 92 | "no_copy": 0, 93 | "permlevel": 0, 94 | "precision": "", 95 | "print_hide": 0, 96 | "print_hide_if_no_value": 0, 97 | "read_only": 0, 98 | "remember_last_selected_value": 0, 99 | "report_hide": 0, 100 | "reqd": 0, 101 | "search_index": 0, 102 | "set_only_once": 0, 103 | "unique": 0 104 | } 105 | ], 106 | "has_web_view": 0, 107 | "hide_heading": 0, 108 | "hide_toolbar": 0, 109 | "idx": 0, 110 | "image_view": 0, 111 | "in_create": 0, 112 | "is_submittable": 0, 113 | "issingle": 0, 114 | "istable": 1, 115 | "max_attachments": 0, 116 | "modified": "2017-10-13 18:26:31.787434", 117 | "modified_by": "Administrator", 118 | "module": "Simplified Production Process", 119 | "name": "Simplified Production Material Request", 120 | "name_case": "", 121 | "owner": "Administrator", 122 | "permissions": [], 123 | "quick_entry": 1, 124 | "read_only": 0, 125 | "read_only_onload": 0, 126 | "show_name_in_global_search": 0, 127 | "sort_field": "modified", 128 | "sort_order": "DESC", 129 | "track_changes": 1, 130 | "track_seen": 0 131 | } -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_material_request/simplified_production_material_request.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017, o10c and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.model.document import Document 8 | 9 | class SimplifiedProductionMaterialRequest(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_sales_orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/doctype/simplified_production_sales_orders/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_sales_orders/simplified_production_sales_orders.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_guest_to_view": 0, 4 | "allow_import": 0, 5 | "allow_rename": 0, 6 | "autoname": "hash", 7 | "beta": 0, 8 | "creation": "2017-08-24 11:50:58.461102", 9 | "custom": 0, 10 | "docstatus": 0, 11 | "doctype": "DocType", 12 | "document_type": "", 13 | "editable_grid": 1, 14 | "engine": "InnoDB", 15 | "fields": [ 16 | { 17 | "allow_bulk_edit": 0, 18 | "allow_on_submit": 0, 19 | "bold": 0, 20 | "collapsible": 0, 21 | "columns": 0, 22 | "fieldname": "sales_order", 23 | "fieldtype": "Link", 24 | "hidden": 0, 25 | "ignore_user_permissions": 0, 26 | "ignore_xss_filter": 0, 27 | "in_filter": 0, 28 | "in_global_search": 0, 29 | "in_list_view": 1, 30 | "in_standard_filter": 0, 31 | "label": "Sales Order", 32 | "length": 0, 33 | "no_copy": 0, 34 | "options": "Sales Order", 35 | "permlevel": 0, 36 | "precision": "", 37 | "print_hide": 0, 38 | "print_hide_if_no_value": 0, 39 | "read_only": 0, 40 | "remember_last_selected_value": 0, 41 | "report_hide": 0, 42 | "reqd": 0, 43 | "search_index": 0, 44 | "set_only_once": 0, 45 | "unique": 0 46 | }, 47 | { 48 | "allow_bulk_edit": 0, 49 | "allow_on_submit": 0, 50 | "bold": 0, 51 | "collapsible": 0, 52 | "columns": 0, 53 | "fieldname": "sales_order_date", 54 | "fieldtype": "Date", 55 | "hidden": 0, 56 | "ignore_user_permissions": 0, 57 | "ignore_xss_filter": 0, 58 | "in_filter": 0, 59 | "in_global_search": 0, 60 | "in_list_view": 1, 61 | "in_standard_filter": 0, 62 | "label": "Sales Order Date", 63 | "length": 0, 64 | "no_copy": 0, 65 | "permlevel": 0, 66 | "precision": "", 67 | "print_hide": 0, 68 | "print_hide_if_no_value": 0, 69 | "read_only": 0, 70 | "remember_last_selected_value": 0, 71 | "report_hide": 0, 72 | "reqd": 0, 73 | "search_index": 0, 74 | "set_only_once": 0, 75 | "unique": 0 76 | }, 77 | { 78 | "allow_bulk_edit": 0, 79 | "allow_on_submit": 0, 80 | "bold": 0, 81 | "collapsible": 0, 82 | "columns": 0, 83 | "fieldname": "column_break_3", 84 | "fieldtype": "Column Break", 85 | "hidden": 0, 86 | "ignore_user_permissions": 0, 87 | "ignore_xss_filter": 0, 88 | "in_filter": 0, 89 | "in_global_search": 0, 90 | "in_list_view": 0, 91 | "in_standard_filter": 0, 92 | "length": 0, 93 | "no_copy": 0, 94 | "options": "", 95 | "permlevel": 0, 96 | "precision": "", 97 | "print_hide": 0, 98 | "print_hide_if_no_value": 0, 99 | "read_only": 0, 100 | "remember_last_selected_value": 0, 101 | "report_hide": 0, 102 | "reqd": 0, 103 | "search_index": 0, 104 | "set_only_once": 0, 105 | "unique": 0 106 | }, 107 | { 108 | "allow_bulk_edit": 0, 109 | "allow_on_submit": 0, 110 | "bold": 0, 111 | "collapsible": 0, 112 | "columns": 0, 113 | "fieldname": "customer", 114 | "fieldtype": "Link", 115 | "hidden": 0, 116 | "ignore_user_permissions": 0, 117 | "ignore_xss_filter": 0, 118 | "in_filter": 0, 119 | "in_global_search": 0, 120 | "in_list_view": 1, 121 | "in_standard_filter": 0, 122 | "label": "Customer", 123 | "length": 0, 124 | "no_copy": 0, 125 | "options": "Customer", 126 | "permlevel": 0, 127 | "precision": "", 128 | "print_hide": 0, 129 | "print_hide_if_no_value": 0, 130 | "read_only": 0, 131 | "remember_last_selected_value": 0, 132 | "report_hide": 0, 133 | "reqd": 0, 134 | "search_index": 0, 135 | "set_only_once": 0, 136 | "unique": 0 137 | }, 138 | { 139 | "allow_bulk_edit": 0, 140 | "allow_on_submit": 0, 141 | "bold": 0, 142 | "collapsible": 0, 143 | "columns": 0, 144 | "fieldname": "grand_total", 145 | "fieldtype": "Currency", 146 | "hidden": 0, 147 | "ignore_user_permissions": 0, 148 | "ignore_xss_filter": 0, 149 | "in_filter": 0, 150 | "in_global_search": 0, 151 | "in_list_view": 1, 152 | "in_standard_filter": 0, 153 | "label": "Grand Total", 154 | "length": 0, 155 | "no_copy": 0, 156 | "permlevel": 0, 157 | "precision": "", 158 | "print_hide": 0, 159 | "print_hide_if_no_value": 0, 160 | "read_only": 0, 161 | "remember_last_selected_value": 0, 162 | "report_hide": 0, 163 | "reqd": 0, 164 | "search_index": 0, 165 | "set_only_once": 0, 166 | "unique": 0 167 | } 168 | ], 169 | "has_web_view": 0, 170 | "hide_heading": 0, 171 | "hide_toolbar": 0, 172 | "idx": 0, 173 | "image_view": 0, 174 | "in_create": 0, 175 | "is_submittable": 0, 176 | "issingle": 0, 177 | "istable": 1, 178 | "max_attachments": 0, 179 | "modified": "2017-10-13 18:26:37.370338", 180 | "modified_by": "Administrator", 181 | "module": "Simplified Production Process", 182 | "name": "Simplified Production Sales Orders", 183 | "name_case": "", 184 | "owner": "Administrator", 185 | "permissions": [], 186 | "quick_entry": 1, 187 | "read_only": 0, 188 | "read_only_onload": 0, 189 | "show_name_in_global_search": 0, 190 | "sort_field": "modified", 191 | "sort_order": "DESC", 192 | "track_changes": 1, 193 | "track_seen": 0 194 | } -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_sales_orders/simplified_production_sales_orders.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017, o10c and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.model.document import Document 8 | 9 | class SimplifiedProductionSalesOrders(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/simplified_production_process/doctype/simplified_production_tool/__init__.py -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/simplified_production_tool.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | cur_frm.cscript.onload = function(doc) { 4 | cur_frm.set_value("company", frappe.defaults.get_user_default("Company")) 5 | } 6 | */ 7 | 8 | 9 | cur_frm.cscript.refresh = function(doc) { 10 | cur_frm.disable_save(); 11 | } 12 | 13 | 14 | cur_frm.add_fetch("material_request", "transaction_date", "material_request_date"); 15 | 16 | cur_frm.add_fetch("sales_order", "transaction_date", "sales_order_date"); 17 | cur_frm.add_fetch("sales_order", "customer", "customer"); 18 | cur_frm.add_fetch("sales_order", "base_grand_total", "grand_total"); 19 | 20 | frappe.ui.form.on("Simplified Production Tool", { 21 | 22 | onload_post_render: function(frm) { 23 | frm.get_field("items").grid.set_multiple_add("item_code", "planned_qty"); 24 | }, 25 | 26 | get_sales_orders: function(frm) { 27 | frappe.call({ 28 | doc: frm.doc, 29 | method: "get_open_sales_orders", 30 | callback: function(r) { 31 | refresh_field("sales_orders"); 32 | } 33 | }); 34 | }, 35 | 36 | get_material_request: function(frm) { 37 | frappe.call({ 38 | doc: frm.doc, 39 | method: "get_pending_material_requests", 40 | callback: function(r) { 41 | refresh_field("material_requests"); 42 | } 43 | }); 44 | }, 45 | 46 | get_items: function(frm) { 47 | frappe.call({ 48 | doc: frm.doc, 49 | method: "get_items", 50 | callback: function(r) { 51 | refresh_field("items"); 52 | } 53 | }); 54 | }, 55 | 56 | manufacture_items: function(frm) { 57 | frappe.call({ 58 | doc: frm.doc, 59 | method: "raise_stock_entries" 60 | }); 61 | }, 62 | 63 | create_material_requests: function(frm) { 64 | frappe.call({ 65 | doc: frm.doc, 66 | method: "raise_material_requests" 67 | }); 68 | } 69 | 70 | 71 | }); 72 | 73 | 74 | 75 | cur_frm.cscript.item_code = function(doc,cdt,cdn) { 76 | var d = locals[cdt][cdn]; 77 | if (d.item_code) { 78 | frappe.call({ 79 | method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details", 80 | args: { 81 | "item" : d.item_code 82 | }, 83 | callback: function(r) { 84 | $.extend(d, r.message); 85 | refresh_field("items"); 86 | } 87 | }); 88 | } 89 | } 90 | 91 | cur_frm.cscript.download_materials_required = function(doc, cdt, cdn) { 92 | return $c_obj(doc, 'validate_data', '', function(r, rt) { 93 | if (!r['exc']) 94 | $c_obj_csv(doc, 'download_raw_materials', '', ''); 95 | }); 96 | } 97 | 98 | 99 | 100 | cur_frm.fields_dict['sales_orders'].grid.get_field('sales_order').get_query = function(doc) { 101 | var args = { "docstatus": 1 }; 102 | if(doc.customer) { 103 | args["customer"] = doc.customer; 104 | } 105 | 106 | return { filters: args } 107 | } 108 | 109 | 110 | 111 | cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) { 112 | return erpnext.queries.item({ 113 | 'is_stock_item': 1 114 | }); 115 | } 116 | 117 | cur_frm.fields_dict['items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) { 118 | var d = locals[cdt][cdn]; 119 | if (d.item_code) { 120 | return { 121 | query: "erpnext.controllers.queries.bom", 122 | filters:{'item': cstr(d.item_code)} 123 | } 124 | } else frappe.msgprint(__("Please enter Item first")); 125 | } 126 | 127 | 128 | 129 | 130 | cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) { 131 | return{ 132 | query: "erpnext.controllers.queries.customer_query" 133 | } 134 | } 135 | 136 | cur_frm.fields_dict.sales_orders.grid.get_field("customer").get_query = 137 | cur_frm.fields_dict.customer.get_query; 138 | 139 | cur_frm.cscript.planned_start_date = function(doc, cdt, cdn) { 140 | erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "planned_start_date"); 141 | } 142 | 143 | 144 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/simplified_production_tool.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 1, 3 | "allow_guest_to_view": 0, 4 | "allow_import": 0, 5 | "allow_rename": 0, 6 | "beta": 0, 7 | "creation": "2017-08-24 11:32:58.610832", 8 | "custom": 0, 9 | "description": "Separate stock entries will be created for each finished good item.", 10 | "docstatus": 0, 11 | "doctype": "DocType", 12 | "document_type": "Setup", 13 | "editable_grid": 1, 14 | "engine": "InnoDB", 15 | "fields": [ 16 | { 17 | "allow_bulk_edit": 0, 18 | "allow_on_submit": 0, 19 | "bold": 0, 20 | "collapsible": 0, 21 | "columns": 0, 22 | "fieldname": "get_items_from", 23 | "fieldtype": "Select", 24 | "hidden": 0, 25 | "ignore_user_permissions": 0, 26 | "ignore_xss_filter": 0, 27 | "in_filter": 0, 28 | "in_global_search": 0, 29 | "in_list_view": 0, 30 | "in_standard_filter": 0, 31 | "label": "Get Items From", 32 | "length": 0, 33 | "no_copy": 0, 34 | "options": "\nSales Order\nMaterial Request", 35 | "permlevel": 0, 36 | "precision": "", 37 | "print_hide": 0, 38 | "print_hide_if_no_value": 0, 39 | "read_only": 0, 40 | "remember_last_selected_value": 0, 41 | "report_hide": 0, 42 | "reqd": 0, 43 | "search_index": 0, 44 | "set_only_once": 0, 45 | "unique": 0 46 | }, 47 | { 48 | "allow_bulk_edit": 0, 49 | "allow_on_submit": 0, 50 | "bold": 0, 51 | "collapsible": 0, 52 | "columns": 0, 53 | "depends_on": "get_items_from", 54 | "fieldname": "filters", 55 | "fieldtype": "Section Break", 56 | "hidden": 0, 57 | "ignore_user_permissions": 0, 58 | "ignore_xss_filter": 0, 59 | "in_filter": 0, 60 | "in_global_search": 0, 61 | "in_list_view": 0, 62 | "in_standard_filter": 0, 63 | "label": "Filters", 64 | "length": 0, 65 | "no_copy": 0, 66 | "permlevel": 0, 67 | "precision": "", 68 | "print_hide": 0, 69 | "print_hide_if_no_value": 0, 70 | "read_only": 0, 71 | "remember_last_selected_value": 0, 72 | "report_hide": 0, 73 | "reqd": 0, 74 | "search_index": 0, 75 | "set_only_once": 0, 76 | "unique": 0 77 | }, 78 | { 79 | "allow_bulk_edit": 0, 80 | "allow_on_submit": 0, 81 | "bold": 0, 82 | "collapsible": 0, 83 | "columns": 0, 84 | "fieldname": "fg_item", 85 | "fieldtype": "Link", 86 | "hidden": 0, 87 | "ignore_user_permissions": 0, 88 | "ignore_xss_filter": 0, 89 | "in_filter": 0, 90 | "in_global_search": 0, 91 | "in_list_view": 1, 92 | "in_standard_filter": 0, 93 | "label": "Item", 94 | "length": 0, 95 | "no_copy": 0, 96 | "options": "Item", 97 | "permlevel": 0, 98 | "precision": "", 99 | "print_hide": 0, 100 | "print_hide_if_no_value": 0, 101 | "read_only": 0, 102 | "remember_last_selected_value": 0, 103 | "report_hide": 0, 104 | "reqd": 0, 105 | "search_index": 0, 106 | "set_only_once": 0, 107 | "unique": 0 108 | }, 109 | { 110 | "allow_bulk_edit": 0, 111 | "allow_on_submit": 0, 112 | "bold": 0, 113 | "collapsible": 0, 114 | "columns": 0, 115 | "fieldname": "customer", 116 | "fieldtype": "Link", 117 | "hidden": 0, 118 | "ignore_user_permissions": 0, 119 | "ignore_xss_filter": 0, 120 | "in_filter": 0, 121 | "in_global_search": 0, 122 | "in_list_view": 1, 123 | "in_standard_filter": 0, 124 | "label": "Customer", 125 | "length": 0, 126 | "no_copy": 0, 127 | "options": "Customer", 128 | "permlevel": 0, 129 | "precision": "", 130 | "print_hide": 0, 131 | "print_hide_if_no_value": 0, 132 | "read_only": 0, 133 | "remember_last_selected_value": 0, 134 | "report_hide": 0, 135 | "reqd": 0, 136 | "search_index": 0, 137 | "set_only_once": 0, 138 | "unique": 0 139 | }, 140 | { 141 | "allow_bulk_edit": 0, 142 | "allow_on_submit": 0, 143 | "bold": 0, 144 | "collapsible": 0, 145 | "columns": 0, 146 | "fieldname": "company", 147 | "fieldtype": "Link", 148 | "hidden": 0, 149 | "ignore_user_permissions": 0, 150 | "ignore_xss_filter": 0, 151 | "in_filter": 0, 152 | "in_global_search": 0, 153 | "in_list_view": 0, 154 | "in_standard_filter": 0, 155 | "label": "Company", 156 | "length": 0, 157 | "no_copy": 0, 158 | "options": "Company", 159 | "permlevel": 0, 160 | "precision": "", 161 | "print_hide": 0, 162 | "print_hide_if_no_value": 0, 163 | "read_only": 0, 164 | "remember_last_selected_value": 0, 165 | "report_hide": 0, 166 | "reqd": 0, 167 | "search_index": 0, 168 | "set_only_once": 0, 169 | "unique": 0 170 | }, 171 | { 172 | "allow_bulk_edit": 0, 173 | "allow_on_submit": 0, 174 | "bold": 0, 175 | "collapsible": 0, 176 | "columns": 0, 177 | "fieldname": "column_break_5", 178 | "fieldtype": "Column Break", 179 | "hidden": 0, 180 | "ignore_user_permissions": 0, 181 | "ignore_xss_filter": 0, 182 | "in_filter": 0, 183 | "in_global_search": 0, 184 | "in_list_view": 0, 185 | "in_standard_filter": 0, 186 | "length": 0, 187 | "no_copy": 0, 188 | "permlevel": 0, 189 | "precision": "", 190 | "print_hide": 0, 191 | "print_hide_if_no_value": 0, 192 | "read_only": 0, 193 | "remember_last_selected_value": 0, 194 | "report_hide": 0, 195 | "reqd": 0, 196 | "search_index": 0, 197 | "set_only_once": 0, 198 | "unique": 0 199 | }, 200 | { 201 | "allow_bulk_edit": 0, 202 | "allow_on_submit": 0, 203 | "bold": 0, 204 | "collapsible": 0, 205 | "columns": 0, 206 | "fieldname": "from_date", 207 | "fieldtype": "Date", 208 | "hidden": 0, 209 | "ignore_user_permissions": 0, 210 | "ignore_xss_filter": 0, 211 | "in_filter": 0, 212 | "in_global_search": 0, 213 | "in_list_view": 0, 214 | "in_standard_filter": 0, 215 | "label": "From date", 216 | "length": 0, 217 | "no_copy": 0, 218 | "permlevel": 0, 219 | "precision": "", 220 | "print_hide": 0, 221 | "print_hide_if_no_value": 0, 222 | "read_only": 0, 223 | "remember_last_selected_value": 0, 224 | "report_hide": 0, 225 | "reqd": 0, 226 | "search_index": 0, 227 | "set_only_once": 0, 228 | "unique": 0 229 | }, 230 | { 231 | "allow_bulk_edit": 0, 232 | "allow_on_submit": 0, 233 | "bold": 0, 234 | "collapsible": 0, 235 | "columns": 0, 236 | "fieldname": "to_date", 237 | "fieldtype": "Date", 238 | "hidden": 0, 239 | "ignore_user_permissions": 0, 240 | "ignore_xss_filter": 0, 241 | "in_filter": 0, 242 | "in_global_search": 0, 243 | "in_list_view": 0, 244 | "in_standard_filter": 0, 245 | "label": "To Date", 246 | "length": 0, 247 | "no_copy": 0, 248 | "permlevel": 0, 249 | "precision": "", 250 | "print_hide": 0, 251 | "print_hide_if_no_value": 0, 252 | "read_only": 0, 253 | "remember_last_selected_value": 0, 254 | "report_hide": 0, 255 | "reqd": 0, 256 | "search_index": 0, 257 | "set_only_once": 0, 258 | "unique": 0 259 | }, 260 | { 261 | "allow_bulk_edit": 0, 262 | "allow_on_submit": 0, 263 | "bold": 0, 264 | "collapsible": 0, 265 | "columns": 0, 266 | "fieldname": "project", 267 | "fieldtype": "Link", 268 | "hidden": 0, 269 | "ignore_user_permissions": 0, 270 | "ignore_xss_filter": 0, 271 | "in_filter": 0, 272 | "in_global_search": 0, 273 | "in_list_view": 0, 274 | "in_standard_filter": 0, 275 | "label": "Project", 276 | "length": 0, 277 | "no_copy": 0, 278 | "options": "Project", 279 | "permlevel": 0, 280 | "precision": "", 281 | "print_hide": 0, 282 | "print_hide_if_no_value": 0, 283 | "read_only": 0, 284 | "remember_last_selected_value": 0, 285 | "report_hide": 0, 286 | "reqd": 0, 287 | "search_index": 0, 288 | "set_only_once": 0, 289 | "unique": 0 290 | }, 291 | { 292 | "allow_bulk_edit": 0, 293 | "allow_on_submit": 0, 294 | "bold": 0, 295 | "collapsible": 0, 296 | "columns": 0, 297 | "depends_on": "eval: doc.get_items_from == \"Sales Order\"", 298 | "fieldname": "section_break_9", 299 | "fieldtype": "Section Break", 300 | "hidden": 0, 301 | "ignore_user_permissions": 0, 302 | "ignore_xss_filter": 0, 303 | "in_filter": 0, 304 | "in_global_search": 0, 305 | "in_list_view": 0, 306 | "in_standard_filter": 0, 307 | "label": "", 308 | "length": 0, 309 | "no_copy": 0, 310 | "permlevel": 0, 311 | "precision": "", 312 | "print_hide": 0, 313 | "print_hide_if_no_value": 0, 314 | "read_only": 0, 315 | "remember_last_selected_value": 0, 316 | "report_hide": 0, 317 | "reqd": 0, 318 | "search_index": 0, 319 | "set_only_once": 0, 320 | "unique": 0 321 | }, 322 | { 323 | "allow_bulk_edit": 0, 324 | "allow_on_submit": 0, 325 | "bold": 0, 326 | "collapsible": 0, 327 | "columns": 0, 328 | "description": "Pull sales orders (pending to deliver) based on the above criteria", 329 | "fieldname": "get_sales_orders", 330 | "fieldtype": "Button", 331 | "hidden": 0, 332 | "ignore_user_permissions": 0, 333 | "ignore_xss_filter": 0, 334 | "in_filter": 0, 335 | "in_global_search": 0, 336 | "in_list_view": 0, 337 | "in_standard_filter": 0, 338 | "label": "Get Sales Orders", 339 | "length": 0, 340 | "no_copy": 0, 341 | "permlevel": 0, 342 | "precision": "", 343 | "print_hide": 0, 344 | "print_hide_if_no_value": 0, 345 | "read_only": 0, 346 | "remember_last_selected_value": 0, 347 | "report_hide": 0, 348 | "reqd": 0, 349 | "search_index": 0, 350 | "set_only_once": 0, 351 | "unique": 0 352 | }, 353 | { 354 | "allow_bulk_edit": 0, 355 | "allow_on_submit": 0, 356 | "bold": 0, 357 | "collapsible": 0, 358 | "columns": 0, 359 | "fieldname": "sales_orders", 360 | "fieldtype": "Table", 361 | "hidden": 0, 362 | "ignore_user_permissions": 0, 363 | "ignore_xss_filter": 0, 364 | "in_filter": 0, 365 | "in_global_search": 0, 366 | "in_list_view": 0, 367 | "in_standard_filter": 0, 368 | "label": "Sales Orders", 369 | "length": 0, 370 | "no_copy": 0, 371 | "options": "Simplified Production Sales Orders", 372 | "permlevel": 0, 373 | "precision": "", 374 | "print_hide": 0, 375 | "print_hide_if_no_value": 0, 376 | "read_only": 0, 377 | "remember_last_selected_value": 0, 378 | "report_hide": 0, 379 | "reqd": 0, 380 | "search_index": 0, 381 | "set_only_once": 0, 382 | "unique": 0 383 | }, 384 | { 385 | "allow_bulk_edit": 0, 386 | "allow_on_submit": 0, 387 | "bold": 0, 388 | "collapsible": 0, 389 | "columns": 0, 390 | "depends_on": "eval: doc.get_items_from == \"Material Request\"", 391 | "fieldname": "section_break_13", 392 | "fieldtype": "Section Break", 393 | "hidden": 0, 394 | "ignore_user_permissions": 0, 395 | "ignore_xss_filter": 0, 396 | "in_filter": 0, 397 | "in_global_search": 0, 398 | "in_list_view": 0, 399 | "in_standard_filter": 0, 400 | "length": 0, 401 | "no_copy": 0, 402 | "permlevel": 0, 403 | "precision": "", 404 | "print_hide": 0, 405 | "print_hide_if_no_value": 0, 406 | "read_only": 0, 407 | "remember_last_selected_value": 0, 408 | "report_hide": 0, 409 | "reqd": 0, 410 | "search_index": 0, 411 | "set_only_once": 0, 412 | "unique": 0 413 | }, 414 | { 415 | "allow_bulk_edit": 0, 416 | "allow_on_submit": 0, 417 | "bold": 0, 418 | "collapsible": 0, 419 | "columns": 0, 420 | "fieldname": "get_material_request", 421 | "fieldtype": "Button", 422 | "hidden": 0, 423 | "ignore_user_permissions": 0, 424 | "ignore_xss_filter": 0, 425 | "in_filter": 0, 426 | "in_global_search": 0, 427 | "in_list_view": 0, 428 | "in_standard_filter": 0, 429 | "label": "Get Material Request", 430 | "length": 0, 431 | "no_copy": 0, 432 | "permlevel": 0, 433 | "precision": "", 434 | "print_hide": 0, 435 | "print_hide_if_no_value": 0, 436 | "read_only": 0, 437 | "remember_last_selected_value": 0, 438 | "report_hide": 0, 439 | "reqd": 0, 440 | "search_index": 0, 441 | "set_only_once": 0, 442 | "unique": 0 443 | }, 444 | { 445 | "allow_bulk_edit": 0, 446 | "allow_on_submit": 0, 447 | "bold": 0, 448 | "collapsible": 0, 449 | "columns": 0, 450 | "fieldname": "material_requests", 451 | "fieldtype": "Table", 452 | "hidden": 0, 453 | "ignore_user_permissions": 0, 454 | "ignore_xss_filter": 0, 455 | "in_filter": 0, 456 | "in_global_search": 0, 457 | "in_list_view": 0, 458 | "in_standard_filter": 0, 459 | "label": "Material Requests", 460 | "length": 0, 461 | "no_copy": 0, 462 | "options": "Simplified Production Material Request", 463 | "permlevel": 0, 464 | "precision": "", 465 | "print_hide": 0, 466 | "print_hide_if_no_value": 0, 467 | "read_only": 0, 468 | "remember_last_selected_value": 0, 469 | "report_hide": 0, 470 | "reqd": 0, 471 | "search_index": 0, 472 | "set_only_once": 0, 473 | "unique": 0 474 | }, 475 | { 476 | "allow_bulk_edit": 0, 477 | "allow_on_submit": 0, 478 | "bold": 0, 479 | "collapsible": 0, 480 | "columns": 0, 481 | "fieldname": "select_items", 482 | "fieldtype": "Section Break", 483 | "hidden": 0, 484 | "ignore_user_permissions": 0, 485 | "ignore_xss_filter": 0, 486 | "in_filter": 0, 487 | "in_global_search": 0, 488 | "in_list_view": 0, 489 | "in_standard_filter": 0, 490 | "label": "Select Items", 491 | "length": 0, 492 | "no_copy": 0, 493 | "permlevel": 0, 494 | "precision": "", 495 | "print_hide": 0, 496 | "print_hide_if_no_value": 0, 497 | "read_only": 0, 498 | "remember_last_selected_value": 0, 499 | "report_hide": 0, 500 | "reqd": 0, 501 | "search_index": 0, 502 | "set_only_once": 0, 503 | "unique": 0 504 | }, 505 | { 506 | "allow_bulk_edit": 0, 507 | "allow_on_submit": 0, 508 | "bold": 0, 509 | "collapsible": 0, 510 | "columns": 0, 511 | "fieldname": "get_items", 512 | "fieldtype": "Button", 513 | "hidden": 0, 514 | "ignore_user_permissions": 0, 515 | "ignore_xss_filter": 0, 516 | "in_filter": 0, 517 | "in_global_search": 0, 518 | "in_list_view": 0, 519 | "in_standard_filter": 0, 520 | "label": "Get Items", 521 | "length": 0, 522 | "no_copy": 0, 523 | "permlevel": 0, 524 | "precision": "", 525 | "print_hide": 0, 526 | "print_hide_if_no_value": 0, 527 | "read_only": 0, 528 | "remember_last_selected_value": 0, 529 | "report_hide": 0, 530 | "reqd": 0, 531 | "search_index": 0, 532 | "set_only_once": 0, 533 | "unique": 0 534 | }, 535 | { 536 | "allow_bulk_edit": 0, 537 | "allow_on_submit": 0, 538 | "bold": 0, 539 | "collapsible": 0, 540 | "columns": 0, 541 | "fieldname": "items", 542 | "fieldtype": "Table", 543 | "hidden": 0, 544 | "ignore_user_permissions": 0, 545 | "ignore_xss_filter": 0, 546 | "in_filter": 0, 547 | "in_global_search": 0, 548 | "in_list_view": 0, 549 | "in_standard_filter": 0, 550 | "label": "Items", 551 | "length": 0, 552 | "no_copy": 0, 553 | "options": "Simplified Production Items", 554 | "permlevel": 0, 555 | "precision": "", 556 | "print_hide": 0, 557 | "print_hide_if_no_value": 0, 558 | "read_only": 0, 559 | "remember_last_selected_value": 0, 560 | "report_hide": 0, 561 | "reqd": 0, 562 | "search_index": 0, 563 | "set_only_once": 0, 564 | "unique": 0 565 | }, 566 | { 567 | "allow_bulk_edit": 0, 568 | "allow_on_submit": 0, 569 | "bold": 0, 570 | "collapsible": 0, 571 | "columns": 0, 572 | "fieldname": "section_break_19", 573 | "fieldtype": "Section Break", 574 | "hidden": 0, 575 | "ignore_user_permissions": 0, 576 | "ignore_xss_filter": 0, 577 | "in_filter": 0, 578 | "in_global_search": 0, 579 | "in_list_view": 0, 580 | "in_standard_filter": 0, 581 | "length": 0, 582 | "no_copy": 0, 583 | "permlevel": 0, 584 | "precision": "", 585 | "print_hide": 0, 586 | "print_hide_if_no_value": 0, 587 | "read_only": 0, 588 | "remember_last_selected_value": 0, 589 | "report_hide": 0, 590 | "reqd": 0, 591 | "search_index": 0, 592 | "set_only_once": 0, 593 | "unique": 0 594 | }, 595 | { 596 | "allow_bulk_edit": 0, 597 | "allow_on_submit": 0, 598 | "bold": 0, 599 | "collapsible": 0, 600 | "columns": 0, 601 | "fieldname": "manufacture_items", 602 | "fieldtype": "Button", 603 | "hidden": 0, 604 | "ignore_user_permissions": 0, 605 | "ignore_xss_filter": 0, 606 | "in_filter": 0, 607 | "in_global_search": 0, 608 | "in_list_view": 0, 609 | "in_standard_filter": 0, 610 | "label": "Manufacture Items", 611 | "length": 0, 612 | "no_copy": 0, 613 | "permlevel": 0, 614 | "precision": "", 615 | "print_hide": 0, 616 | "print_hide_if_no_value": 0, 617 | "read_only": 0, 618 | "remember_last_selected_value": 0, 619 | "report_hide": 0, 620 | "reqd": 0, 621 | "search_index": 0, 622 | "set_only_once": 0, 623 | "unique": 0 624 | } 625 | ], 626 | "has_web_view": 0, 627 | "hide_heading": 0, 628 | "hide_toolbar": 1, 629 | "icon": "icon-calendar", 630 | "idx": 0, 631 | "image_view": 0, 632 | "in_create": 1, 633 | "is_submittable": 0, 634 | "issingle": 1, 635 | "istable": 0, 636 | "max_attachments": 0, 637 | "modified": "2017-10-13 18:26:14.122044", 638 | "modified_by": "Administrator", 639 | "module": "Simplified Production Process", 640 | "name": "Simplified Production Tool", 641 | "name_case": "", 642 | "owner": "Administrator", 643 | "permissions": [ 644 | { 645 | "amend": 0, 646 | "apply_user_permissions": 0, 647 | "cancel": 0, 648 | "create": 1, 649 | "delete": 1, 650 | "email": 1, 651 | "export": 0, 652 | "if_owner": 0, 653 | "import": 0, 654 | "permlevel": 0, 655 | "print": 1, 656 | "read": 1, 657 | "report": 0, 658 | "role": "System Manager", 659 | "set_user_permissions": 0, 660 | "share": 1, 661 | "submit": 0, 662 | "write": 1 663 | }, 664 | { 665 | "amend": 0, 666 | "apply_user_permissions": 0, 667 | "cancel": 0, 668 | "create": 1, 669 | "delete": 0, 670 | "email": 0, 671 | "export": 0, 672 | "if_owner": 0, 673 | "import": 0, 674 | "permlevel": 0, 675 | "print": 0, 676 | "read": 1, 677 | "report": 0, 678 | "role": "Manufacturing User", 679 | "set_user_permissions": 0, 680 | "share": 0, 681 | "submit": 0, 682 | "write": 1 683 | } 684 | ], 685 | "quick_entry": 0, 686 | "read_only": 1, 687 | "read_only_onload": 0, 688 | "show_name_in_global_search": 0, 689 | "sort_field": "", 690 | "sort_order": "DESC", 691 | "track_changes": 0, 692 | "track_seen": 0 693 | } -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/simplified_production_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017, o10c and contributors 3 | # For license information, please see license.txt 4 | 5 | 6 | from __future__ import unicode_literals 7 | import frappe 8 | from frappe.utils import cstr, flt, cint, nowdate, nowtime, add_days, comma_and 9 | 10 | from frappe import msgprint, _ 11 | 12 | from frappe.model.document import Document 13 | from erpnext.manufacturing.doctype.bom.bom import validate_bom_no 14 | from erpnext.manufacturing.doctype.production_order.production_order import get_item_details 15 | from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError 16 | 17 | class SimplifiedProductionTool(Document): 18 | 19 | def clear_table(self, table_name): 20 | self.set(table_name, []) 21 | 22 | def validate_company(self): 23 | if not self.company: 24 | frappe.throw(_("Please enter Company")) 25 | 26 | def get_open_sales_orders(self): 27 | """ Pull sales orders which are pending to deliver based on criteria selected""" 28 | so_filter = item_filter = "" 29 | if self.from_date: 30 | so_filter += " and so.transaction_date >= %(from_date)s" 31 | if self.to_date: 32 | so_filter += " and so.transaction_date <= %(to_date)s" 33 | if self.customer: 34 | so_filter += " and so.customer = %(customer)s" 35 | if self.project: 36 | so_filter += " and so.project = %(project)s" 37 | 38 | if self.fg_item: 39 | item_filter += " and so_item.item_code = %(item)s" 40 | 41 | open_so = frappe.db.sql(""" 42 | select distinct so.name, so.transaction_date, so.customer, so.base_grand_total 43 | from `tabSales Order` so, `tabSales Order Item` so_item 44 | where so_item.parent = so.name 45 | and so.docstatus = 1 and so.status not in ("Stopped", "Closed") 46 | and so.company = %(company)s 47 | and so_item.qty > so_item.delivered_qty {0} {1} 48 | and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code 49 | and bom.is_active = 1) 50 | or exists (select name from `tabPacked Item` pi 51 | where pi.parent = so.name and pi.parent_item = so_item.item_code 52 | and exists (select name from `tabBOM` bom where bom.item=pi.item_code 53 | and bom.is_active = 1))) 54 | """.format(so_filter, item_filter), { 55 | "from_date": self.from_date, 56 | "to_date": self.to_date, 57 | "customer": self.customer, 58 | "project": self.project, 59 | "item": self.fg_item, 60 | "company": self.company 61 | }, as_dict=1) 62 | 63 | self.add_so_in_table(open_so) 64 | 65 | def add_so_in_table(self, open_so): 66 | """ Add sales orders in the table""" 67 | #self.clear_table("sales_orders") 68 | #so_list = [] 69 | so_list = [d.sales_order for d in self.get('sales_orders') if d.sales_order] 70 | for r in open_so: 71 | if cstr(r['name']) not in so_list: 72 | pp_so = self.append('sales_orders', {}) 73 | pp_so.sales_order = r['name'] 74 | pp_so.sales_order_date = cstr(r['transaction_date']) 75 | pp_so.customer = cstr(r['customer']) 76 | pp_so.grand_total = flt(r['base_grand_total']) 77 | 78 | 79 | def get_pending_material_requests(self): 80 | """ Pull Material Requests that are pending based on criteria selected""" 81 | mr_filter = item_filter = "" 82 | if self.from_date: 83 | mr_filter += " and mr.transaction_date >= %(from_date)s" 84 | if self.to_date: 85 | mr_filter += " and mr.transaction_date <= %(to_date)s" 86 | if self.warehouse: 87 | mr_filter += " and mr_item.warehouse = %(warehouse)s" 88 | 89 | if self.fg_item: 90 | item_filter += " and mr_item.item_code = %(item)s" 91 | 92 | pending_mr = frappe.db.sql(""" 93 | select distinct mr.name, mr.transaction_date 94 | from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item 95 | where mr_item.parent = mr.name 96 | and mr.material_request_type = "Manufacture" 97 | and mr.docstatus = 1 98 | and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1} 99 | and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code 100 | and bom.is_active = 1)) 101 | """.format(mr_filter, item_filter), { 102 | "from_date": self.from_date, 103 | "to_date": self.to_date, 104 | "warehouse": self.warehouse, 105 | "item": self.fg_item 106 | }, as_dict=1) 107 | 108 | self.add_mr_in_table(pending_mr) 109 | 110 | def add_mr_in_table(self, pending_mr): 111 | """ Add Material Requests in the table""" 112 | self.clear_table("material_requests") 113 | 114 | mr_list = [] 115 | for r in pending_mr: 116 | if cstr(r['name']) not in mr_list: 117 | mr = self.append('material_requests', {}) 118 | mr.material_request = r['name'] 119 | mr.material_request_date = cstr(r['transaction_date']) 120 | 121 | def get_items(self): 122 | if self.get_items_from == "Sales Order": 123 | self.get_so_items() 124 | elif self.get_items_from == "Material Request": 125 | self.get_mr_items() 126 | 127 | def get_so_items(self): 128 | so_list = [d.sales_order for d in self.get('sales_orders') if d.sales_order] 129 | if not so_list: 130 | msgprint(_("Please enter Sales Orders in the above table")) 131 | return [] 132 | 133 | item_condition = "" 134 | if self.fg_item: 135 | item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) 136 | 137 | items = frappe.db.sql("""select distinct parent, item_code, warehouse, 138 | (qty - delivered_qty)*conversion_factor as pending_qty 139 | from `tabSales Order Item` so_item 140 | where parent in (%s) and docstatus = 1 and qty > delivered_qty 141 | and exists (select name from `tabBOM` bom where bom.item=so_item.item_code 142 | and bom.is_active = 1) %s""" % \ 143 | (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) 144 | 145 | if self.fg_item: 146 | item_condition = ' and pi.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) 147 | 148 | packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, 149 | (((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty) 150 | as pending_qty 151 | from `tabSales Order Item` so_item, `tabPacked Item` pi 152 | where so_item.parent = pi.parent and so_item.docstatus = 1 153 | and pi.parent_item = so_item.item_code 154 | and so_item.parent in (%s) and so_item.qty > so_item.delivered_qty 155 | and exists (select name from `tabBOM` bom where bom.item=pi.item_code 156 | and bom.is_active = 1) %s""" % \ 157 | (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) 158 | 159 | self.add_items(items + packed_items) 160 | 161 | def get_mr_items(self): 162 | mr_list = [d.material_request for d in self.get('material_requests') if d.material_request] 163 | if not mr_list: 164 | msgprint(_("Please enter Material Requests in the above table")) 165 | return [] 166 | 167 | item_condition = "" 168 | if self.fg_item: 169 | item_condition = ' and mr_item.item_code = "' + frappe.db.escape(self.fg_item, percent=False) + '"' 170 | 171 | items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, 172 | (qty - ordered_qty) as pending_qty 173 | from `tabMaterial Request Item` mr_item 174 | where parent in (%s) and docstatus = 1 and qty > ordered_qty 175 | and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code 176 | and bom.is_active = 1) %s""" % \ 177 | (", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1) 178 | 179 | self.add_items(items) 180 | 181 | 182 | def add_items(self, items): 183 | self.clear_table("items") 184 | for p in items: 185 | item_details = get_item_details(p['item_code']) 186 | pi = self.append('items', {}) 187 | pi.warehouse = p['warehouse'] 188 | pi.item_code = p['item_code'] 189 | pi.description = item_details and item_details.description or '' 190 | pi.stock_uom = item_details and item_details.stock_uom or '' 191 | pi.bom_no = item_details and item_details.bom_no or '' 192 | pi.planned_qty = flt(p['pending_qty']) 193 | pi.pending_qty = flt(p['pending_qty']) 194 | 195 | if self.get_items_from == "Sales Order": 196 | pi.sales_order = p['parent'] 197 | elif self.get_items_from == "Material Request": 198 | pi.material_request = p['parent'] 199 | pi.material_request_item = p['name'] 200 | 201 | def validate_data(self): 202 | self.validate_company() 203 | for d in self.get('items'): 204 | if not d.bom_no: 205 | frappe.throw(_("Please select BOM for Item in Row {0}".format(d.idx))) 206 | else: 207 | validate_bom_no(d.item_code, d.bom_no) 208 | 209 | if not flt(d.planned_qty): 210 | frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) 211 | 212 | 213 | 214 | def raise_stock_entries(self): 215 | """It will raise a stock entries of purpose manufacture for all distinct FG items""" 216 | 217 | self.validate_data() 218 | 219 | from erpnext.utilities.transaction_base import validate_uom_is_integer 220 | validate_uom_is_integer(self, "stock_uom", "planned_qty") 221 | 222 | #frappe.flags.mute_messages = False 223 | 224 | 225 | items = self.get_manufacture_items() 226 | 227 | allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) 228 | 229 | se_list = [] 230 | 231 | for key in items: 232 | i = items[key] 233 | se = self.make_stock_entry(items[key]) 234 | if se: 235 | 236 | #check quantities in stock for each BOM items before insert and submit 237 | for d in se.items: 238 | previous_sle = get_previous_sle({ 239 | "item_code": d.item_code, 240 | "warehouse": d.s_warehouse or d.t_warehouse, 241 | "posting_date": nowdate(), 242 | "posting_time": nowtime() 243 | }) 244 | 245 | # get actual stock at source warehouse 246 | d.actual_qty = previous_sle.get("qty_after_transaction") or 0 247 | 248 | # validate qty before submit 249 | if d.s_warehouse and not allow_negative_stock and d.actual_qty < d.transfer_qty: 250 | frappe.throw(_("For manufacturing item {0} you need {3} x {2} in warehouse {1}").format(i['manufacture_item'], 251 | frappe.bold(d.s_warehouse), frappe.bold(d.item_code), frappe.bold(d.transfer_qty)) 252 | + '
' + _("Available qty is {0}, you need {1}").format(frappe.bold(d.actual_qty),frappe.bold(d.transfer_qty)), 253 | NegativeStockError, title=_('Insufficient Stock')) 254 | 255 | se_list.append(se) 256 | 257 | if se_list: 258 | for se in se_list: 259 | se.insert() 260 | se.submit() 261 | se_list = ["""%s""" % (p.name, p.title) for p in se_list] 262 | msgprint(_("Created {0} stock entries:
{1}").format(len(se_list), "
".join(se_list))) 263 | else : 264 | msgprint(_("No Stock Entry created")) 265 | 266 | def get_manufacture_items(self): 267 | item_dict = {} 268 | for d in self.get("items"): 269 | item_details= { 270 | "manufacture_item" : d.item_code, 271 | "sales_order" : d.sales_order, 272 | "material_request" : d.material_request, 273 | "material_request_item" : d.material_request_item, 274 | "bom_no" : d.bom_no, 275 | "description" : d.description, 276 | "stock_uom" : d.stock_uom, 277 | "company" : self.company, 278 | "from_warehouse" : "", 279 | "to_warehouse" : d.warehouse, 280 | "project" : frappe.db.get_value("Sales Order", d.sales_order, "project") 281 | } 282 | 283 | """ Club similar BOM and item for processing in case of Sales Orders """ 284 | if self.get_items_from == "Material Request": 285 | item_details.update({ 286 | "fg_completed_qty": d.planned_qty 287 | }) 288 | item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details 289 | 290 | else: 291 | item_details.update({ 292 | "fg_completed_qty":flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{}) 293 | .get("fg_completed_qty")) + flt(d.planned_qty) 294 | }) 295 | item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details 296 | 297 | return item_dict 298 | 299 | def make_stock_entry(self, item_dict): 300 | """Create a stock entry with purpose of manufacturing.""" 301 | from erpnext.manufacturing.doctype.production_order.production_order import get_default_warehouse 302 | 303 | warehouse = get_default_warehouse() 304 | se = frappe.new_doc("Stock Entry") 305 | se.update(item_dict) 306 | 307 | se.purpose = "Manufacture" 308 | se.status = "submitted" #submitted or "Draft" 309 | se.from_bom = 1 310 | se.use_multi_level_bom = 1 311 | se.title = "Manufacture %s x %s" % (se.fg_completed_qty,se.manufacture_item) 312 | if (se.sales_order): 313 | se.title += " for order %s" % (se.sales_order) 314 | if warehouse: 315 | se.from_warehouse = warehouse.get('wip_warehouse') 316 | if not se.to_warehouse: 317 | se.to_warehouse = warehouse.get('fg_warehouse') 318 | 319 | se.get_items() 320 | 321 | return se 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | def get_so_wise_planned_qty(self): 330 | """ 331 | bom_dict { 332 | bom_no: ['sales_order', 'qty'] 333 | } 334 | """ 335 | bom_dict = {} 336 | for d in self.get("items"): 337 | if self.get_items_from == "Material Request": 338 | bom_dict.setdefault(d.bom_no, []).append([d.material_request_item, flt(d.planned_qty)]) 339 | else: 340 | bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)]) 341 | return bom_dict 342 | 343 | def download_raw_materials(self): 344 | """ Create csv data for required raw material to produce finished goods""" 345 | self.validate_data() 346 | bom_dict = self.get_so_wise_planned_qty() 347 | self.get_raw_materials(bom_dict) 348 | return self.get_csv() 349 | 350 | def get_raw_materials(self, bom_dict,non_stock_item=0): 351 | """ Get raw materials considering sub-assembly items 352 | { 353 | "item_code": [qty_required, description, stock_uom, min_order_qty] 354 | } 355 | """ 356 | item_list = [] 357 | 358 | for bom, so_wise_qty in bom_dict.items(): 359 | bom_wise_item_details = {} 360 | if self.use_multi_level_bom and self.only_raw_materials and self.include_subcontracted: 361 | # get all raw materials with sub assembly childs 362 | # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss 363 | for d in frappe.db.sql("""select fb.item_code, 364 | ifnull(sum(fb.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, 365 | fb.description, fb.stock_uom, item.min_order_qty 366 | from `tabBOM Explosion Item` fb, `tabBOM` bom, `tabItem` item 367 | where bom.name = fb.parent and item.name = fb.item_code 368 | and (item.is_sub_contracted_item = 0 or ifnull(item.default_bom, "")="") 369 | """ + ("and item.is_stock_item = 1","")[non_stock_item] + """ 370 | and fb.docstatus<2 and bom.name=%(bom)s 371 | group by fb.item_code, fb.stock_uom""", {"bom":bom}, as_dict=1): 372 | bom_wise_item_details.setdefault(d.item_code, d) 373 | else: 374 | # Get all raw materials considering SA items as raw materials, 375 | # so no childs of SA items 376 | bom_wise_item_details = self.get_subitems(bom_wise_item_details, bom,1, \ 377 | self.use_multi_level_bom,self.only_raw_materials, self.include_subcontracted,non_stock_item) 378 | 379 | for item, item_details in bom_wise_item_details.items(): 380 | for so_qty in so_wise_qty: 381 | item_list.append([item, flt(item_details.qty) * so_qty[1], item_details.description, 382 | item_details.stock_uom, item_details.min_order_qty, so_qty[0]]) 383 | 384 | self.make_items_dict(item_list) 385 | 386 | def get_subitems(self,bom_wise_item_details, bom, parent_qty, include_sublevel, only_raw, supply_subs,non_stock_item=0): 387 | items = frappe.db.sql(""" 388 | SELECT 389 | bom_item.item_code, 390 | default_material_request_type, 391 | ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, 392 | item.is_sub_contracted_item as is_sub_contracted, 393 | item.default_bom as default_bom, 394 | bom_item.description as description, 395 | bom_item.stock_uom as stock_uom, 396 | item.min_order_qty as min_order_qty 397 | FROM 398 | `tabBOM Item` bom_item, 399 | `tabBOM` bom, 400 | tabItem item 401 | where 402 | bom.name = bom_item.parent 403 | and bom.name = %(bom)s 404 | and bom_item.docstatus < 2 405 | and bom_item.item_code = item.name 406 | """ + ("and item.is_stock_item = 1", "")[non_stock_item] + """ 407 | group by bom_item.item_code""", {"bom": bom, "parent_qty": parent_qty}, as_dict=1) 408 | 409 | for d in items: 410 | if ((d.default_material_request_type == "Purchase" 411 | and not (d.is_sub_contracted and only_raw and include_sublevel)) 412 | or (d.default_material_request_type == "Manufacture" and not only_raw)): 413 | 414 | if d.item_code in bom_wise_item_details: 415 | bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty 416 | else: 417 | bom_wise_item_details[d.item_code] = d 418 | 419 | if include_sublevel: 420 | if ((d.default_material_request_type == "Purchase" and d.is_sub_contracted and supply_subs) 421 | or (d.default_material_request_type == "Manufacture")): 422 | 423 | my_qty = 0 424 | projected_qty = self.get_item_projected_qty(d.item_code) 425 | 426 | if self.create_material_requests_for_all_required_qty: 427 | my_qty = d.qty 428 | elif (bom_wise_item_details[d.item_code].qty - d.qty) < projected_qty: 429 | my_qty = bom_wise_item_details[d.item_code].qty - projected_qty 430 | else: 431 | my_qty = d.qty 432 | 433 | if my_qty > 0: 434 | self.get_subitems(bom_wise_item_details, 435 | d.default_bom, my_qty, include_sublevel, only_raw, supply_subs) 436 | 437 | return bom_wise_item_details 438 | 439 | def make_items_dict(self, item_list): 440 | for i in item_list: 441 | self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]]) 442 | 443 | def get_csv(self): 444 | item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', 445 | 'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']] 446 | for item in self.item_dict: 447 | total_qty = sum([flt(d[0]) for d in self.item_dict[item]]) 448 | item_list.append([item, self.item_dict[item][0][1], self.item_dict[item][0][2], total_qty]) 449 | item_qty = frappe.db.sql("""select warehouse, indented_qty, ordered_qty, actual_qty 450 | from `tabBin` where item_code = %s""", item, as_dict=1) 451 | 452 | i_qty, o_qty, a_qty = 0, 0, 0 453 | for w in item_qty: 454 | i_qty, o_qty, a_qty = i_qty + flt(w.indented_qty), o_qty + \ 455 | flt(w.ordered_qty), a_qty + flt(w.actual_qty) 456 | 457 | item_list.append(['', '', '', '', w.warehouse, flt(w.indented_qty), 458 | flt(w.ordered_qty), flt(w.actual_qty)]) 459 | if item_qty: 460 | item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty]) 461 | else: 462 | item_list.append(['', '', '', '', 'Total', 0, 0, 0]) 463 | 464 | return item_list 465 | 466 | def raise_material_requests(self): 467 | """ 468 | Raise Material Request if projected qty is less than qty required 469 | Requested qty should be shortage qty considering minimum order qty 470 | """ 471 | self.validate_data() 472 | if not self.purchase_request_for_warehouse: 473 | frappe.throw(_("Please enter Warehouse for which Material Request will be raised")) 474 | 475 | bom_dict = self.get_so_wise_planned_qty() 476 | self.get_raw_materials(bom_dict,self.create_material_requests_non_stock_request) 477 | 478 | if self.item_dict: 479 | self.create_material_request() 480 | 481 | def get_requested_items(self): 482 | items_to_be_requested = frappe._dict() 483 | 484 | if not self.create_material_requests_for_all_required_qty: 485 | item_projected_qty = self.get_projected_qty() 486 | 487 | for item, so_item_qty in self.item_dict.items(): 488 | total_qty = sum([flt(d[0]) for d in so_item_qty]) 489 | requested_qty = 0 490 | 491 | if self.create_material_requests_for_all_required_qty: 492 | requested_qty = total_qty 493 | elif total_qty > item_projected_qty.get(item, 0): 494 | # shortage 495 | requested_qty = total_qty - flt(item_projected_qty.get(item)) 496 | # consider minimum order qty 497 | 498 | if requested_qty and requested_qty < flt(so_item_qty[0][3]): 499 | requested_qty = flt(so_item_qty[0][3]) 500 | 501 | # distribute requested qty SO wise 502 | for item_details in so_item_qty: 503 | if requested_qty: 504 | sales_order = item_details[4] or "No Sales Order" 505 | if self.get_items_from == "Material Request": 506 | sales_order = "No Sales Order" 507 | if requested_qty <= item_details[0]: 508 | adjusted_qty = requested_qty 509 | else: 510 | adjusted_qty = item_details[0] 511 | 512 | items_to_be_requested.setdefault(item, {}).setdefault(sales_order, 0) 513 | items_to_be_requested[item][sales_order] += adjusted_qty 514 | requested_qty -= adjusted_qty 515 | else: 516 | break 517 | 518 | # requested qty >= total so qty, due to minimum order qty 519 | if requested_qty: 520 | items_to_be_requested.setdefault(item, {}).setdefault("No Sales Order", 0) 521 | items_to_be_requested[item]["No Sales Order"] += requested_qty 522 | 523 | return items_to_be_requested 524 | 525 | def get_item_projected_qty(self,item): 526 | item_projected_qty = frappe.db.sql(""" 527 | select ifnull(sum(projected_qty),0) as qty 528 | from `tabBin` 529 | where item_code = %(item_code)s and warehouse=%(warehouse)s 530 | """, { 531 | "item_code": item, 532 | "warehouse": self.purchase_request_for_warehouse 533 | }, as_dict=1) 534 | 535 | return item_projected_qty[0].qty 536 | 537 | def get_projected_qty(self): 538 | items = self.item_dict.keys() 539 | item_projected_qty = frappe.db.sql("""select item_code, sum(projected_qty) 540 | from `tabBin` where item_code in (%s) and warehouse=%s group by item_code""" % 541 | (", ".join(["%s"]*len(items)), '%s'), tuple(items + [self.purchase_request_for_warehouse])) 542 | 543 | return dict(item_projected_qty) 544 | 545 | def create_material_request(self): 546 | items_to_be_requested = self.get_requested_items() 547 | 548 | material_request_list = [] 549 | if items_to_be_requested: 550 | for item in items_to_be_requested: 551 | item_wrapper = frappe.get_doc("Item", item) 552 | material_request = frappe.new_doc("Material Request") 553 | material_request.update({ 554 | "transaction_date": nowdate(), 555 | "status": "Draft", 556 | "company": self.company, 557 | "requested_by": frappe.session.user 558 | }) 559 | material_request.update({"material_request_type": item_wrapper.default_material_request_type}) 560 | 561 | for sales_order, requested_qty in items_to_be_requested[item].items(): 562 | material_request.append("items", { 563 | "doctype": "Material Request Item", 564 | "__islocal": 1, 565 | "item_code": item, 566 | "item_name": item_wrapper.item_name, 567 | "description": item_wrapper.description, 568 | "uom": item_wrapper.stock_uom, 569 | "item_group": item_wrapper.item_group, 570 | "brand": item_wrapper.brand, 571 | "qty": requested_qty, 572 | "schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)), 573 | "warehouse": self.purchase_request_for_warehouse, 574 | "sales_order": sales_order if sales_order!="No Sales Order" else None, 575 | "project": frappe.db.get_value("Sales Order", sales_order, "project") \ 576 | if sales_order!="No Sales Order" else None 577 | }) 578 | 579 | material_request.flags.ignore_permissions = 1 580 | material_request.submit() 581 | material_request_list.append(material_request.name) 582 | 583 | if material_request_list: 584 | message = ["""%s""" % \ 585 | (p, p) for p in material_request_list] 586 | msgprint(_("Material Requests {0} created").format(comma_and(message))) 587 | else: 588 | msgprint(_("Nothing to request")) 589 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/test_simplified_production_tool.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // rename this file from _test_[name] to test_[name] to activate 3 | // and remove above this line 4 | 5 | QUnit.test("test: Simplified Production Tool", function (assert) { 6 | let done = assert.async(); 7 | 8 | // number of asserts 9 | assert.expect(1); 10 | 11 | frappe.run_serially([ 12 | // insert a new Simplified Production Tool 13 | () => frappe.tests.make('Simplified Production Tool', [ 14 | // values to be set 15 | {key: 'value'} 16 | ]), 17 | () => { 18 | assert.equal(cur_frm.doc.key, 'value'); 19 | }, 20 | () => done() 21 | ]); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /spp/simplified_production_process/doctype/simplified_production_tool/test_simplified_production_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2017, o10c and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | import frappe 7 | import unittest 8 | 9 | class TestSimplifiedProductionTool(unittest.TestCase): 10 | pass 11 | -------------------------------------------------------------------------------- /spp/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/templates/__init__.py -------------------------------------------------------------------------------- /spp/templates/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiousi/erpnext-spp/44eb2064bb476a2460a0f34085d61537bd60b2f3/spp/templates/pages/__init__.py --------------------------------------------------------------------------------