Compare commits

...

51 Commits

Author SHA1 Message Date
Nabin Hait
398f144833 Merge branch 'develop' 2017-04-24 18:59:01 +05:30
Nabin Hait
e2741e85fd bumped to version 8.0.16 2017-04-24 19:29:01 +06:00
Rohit Waghchaure
dd70fbfdae [fix] Letter head not showing in the report 2017-04-24 18:57:51 +05:30
Manas Solanki
6eaf281b2b Export and import program enrollments 2017-04-24 18:44:12 +05:30
Ishan Loya
56c1b2a625 Make proposed corrections 2017-04-24 10:44:36 +05:30
Ishan Loya
b12e15dcc4 Change modified by 2017-04-24 10:44:36 +05:30
Ishan Loya
e5fdd47fd8 Hide 'Qty Transferred for Manufacturing' field if skip material transfer 2017-04-24 10:44:36 +05:30
Ishan Loya
04c69fb46d Minor fix 2017-04-24 10:44:36 +05:30
Ishan Loya
f04ef8dfe0 Update documentation for Production Order skipping material transfer entry option 2017-04-24 10:44:36 +05:30
Ishan Loya
7544904857 Add option to skip material transfer for production orders 2017-04-24 10:44:36 +05:30
Rushabh Mehta
c44910370b [fix] is_sample_item 2017-04-24 10:28:38 +05:30
Rushabh Mehta
551406ab11 [enhance] automatic batch selection in Delivery Note and Stock Entry 2017-04-24 10:28:38 +05:30
Rushabh Mehta
e385b5b97b [enhance] automatic batch creation, move and split 2017-04-24 10:28:38 +05:30
mbauskar
bb2670d57a [fixes] minor fixes in bank reconciliation form and report 2017-04-24 10:23:50 +05:30
mbauskar
0b293133be [minor] fixes for unsupported operand type(s) for +=: 'int' and 'NoneType' 2017-04-21 21:40:30 +05:30
ibi
83c1c3a171 fleet_management: replace references to fleet_management by hr in vehicle_log doctype 2017-04-21 17:57:07 +05:30
mbauskar
c3d642e5d1 [minor] allow bulk edit for doctype 2017-04-21 17:50:40 +05:30
Manas Solanki
8230ce095f [Fix] fixed routing to the assessment result 2017-04-21 17:49:52 +05:30
Nabin Hait
e713a7d840 Merge branch 'develop' 2017-04-21 11:49:09 +05:30
Nabin Hait
557847a5ba bumped to version 8.0.15 2017-04-21 12:19:09 +06:00
mbauskar
9bac58cdc6 [fixes] fixes in setup wizard for education domain 2017-04-21 11:38:28 +05:30
Nabin Hait
5650bf9ba5 Merge branch 'develop' 2017-04-20 08:51:44 +05:30
Nabin Hait
9afb53203f bumped to version 8.0.14 2017-04-20 09:21:44 +06:00
Nabin Hait
825e053e66 Fixed project_copied_from patch 2017-04-20 08:50:37 +05:30
Nabin Hait
3e519770de Merge pull request #8505 from nabinhait/develop
Fixed allow_zero_valuation_rate patch
2017-04-20 08:46:30 +05:30
Nabin Hait
e42fb32f6f Fixed allow_zero_valuation_rate patch 2017-04-20 08:45:18 +05:30
Nabin Hait
00bec1c272 Merge pull request #8504 from nabinhait/allow_on_submit_fix
Fixes allowed on submit
2017-04-19 21:11:13 +05:30
Nabin Hait
68ae9f3f88 Merge branch 'develop' into allow_on_submit_fix 2017-04-19 21:10:23 +05:30
mbauskar
8f3cc81302 [minor] minor fixes in pricing_rule and set_missing_value 2017-04-19 21:05:37 +05:30
mbauskar
9e9d242a24 [minor] fixes in test cases and added the test case for multiple uom in selling 2017-04-19 21:05:37 +05:30
mbauskar
287fe81329 [minor] calculate price list rate based on items uom 2017-04-19 21:05:37 +05:30
Nabin Hait
24053478a1 Merge pull request #8486 from nabinhait/develop
Asset Depreciation Enhancements
2017-04-19 21:01:34 +05:30
Nabin Hait
f382373cf4 Merge branch 'develop' into develop 2017-04-19 21:01:19 +05:30
CH
61f4a8e757 Make the address type translatable in the Address_HTML field 2017-04-19 20:58:53 +05:30
Nabin Hait
eef55185fc Update purchase_receipt_item.json 2017-04-19 20:58:09 +05:30
Nabin Hait
8691e0777b Update purchase_invoice_item.json 2017-04-19 20:58:09 +05:30
Nabin Hait
2de3bf7a0f Fixed indexes on sales and purchase transactions 2017-04-19 20:58:09 +05:30
Julian Robbins
2e6f12b850 Update purchase-details.md 2017-04-19 20:55:04 +05:30
mbauskar
ba41242f1f [fixes] fixed the pricing rule issue https://github.com/frappe/erpnext/issues/8493 2017-04-19 20:54:23 +05:30
Kanchan Chauhan
0633df5872 Item variant searchable in website products 2017-04-19 20:51:43 +05:30
Rohit Waghchaure
f4f774d1df [fix] offline_pos_name is not defined during print from the POS 2017-04-19 20:50:35 +05:30
Faris Ansari
695327a513 Duplicate dependencies when project is duplicated
- fix frappe/erpnext#8274
2017-04-19 20:48:49 +05:30
Rohit Waghchaure
019501e4a0 [fix] Balance Sheet, linking account to general ledger is not working 2017-04-19 20:46:30 +05:30
Nabin Hait
2704162f5a Fixes allowd on submit 2017-04-19 16:25:39 +05:30
Nabin Hait
117be7ddd5 Added a settings in Accounts Settings to disable booking depreciation entry automatically 2017-04-18 14:03:06 +05:30
Nabin Hait
f59920500c Merge branch 'develop' of github.com:nabinhait/erpnext into develop 2017-04-18 13:13:38 +05:30
Nabin Hait
8a01980757 Unlink Journal Entry reference from Asset 2017-04-18 13:11:10 +05:30
mbauskar
5123a8519b [minor] check if joining date is available before validating attandance date 2017-04-18 11:08:57 +05:30
Rohit Waghchaure
d7de3c606b [fix] Pricing rule for pos 2017-04-17 17:32:36 +05:30
Rohit Waghchaure
dc981dc546 [fix] Auto serial no fecthed on the invoice even if stock update is disabled issue 2017-04-17 17:31:32 +05:30
Nabin Hait
3fc3305251 bom traversing: argument mutable issue 2017-04-11 16:00:48 +05:30
86 changed files with 4545 additions and 3336 deletions

View File

@@ -2,7 +2,7 @@
from __future__ import unicode_literals
import frappe
__version__ = '8.0.13'
__version__ = '8.0.16'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@@ -25,7 +26,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Make Accounting Entry For Every Stock Movement",
"length": 0,
"no_copy": 0,
@@ -33,6 +36,7 @@
"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,
@@ -51,7 +55,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Accounts Frozen Upto",
"length": 0,
"no_copy": 0,
@@ -59,6 +65,7 @@
"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,
@@ -77,7 +84,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
"length": 0,
"no_copy": 0,
@@ -86,6 +95,7 @@
"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,
@@ -103,7 +113,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -111,6 +123,7 @@
"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,
@@ -129,7 +142,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Credit Controller",
"length": 0,
"no_copy": 0,
@@ -138,6 +153,7 @@
"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,
@@ -155,7 +171,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Check Supplier Invoice Number Uniqueness",
"length": 0,
"no_copy": 0,
@@ -164,6 +182,7 @@
"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,
@@ -181,7 +200,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Make Payment via Journal Entry",
"length": 0,
"no_copy": 0,
@@ -190,6 +211,7 @@
"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,
@@ -208,7 +230,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Unlink Payment on Cancellation of Invoice",
"length": 0,
"no_copy": 0,
@@ -217,6 +241,37 @@
"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,
"default": "1",
"fieldname": "book_asset_depreciation_entry_automatically",
"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": "Book Asset Depreciation Entry Automatically",
"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,
@@ -224,18 +279,18 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"icon": "icon-cog",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2016-10-20 16:12:38.595075",
"modified": "2017-04-18 13:35:59.166250",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -251,7 +306,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -266,6 +320,8 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0
}

View File

@@ -8,6 +8,10 @@ from frappe import _
from frappe.utils import flt, today, getdate
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
if not frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically"):
return
if not date:
date = today()
for asset in get_depreciable_assets(date):

View File

@@ -166,6 +166,23 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 70000)
def test_depreciation_entry_cancellation(self):
asset = frappe.get_doc("Asset", "Macbook Pro 1")
asset.submit()
post_depreciation_entries(date="2021-01-01")
asset.load_from_db()
# cancel depreciation entry
depr_entry = asset.get("schedules")[0].journal_entry
self.assertTrue(depr_entry)
frappe.get_doc("Journal Entry", depr_entry).cancel()
asset.load_from_db()
depr_entry = asset.get("schedules")[0].journal_entry
self.assertFalse(depr_entry)
def test_scrap_asset(self):
asset = frappe.get_doc("Asset", "Macbook Pro 1")
@@ -297,4 +314,7 @@ def set_depreciation_settings_in_company():
company.depreciation_expense_account = "_Test Depreciations - _TC"
company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
company.depreciation_cost_center = "_Test Cost Center - _TC"
company.save()
company.save()
# Enable booking asset depreciation entry automatically
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)

View File

@@ -3,13 +3,13 @@
frappe.ui.form.on("Bank Reconciliation", {
setup: function(frm) {
frm.get_docfield("payment_entries").allow_bulk_edit = 1;
frm.add_fetch("bank_account", "account_currency", "account_currency");
},
onload: function(frm) {
var default_bank_account = locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"];
let default_bank_account = frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("bank_account", default_bank_account);
frm.set_query("bank_account", function() {

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 1,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@@ -7,11 +8,15 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Select account head of the bank where cheque was deposited.",
"fieldname": "bank_account",
"fieldtype": "Link",
@@ -19,7 +24,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bank Account",
"length": 0,
"no_copy": 0,
@@ -28,6 +35,7 @@
"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,
@@ -35,16 +43,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Currency",
"length": 0,
"no_copy": 0,
@@ -53,6 +65,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -60,16 +73,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
@@ -77,6 +94,7 @@
"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,
@@ -84,16 +102,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
@@ -101,6 +123,7 @@
"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,
@@ -108,16 +131,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "include_reconciled_entries",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Include Reconciled Entries",
"length": 0,
"no_copy": 0,
@@ -125,6 +152,7 @@
"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,
@@ -132,16 +160,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_payment_entries",
"fieldtype": "Button",
"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": "Get Payment Entries",
"length": 0,
"no_copy": 0,
@@ -150,6 +182,7 @@
"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,
@@ -157,16 +190,20 @@
"unique": 0
},
{
"allow_bulk_edit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_entries",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Entries",
"length": 0,
"no_copy": 0,
@@ -175,6 +212,7 @@
"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,
@@ -182,16 +220,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "update_clearance_date",
"fieldtype": "Button",
"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": "Update Clearance Date",
"length": 0,
"no_copy": 0,
@@ -200,6 +242,7 @@
"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,
@@ -207,16 +250,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Amount",
"length": 0,
"no_copy": 0,
@@ -225,6 +272,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -232,19 +280,19 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 1,
"hide_toolbar": 1,
"icon": "fa fa-check",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2016-06-28 13:11:09.396353",
"modified": "2017-04-21 16:58:26.902732",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation",
@@ -274,7 +322,8 @@
"quick_entry": 1,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_seen": 0,
"version": 0
"track_changes": 0,
"track_seen": 0
}

View File

@@ -72,6 +72,7 @@ class JournalEntry(AccountsController):
self.update_expense_claim()
self.update_employee_loan()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
def unlink_advance_entry_reference(self):
for d in self.get("accounts"):
@@ -81,6 +82,18 @@ class JournalEntry(AccountsController):
d.reference_type = ''
d.reference_name = ''
d.db_update()
def unlink_asset_reference(self):
for d in self.get("accounts"):
if d.reference_type=="Asset" and d.reference_name:
asset = frappe.get_doc("Asset", d.reference_name)
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
asset.value_after_depreciation += s.depreciation_amount
asset.db_set("value_after_depreciation", asset.value_after_depreciation)
asset.set_status()
def validate_party(self):
for d in self.get("accounts"):

View File

@@ -23,6 +23,8 @@ class PricingRule(Document):
self.validate_price_or_discount()
self.validate_max_discount()
if not self.margin_type: self.margin_rate_or_amount = 0.0
def validate_mandatory(self):
for field in ["apply_on", "applicable_for"]:
tocheck = frappe.scrub(self.get(field) or "")
@@ -143,7 +145,7 @@ def get_pricing_rule_for_item(args):
})
if args.ignore_pricing_rule or not args.item_code:
if frappe.db.exists(args.doctype, args.name) and args.get("pricing_rule"):
if args.get("pricing_rule"):
item_details = remove_pricing_rule(args, item_details)
return item_details
@@ -178,7 +180,7 @@ def get_pricing_rule_for_item(args):
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
if pricing_rule.price_or_discount == "Price":
item_details.update({
"price_list_rate": pricing_rule.price/flt(args.conversion_rate) \
"price_list_rate": (pricing_rule.price/flt(args.conversion_rate)) * args.conversion_factor or 1.0 \
if args.conversion_rate else 0.0,
"discount_percentage": 0.0
})

View File

@@ -1422,7 +1422,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -1508,7 +1508,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -1939,7 +1939,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-04-17 13:44:17.460674",
"modified": "2017-04-19 11:54:16.112134",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -1953,4 +1953,4 @@
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}
}

View File

@@ -32,7 +32,7 @@ def get_pos_data():
'doc': doc,
'default_customer': pos_profile.get('customer'),
'items': get_items_list(pos_profile),
'item_groups': get_item_group(pos_profile),
'item_groups': get_item_groups(pos_profile),
'customers': customers,
'address': get_customers_address(customers),
'serial_no_data': get_serial_no_data(pos_profile, doc.company),
@@ -132,7 +132,7 @@ def get_items_list(pos_profile):
if pos_profile.get('item_groups'):
# Get items based on the item groups defined in the POS profile
for d in pos_profile.get('item_groups'):
item_groups.extend(get_child_nodes('Item Group', d.item_group))
item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
cond = "item_group in (%s)"%(', '.join(['%s']*len(item_groups)))
return frappe.db.sql("""
@@ -146,14 +146,19 @@ def get_items_list(pos_profile):
disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond}
""".format(cond=cond), tuple(item_groups), as_dict=1)
def get_item_group(pos_profile):
def get_item_groups(pos_profile):
item_group_dict = {}
if pos_profile.get('item_groups'):
item_groups = []
for d in pos_profile.get('item_groups'):
item_groups.extend(get_child_nodes('Item Group', d.item_group))
return item_groups
else:
return frappe.db.sql_list("""Select name from `tabItem Group` order by name""")
item_groups = frappe.db.sql("""Select name,
lft, rgt from `tabItem Group` order by lft""", as_dict=1)
for data in item_groups:
item_group_dict[data.name] = [data.lft, data.rgt]
return item_group_dict
def get_customers_list(pos_profile):
cond = "1=1"
@@ -161,7 +166,7 @@ def get_customers_list(pos_profile):
if pos_profile.get('customer_groups'):
# Get customers based on the customer groups defined in the POS profile
for d in pos_profile.get('customer_groups'):
customer_groups.extend(get_child_nodes('Customer Group', d.customer_group))
customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)])
cond = "customer_group in (%s)"%(', '.join(['%s']*len(customer_groups)))
return frappe.db.sql(""" select name, customer_name, customer_group,
@@ -187,8 +192,8 @@ def get_customers_address(customers):
def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql_list(""" Select name from `tab{tab}` where
lft >= {lft} and rgt <= {rgt}""".format(tab=group_type, lft=lft, rgt=rgt))
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
def get_serial_no_data(pos_profile, company):
# get itemwise serial no data

View File

@@ -147,9 +147,9 @@
"price_list_rate": 50,
"qty": 10,
"rate": 50,
"uom": "_Test UOM",
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM"
"stock_uom": "_Test UOM 1"
},
{
"cost_center": "_Test Cost Center - _TC",
@@ -273,9 +273,9 @@
"parentfield": "items",
"price_list_rate": 62.5,
"qty": 10,
"uom": "_Test UOM",
"conversion_factor": 1,
"stock_uom": "_Test UOM"
"uom": "_Test UOM 1",
"conversion_factor": 1,
"stock_uom": "_Test UOM 1"
},
{

View File

@@ -1043,6 +1043,25 @@ class TestSalesInvoice(unittest.TestCase):
#check outstanding after advance cancellation
self.assertEqual(flt(si.outstanding_amount), flt(si.grand_total + si.total_advance, si.precision("outstanding_amount")))
def test_multiple_uom_in_selling(self):
si = frappe.copy_doc(test_records[1])
si.items[0].uom = "_Test UOM 1"
si.items[0].conversion_factor = None
si.items[0].price_list_rate = None
si.save()
expected_values = {
"keys": ["price_list_rate", "stock_uom", "uom", "conversion_factor", "rate", "amount",
"base_price_list_rate", "base_rate", "base_amount"],
"_Test Item": [1000, "_Test UOM", "_Test UOM 1", 10.0, 1000, 1000, 1000, 1000, 1000]
}
# check if the conversion_factor and price_list_rate is calculated according to uom
for d in si.get("items"):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)

View File

@@ -1548,7 +1548,7 @@
"unique": 0
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -2094,7 +2094,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-04-05 23:28:13.520429",
"modified": "2017-04-19 11:53:26.682964",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -407,8 +407,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
});
this.search_item_group = this.wrapper.find('.search-item-group');
var dropdown_html = me.item_groups.map(function(item_group) {
sorted_item_groups = this.get_sorted_item_groups()
var dropdown_html = sorted_item_groups.map(function(item_group) {
return "<li><a class='option' data-value='"+item_group+"'>"+item_group+"</a></li>";
}).join("");
@@ -437,6 +437,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
})
},
get_sorted_item_groups: function() {
list = {}
$.each(this.item_groups, function(i, data) {
list[i] = data[0]
})
return Object.keys(list).sort(function(a,b){return list[a]-list[b]})
},
toggle_more_btn: function() {
if(!this.items || this.items.length <= this.page_len) {
this.wrapper.find(".btn-more").hide();
@@ -1091,9 +1100,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
// $(me.wrapper).find(".pos-item-wrapper").on("click", function () {
$(this.wrapper).on("click", ".pos-item-wrapper", function () {
me.item_code = '';
me.customer_validate();
if($(me.pos_bill).is(":hidden")) return;
me.customer_validate();
if (me.frm.doc.docstatus == 0) {
me.items = me.get_items($(this).attr("data-item-code"))
me.add_to_cart();
@@ -1768,7 +1777,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
var me = this;
return $.grep(this.pricing_rules, function (data) {
if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) {
if (data.item_code == item.item_code || in_list(['All Item Groups', item.item_group], data.item_group) || item.brand == data.brand) {
if (me.validate_item_condition(data, item)) {
if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) {
return me.validate_condition(data)
} else {
@@ -1779,6 +1788,26 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
})
},
validate_item_condition: function (data, item) {
var apply_on = frappe.model.scrub(data.apply_on);
return (data.apply_on == 'Item Group')
? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]);
},
validate_item_group: function (pr_item_group, cart_item_group) {
//pr_item_group = pricing rule's item group
//cart_item_group = cart item's item group
//this.item_groups has information about item group's lft and rgt
//for example: {'Foods': [12, 19]}
pr_item_group = this.item_groups[pr_item_group]
cart_item_group = this.item_groups[cart_item_group]
return (cart_item_group[0] >= pr_item_group[0] &&
cart_item_group[1] <= pr_item_group[1])
},
validate_condition: function (data) {
//This method check condition based on applicable for
condition = this.get_mapper_for_pricing_rule(data)[data.applicable_for]

View File

@@ -7,10 +7,10 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }}{{offline_pos_name}}<br>\n</p>\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"idx": 0,
"line_breaks": 0,
"modified": "2017-04-17 12:12:00.153763",
"modified": "2017-04-19 13:28:05.129504",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Point of Sale",

View File

@@ -1,6 +1,9 @@
{% var letterhead= filters.letter_head || frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head"); %}
{% if(letterhead) { %}
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head")] %}
{%= frappe.boot.letter_heads[letterhead].header %}
</div>
{% } %}
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h4 class="text-center">{%= filters.customer || filters.supplier %} </h4>
<h5 class="text-center">

View File

@@ -22,7 +22,8 @@ frappe.query_reports["Bank Clearance Summary"] = {
"fieldtype": "Link",
"options": "Account",
"reqd": 1,
"default": locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"],
"default": frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"get_query": function() {
return {
"query": "erpnext.controllers.queries.get_account_list",

View File

@@ -8,7 +8,8 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
"label": __("Bank Account"),
"fieldtype": "Link",
"options": "Account",
"default": locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"],
"default": frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
"reqd": 1,
"get_query": function() {
return {

View File

@@ -13,12 +13,16 @@
height: 37px;
}
</style>
{% var letterhead= filters.letter_head || frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head"); %}
{% if(letterhead) { %}
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head")] %}
{%= frappe.boot.letter_heads[letterhead].header %}
</div>
{% } %}
<h2 class="text-center">{%= __(report.report_name) %}</h2>
<h4 class="text-center">{%= filters.company %}</h3>
<h4 class="text-center">{%= filters.fiscal_year %}</h3>
<h3 class="text-center">{%= filters.company %}</h3>
<h3 class="text-center">{%= filters.fiscal_year %}</h3>
<h5 class="text-center">{%= __("Currency") %} : {%= erpnext.get_currency(filters.company) %} </h4>
{% if (filters.from_date) { %}
<h4 class="text-center">{%= dateutil.str_to_user(filters.from_date) %} - {%= dateutil.str_to_user(filters.to_date) %}</h3>
{% } %}
@@ -47,7 +51,7 @@
<td class="text-right">
{% var fieldname = report.columns[i].field; %}
{% if (!is_null(row[fieldname])) { %}
{%= format_currency(row[fieldname], row.currency) %}
{%= format_number(row[fieldname], null)%}
{% } %}
</td>
{% } %}

View File

@@ -1,6 +1,9 @@
{% var letterhead= filters.letter_head || frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head"); %}
{% if(letterhead) { %}
<div style="margin-bottom: 7px;" class="text-center">
{%= frappe.boot.letter_heads[filters.letter_head || frappe.get_doc(":Company", filters.company).default_letter_head || frappe.defaults.get_default("letter_head")] %}
{%= frappe.boot.letter_heads[letterhead].header %}
</div>
{% } %}
<h2 class="text-center">{%= __("Statement of Account") %}</h2>
<h4 class="text-center">{%= (filters.party || filters.account) && ((filters.party || filters.account) + ", ") || "" %} {%= filters.company %}</h4>
<h5 class="text-center">

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
@@ -341,7 +342,7 @@
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -1207,6 +1208,8 @@
"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": "Link to material requests",
@@ -3185,18 +3188,18 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-file-text",
"idx": 105,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-28 18:20:15.650815",
"modified": "2017-04-18 18:49:49.535066",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -3294,4 +3297,4 @@
"title_field": "title",
"track_changes": 0,
"track_seen": 0
}
}

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
@@ -98,7 +99,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -156,7 +157,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -799,6 +800,7 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "9",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": "100px",
@@ -1647,17 +1649,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-02-17 16:44:55.434162",
"modified": "2017-04-18 18:49:08.604055",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -186,15 +186,19 @@ class AccountsController(TransactionBase):
ret = get_item_details(args)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
if (item.get(fieldname) is None or fieldname in force_item_fields):
item.set(fieldname, value)
elif fieldname == "cost_center" and not item.get("cost_center"):
elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname):
item.set(fieldname, value)
elif fieldname == "serial_no":
stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty")
if stock_qty != len(item.get('serial_no').split('\n')):
item.set(fieldname, value)
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
item.set(fieldname, value)

View File

@@ -41,7 +41,7 @@ class SellingController(StockController):
# set contact and address details for customer, if they are not mentioned
self.set_missing_lead_customer_details()
self.set_price_list_and_item_details()
self.set_price_list_and_item_details(for_validate=for_validate)
def set_missing_lead_customer_details(self):
if getattr(self, "customer", None):
@@ -60,9 +60,9 @@ class SellingController(StockController):
posting_date=self.get('transaction_date') or self.get('posting_date'),
company=self.company))
def set_price_list_and_item_details(self):
def set_price_list_and_item_details(self, for_validate=False):
self.set_price_list_currency("Selling")
self.set_missing_item_details()
self.set_missing_item_details(for_validate=for_validate)
def apply_shipping_rule(self):
if self.shipping_rule:

View File

@@ -177,6 +177,19 @@ class StockController(AccountsController):
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger
def make_batches(self, warehouse_field):
'''Create batches if required. Called before submit'''
for d in self.items:
if d.get(warehouse_field) and not d.batch_no:
has_batch_no, create_new_batch = frappe.db.get_value('Item', d.item_code, ['has_batch_no', 'create_new_batch'])
if has_batch_no and create_new_batch:
d.batch_no = frappe.get_doc(dict(
doctype='Batch',
item=d.item_code,
supplier=getattr(self, 'supplier', None),
reference_doctype=self.doctype,
reference_name=self.name)).insert().name
def make_adjustment_entry(self, expected_gle, voucher_obj):
from erpnext.accounts.utils import get_stock_and_account_difference
account_list = [d.account for d in expected_gle]

View File

@@ -60,6 +60,7 @@ class calculate_taxes_and_totals(object):
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
item.total_margin = self.calculate_margin(item)
item.rate = flt(item.total_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))\
if item.total_margin > 0 else item.rate

View File

@@ -18,6 +18,9 @@ def work():
# fixed_asset.work() already run
return
# Enable booking asset depreciation entry automatically
frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
# post depreciation entries as on today
post_depreciation_entries()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -62,6 +62,13 @@ by:
<img class="screenshot" alt="Stock Entry for PO" src="{{docs_base_url}}/assets/img/manufacturing/PO-material-transfer-updated.png">
#### Material Transfer through Stock Entry
Use cases for this option are:
* If material transfer is done in bulk and/or is not required to be tracked against a particular Production Order
* If the responsibility for Material Transfer and Production Entry lies with two separate users
If this is the case, you can select the Skip Material Transfer check box, which will allow you to make the “Manufacture” Stock Entry directly by clicking on the Finish button.
### Making Time Logs
* Progress in the Production Order can be tracked using [Timesheet]({{docs_base_url}}/user/manual/en/projects/timesheet/timesheet-against-production-order.html)

View File

@@ -1,27 +1,45 @@
Batch inventory feature in ERPNext allows you to group multiple units of an item,
Batch feature in ERPNext allows you to group multiple units of an item,
and assign them a unique value/number/tag called Batch No.
The practice of stocking based on batch is mainly followed in the pharmaceutical industry.
Medicines/drugs produced in a particular batched is assigned a unique id.
This helps them updating and tracking manufacturing and expiry date for all the units produced under specific batch.
This is done based on the Item. If the Item is batched, then a Batch number must be mentioned in every stock transaction. Batch numbers can be maintained manually or automatically
> Note: To set item as a batch item, "Has Batch No" field should be updated as Yes in the Item master.
### Item Setup
On every stock transaction (Purchase Receipt, Delivery Note, POS Invoice) made for batch item,
you should provide item's Batch No.
To set item as a batch item, "Has Batch No" field should be checked in the Item master.
If you want automatic batch creation at the time of Purchase Receipt, you must check "Create New Batches Automatically"
<img class="screenshot" alt="Item Setup for Batches" src="{{docs_base_url}}/assets/img/stock/item_setup_for_batch.png">
### Creating Batches
If you have not selected "Create New Batches Automatically", you will have to make Batches Manually as you go along.
To create new Batch No. master for an item, go to:
> Stock > Setup > Batch > New
Batch master is created before creation of Purchase Receipt.
Hence eveytime there is Purchase Receipt or Production entry being made for a batch item,
you will first create its Batch No, and then select it in Purcase order or Production Entry.
### Splitting and Moving Batches
<img class="screenshot" alt="batch" src="{{docs_base_url}}/assets/img/stock/batch.png">
When you open a batch, you will see all the quantities relating this that batch on the page.
> Note: In stock transactions, Batch IDs will be filtered based on Item Code, Warehouse,
Batch Expiry Date (compared with Posting date of a transaction) and Actual Qty in Warehouse.
<img class="screenshot" alt="Batch View" src="{{docs_base_url}}/assets/img/stock/batch_view.png">
To move the batch from one warehouse to another, you can click on the move button.
You can also split the batch into smaller one by clicking on "Split". This will create a new Batch based on this Batch and the quantities will be split between the batches.
### Transacting Items with Batches
Batch master is created before creation of Purchase Receipt.
Hence eveytime there is Purchase Receipt or Production Order being made for a batch item,
you will first create its Batch No, and then select it in Purchase order or Production Entry.
On every stock transaction (Purchase Receipt, Delivery Note, POS Invoice) made for batch item,
you should provide item's Batch No.
> Note: In stock transactions, Batch IDs will be filtered based on Item Code, Warehouse,
Batch Expiry Date (compared with Posting date of a transaction) and Actual Qty in Warehouse.
While searching for Batch ID without value in Warehouse field, then Actual Qty filter won't be applied.
{next}

View File

@@ -1,4 +1,4 @@
# purchase details
# Purchase Details
# How Do I Track Warranty Status?

View File

@@ -35,9 +35,11 @@ class Attendance(Document):
frappe.throw(_("No leave record found for employee {0} for {1}").format(self.employee, self.attendance_date))
def validate_attendance_date(self):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
if getdate(self.attendance_date) > getdate(nowdate()):
frappe.throw(_("Attendance can not be marked for future dates"))
elif getdate(self.attendance_date) < frappe.db.get_value("Employee", self.employee, "date_of_joining"):
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date"))
def validate_employee(self):

View File

@@ -6,7 +6,7 @@ frappe.ui.form.on("Vehicle Log", {
vehicle_log=frappe.model.get_doc(cdt,cdn);
if (vehicle_log.license_plate) {
frappe.call({
method: "erpnext.fleet_management.doctype.vehicle_log.vehicle_log.get_make_model",
method: "erpnext.hr.doctype.vehicle_log.vehicle_log.get_make_model",
args: {
license_plate: vehicle_log.license_plate
},
@@ -19,7 +19,7 @@ frappe.ui.form.on("Vehicle Log", {
},
expense_claim: function(frm){
frappe.call({
method: "erpnext.fleet_management.doctype.vehicle_log.vehicle_log.make_expense_claim",
method: "erpnext.hr.doctype.vehicle_log.vehicle_log.make_expense_claim",
args:{
docname: frm.doc.name
},

View File

@@ -72,15 +72,17 @@ frappe.ui.form.on("Production Order", {
message = title;
// pending qty
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
if(pending_complete) {
var title = __('{0} items in progress', [pending_complete]);
bars.push({
'title': title,
'width': ((pending_complete / frm.doc.qty * 100) - added_min) + '%',
'progress_class': 'progress-bar-warning'
})
message = message + '. ' + title;
if(!frm.doc.skip_transfer){
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
if(pending_complete) {
var title = __('{0} items in progress', [pending_complete]);
bars.push({
'title': title,
'width': ((pending_complete / frm.doc.qty * 100) - added_min) + '%',
'progress_class': 'progress-bar-warning'
})
message = message + '. ' + title;
}
}
frm.dashboard.add_progress(__('Status'), bars, message);
}
@@ -122,21 +124,32 @@ erpnext.production_order = {
frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Production Order'], __("Status"));
}
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
if(!frm.doc.skip_transfer){
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
frm.has_start_btn = true;
var btn = frm.add_custom_button(__('Start'),
cur_frm.cscript['Transfer Raw Materials']);
btn.addClass('btn-primary');
}
}
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true;
var btn = frm.add_custom_button(__('Finish'),
cur_frm.cscript['Update Finished Goods']);
if(!frm.doc.skip_transfer){
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true;
var btn = frm.add_custom_button(__('Finish'),
cur_frm.cscript['Update Finished Goods']);
if(doc.material_transferred_for_manufacturing==doc.qty) {
// all materials transferred for manufacturing,
// make this primary
if(doc.material_transferred_for_manufacturing==doc.qty) {
// all materials transferred for manufacturing,
// make this primary
btn.addClass('btn-primary');
}
}
} else {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true;
var btn = frm.add_custom_button(__('Finish'),
cur_frm.cscript['Update Finished Goods']);
btn.addClass('btn-primary');
}
}
@@ -235,9 +248,13 @@ $.extend(cur_frm.cscript, {
make_se: function(purpose) {
var me = this;
var max = (purpose === "Manufacture") ?
flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) :
flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing);
if(!this.frm.doc.skip_transfer){
var max = (purpose === "Manufacture") ?
flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) :
flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing);
} else {
var max = flt(this.frm.doc.qty) - flt(this.frm.doc.produced_qty);
}
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
description: __("Max: {0}", [max]), 'default': max },
@@ -315,4 +332,4 @@ cur_frm.fields_dict['project'].get_query = function(doc, dt, dn) {
['Project', 'status', 'not in', 'Completed, Cancelled']
]
}
}
}

View File

@@ -319,13 +319,43 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Check if material transfer entry is not required",
"fieldname": "skip_transfer",
"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": "Skip Material Transfer",
"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,
"default": "0",
"depends_on": "eval:doc.docstatus==1",
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
"description": "",
"fieldname": "material_transferred_for_manufacturing",
"fieldtype": "Float",
@@ -1316,7 +1346,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-04-10 12:13:09.312186",
"modified": "2017-04-21 16:31:19.509721",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Order",

View File

@@ -535,7 +535,7 @@ def make_stock_entry(production_order_id, purpose, qty=None):
stock_entry.from_warehouse = production_order.wip_warehouse
stock_entry.to_warehouse = production_order.fg_warehouse
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.project = frappe.db.get_value("Stock Entry",{"production_order": production_order_id,"purpose": "Material Transfer for Manufacture"}, "project")
stock_entry.project = production_order.project
stock_entry.set("additional_costs", additional_costs)
stock_entry.get_items()

View File

@@ -385,4 +385,7 @@ erpnext.patches.v8_0.addresses_linked_to_lead
execute:frappe.delete_doc('DocType', 'Purchase Common')
erpnext.patches.v8_0.update_stock_qty_value_in_purchase_invoice
erpnext.patches.v8_0.update_supplier_address_in_stock_entry
erpnext.patches.v8_0.rename_is_sample_item_to_allow_zero_valuation_rate
erpnext.patches.v8_0.rename_is_sample_item_to_allow_zero_valuation_rate
erpnext.patches.v8_0.set_null_to_serial_nos_for_disabled_sales_invoices
erpnext.patches.v8_0.enable_booking_asset_depreciation_automatically
erpnext.patches.v8_0.set_project_copied_from

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.set_value("Accounts Settings", None,
"book_asset_depreciation_entry_automatically", 1)

View File

@@ -9,4 +9,5 @@ def execute():
for doctype in doc_list:
frappe.reload_doctype(doctype)
rename_field(doctype, "is_sample_item", "allow_zero_valuation_rate")
if "is_sample_item" in frappe.db.get_table_columns(doctype):
rename_field(doctype, "is_sample_item", "allow_zero_valuation_rate")

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
def execute():
frappe.db.sql("""
update
`tabSales Invoice Item`
set serial_no = NULL
where
parent in (select name from `tabSales Invoice` where update_stock = 0 and docstatus = 1)""")

View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doctype("Project")
frappe.db.sql('''
UPDATE `tabProject`
SET copied_from=name
WHERE copied_from is NULL
''')

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:project_name",
@@ -553,6 +554,35 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "copied_from",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Copied From",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -1052,7 +1082,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -1174,19 +1204,19 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-puzzle-piece",
"idx": 29,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 4,
"modified": "2017-02-17 17:24:04.146872",
"modified_by": "Administrator",
"modified": "2017-04-19 13:16:32.462005",
"modified_by": "faris@erpnext.com",
"module": "Projects",
"name": "Project",
"owner": "Administrator",
@@ -1261,4 +1291,4 @@
"timeline_field": "customer",
"track_changes": 0,
"track_seen": 1
}
}

View File

@@ -205,6 +205,32 @@ class Project(Document):
def on_update(self):
self.load_tasks()
self.sync_tasks()
self.update_dependencies_on_duplicated_project()
def update_dependencies_on_duplicated_project(self):
if self.flags.dont_sync_tasks: return
if not self.copied_from:
self.copied_from = self.name
if self.name != self.copied_from and self.get('__unsaved'):
# duplicated project
dependency_map = {}
for task in self.tasks:
name, depends_on_tasks = frappe.db.get_value(
'Task', { "subject": task.title, "project": self.copied_from }, ['name', 'depends_on_tasks']
)
depends_on_tasks = [x for x in depends_on_tasks.split(',') if x]
dependency_map[task.title] = [ x['subject'] for x in frappe.get_list(
'Task Depends On', {"parent": name}, ['subject'])]
for key, value in dependency_map.iteritems():
task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name })
task_doc = frappe.get_doc('Task', task_name)
for dt in value:
dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name })
task_doc.append('depends_on', {"task": dt_name})
task_doc.save()
def get_timeline_data(doctype, name):
'''Return timeline for attendance'''

View File

@@ -53,9 +53,9 @@ class Task(Document):
frappe.throw(_("Progress % for a task cannot be more than 100."))
def update_depends_on(self):
depends_on_tasks = ""
depends_on_tasks = self.depends_on_tasks or ""
for d in self.depends_on:
if d.task:
if d.task and not d.task in depends_on_tasks:
depends_on_tasks += d.task + ","
self.depends_on_tasks = depends_on_tasks

View File

@@ -7,7 +7,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
if(item.margin_type == "Percentage"){
item.total_margin = flt(item.price_list_rate)
+ flt(item.price_list_rate) * ( flt(item.margin_rate_or_amount) / 100);
}else{
} else {
item.total_margin = flt(item.price_list_rate) + flt(item.margin_rate_or_amount);
}

View File

@@ -260,6 +260,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
customer: me.frm.doc.customer,
supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency,
update_stock: in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
conversion_rate: me.frm.doc.conversion_rate,
price_list: me.frm.doc.selling_price_list ||
me.frm.doc.buying_price_list,
@@ -274,14 +275,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
doctype: me.frm.doc.doctype,
name: me.frm.doc.name,
project: item.project || me.frm.doc.project,
qty: item.qty,
stock_qty: item.stock_qty
qty: item.qty || 1,
stock_qty: item.stock_qty,
conversion_factor: item.conversion_factor
}
},
callback: function(r) {
if(!r.exc) {
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
me.toggle_conversion_factor(item);
}
}
});
@@ -566,11 +569,18 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
refresh_field("stock_qty", item.name, item.parentfield);
this.toggle_conversion_factor(item);
this.apply_price_list(item, true);
}
},
toggle_conversion_factor: function(item) {
// toggle read only property for conversion factor field if the uom and stock uom are same
this.frm.fields_dict.items.grid.toggle_enable("conversion_factor",
(item.uom != item.stock_uom)? true: false)
},
qty: function(doc, cdt, cdn) {
this.apply_pricing_rule(frappe.get_doc(cdt, cdn), true);
this.conversion_factor(doc, cdt, cdn);
},
@@ -762,7 +772,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"ignore_pricing_rule": me.frm.doc.ignore_pricing_rule,
"doctype": me.frm.doc.doctype,
"name": me.frm.doc.name,
"is_return": cint(me.frm.doc.is_return)
"is_return": cint(me.frm.doc.is_return),
"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"conversion_factor": me.frm.doc.conversion_factor
};
},
@@ -781,7 +793,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"parent": d.parent,
"pricing_rule": d.pricing_rule,
"warehouse": d.warehouse,
"serial_no": d.serial_no
"serial_no": d.serial_no,
"conversion_factor": d.conversion_factor || 1.0
});
// if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list
@@ -808,16 +821,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
for(var i=0, l=children.length; i<l; i++) {
var d = children[i];
var existing_pricing_rule = frappe.model.get_value(d.doctype, d.name, "pricing_rule");
for(var k in d) {
var v = d[k];
if (["doctype", "name"].indexOf(k)===-1) {
if(k=="price_list_rate") {
if(flt(v) != flt(d.price_list_rate)) price_list_rate_changed = true;
}
if(v) {
frappe.model.set_value(d.doctype, d.name, k, v);
}
frappe.model.set_value(d.doctype, d.name, k, v);
}
}

View File

@@ -26,13 +26,14 @@ erpnext.financial_statements = {
},
"open_general_ledger": function(data) {
if (!data.account) return;
var project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; })
frappe.route_options = {
"account": data.account,
"company": frappe.query_report_filters_by_name.company.get_value(),
"from_date": data.from_date || data.year_start_date,
"to_date": data.to_date || data.year_end_date,
"project": $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; })[0].$input.val()
"project": (project && project.length > 0) ? project[0].$input.val() : ""
};
frappe.set_route("query-report", "General Ledger");
},

View File

@@ -2,7 +2,7 @@
<div class="clearfix"></div>
{% for(var i=0, l=addr_list.length; i<l; i++) { %}
<p class="h6">
{%= i+1 %}. {%= addr_list[i].address_type!="Other" ? addr_list[i].address_type : addr_list[i].address_title %}
{%= i+1 %}. {%= addr_list[i].address_type!="Other" ? __(addr_list[i].address_type) : addr_list[i].address_title %}
{% if(addr_list[i].is_primary_address) { %}
<span class="text-muted">({%= __("Primary") %})</span>{% } %}
{% if(addr_list[i].is_shipping_address) { %}

View File

@@ -8,7 +8,7 @@ cur_frm.add_fetch("supervisor", "instructor_name", "supervisor_name");
frappe.ui.form.on("Assessment Plan", {
refresh: function(frm) {
if (!frm.doc.__islocal) {
if (frm.doc.docstatus == 1) {
frm.add_custom_button(__("Assessment Result"), function() {
frappe.route_options = {
assessment_plan: frm.doc.name

View File

@@ -5,6 +5,14 @@ cur_frm.add_fetch("assessment_plan", "student_group", "student_group");
cur_frm.add_fetch("assessment_plan", "student_batch", "student_batch");
frappe.ui.form.on('Assessment Result Tool', {
onload: function(frm) {
if (frappe.route_options) {
frm.doc.assessment_plan = frappe.route_options.assessment_plan;
frappe.route_options = null;
}
},
refresh: function(frm) {
frm.disable_save();
frm.page.clear_indicator();

View File

@@ -15,6 +15,6 @@ class Course(Document):
if self.assessment_criteria:
total_weightage = 0
for criteria in self.assessment_criteria:
total_weightage += criteria.weightage
total_weightage += criteria.weightage or 0
if total_weightage != 100:
frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%"))

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
@@ -23,6 +24,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Assessment Criteria",
@@ -52,6 +54,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@@ -80,6 +83,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Weightage",
@@ -92,23 +96,23 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-02-01 18:01:40.682674",
"modified": "2017-04-21 20:04:26.621419",
"modified_by": "Administrator",
"module": "Schools",
"name": "Course Assessment Criteria",
@@ -118,6 +122,7 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,

View File

@@ -1,7 +1,7 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "PE.#####",
"beta": 0,
@@ -14,6 +14,7 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -44,6 +45,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -74,6 +76,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -104,6 +107,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -134,6 +138,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -164,6 +169,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -192,6 +198,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -222,6 +229,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -252,6 +260,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -282,6 +291,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -312,6 +322,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -341,6 +352,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -370,6 +382,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -400,6 +413,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -429,6 +443,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -460,6 +475,7 @@
"width": ""
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -489,6 +505,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -530,7 +547,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-04-12 12:12:10.148274",
"modified": "2017-04-24 18:22:43.567607",
"modified_by": "Administrator",
"module": "Schools",
"name": "Program Enrollment",

View File

@@ -54,25 +54,30 @@ class TestCustomer(unittest.TestCase):
self.assertEquals(value, details.get(key))
def test_rename(self):
# delete communication linked to these 2 customers
for name in ("_Test Customer 1", "_Test Customer 1 Renamed"):
frappe.db.sql("""delete from `tabCommunication`
where communication_type='Comment' and reference_doctype=%s and reference_name=%s""",
("Customer", name))
# add comments
comment = frappe.get_doc("Customer", "_Test Customer 1").add_comment("Comment", "Test Comment for Rename")
# rename
frappe.rename_doc("Customer", "_Test Customer 1", "_Test Customer 1 Renamed")
# check if customer renamed
self.assertTrue(frappe.db.exists("Customer", "_Test Customer 1 Renamed"))
self.assertFalse(frappe.db.exists("Customer", "_Test Customer 1"))
# test that comment gets renamed
# test that comment gets linked to renamed doc
self.assertEquals(frappe.db.get_value("Communication", {
"communication_type": "Comment",
"reference_doctype": "Customer",
"reference_name": "_Test Customer 1 Renamed"
}), comment.name)
# rename back to original
frappe.rename_doc("Customer", "_Test Customer 1 Renamed", "_Test Customer 1")
def test_freezed_customer(self):

View File

@@ -23,9 +23,9 @@
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0
"uom": "_Test UOM 1",
"stock_uom": "_Test UOM 1",
"conversion_factor": 1.0
}
],
"quotation_to": "Customer",

View File

@@ -128,7 +128,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -813,6 +813,7 @@
"oldfieldtype": "Currency",
"options": "currency",
"permlevel": 0,
"precision": "9",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "100px",
@@ -1483,7 +1484,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -1515,7 +1516,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -1532,7 +1533,7 @@
"istable": 1,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-03-30 19:35:44.119169",
"modified": "2017-04-18 18:47:26.869235",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",

View File

@@ -3,8 +3,6 @@
{% include 'erpnext/selling/sales_common.js' %}
cur_frm.add_fetch('customer', 'tax_id', 'tax_id');
frappe.ui.form.on("Sales Order", {
setup: function(frm) {
$.extend(frm.cscript, new erpnext.selling.SalesOrderController({frm: frm}));
@@ -14,6 +12,7 @@ frappe.ui.form.on("Sales Order", {
'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order'
}
frm.add_fetch('customer', 'tax_id', 'tax_id');
},
onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {

View File

@@ -313,7 +313,7 @@
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -377,7 +377,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "160px"
@@ -791,7 +791,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -821,7 +821,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -2321,7 +2321,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -2896,7 +2896,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -3516,7 +3516,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-04-10 12:13:03.136885",
"modified": "2017-04-18 20:02:54.895559",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -1397,7 +1397,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -1429,7 +1429,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -1871,7 +1871,7 @@
"istable": 1,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-03-30 16:18:49.367870",
"modified": "2017-04-18 18:43:47.150922",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@@ -571,6 +571,7 @@ def create_program(args):
for i in xrange(1,6):
if args.get("program_" + str(i)):
program = frappe.new_doc("Program")
program.program_code = args.get("program_" + str(i))
program.program_name = args.get("program_" + str(i))
try:
program.save()
@@ -581,6 +582,7 @@ def create_course(args):
for i in xrange(1,6):
if args.get("course_" + str(i)):
course = frappe.new_doc("Course")
course.course_code = args.get("course_" + str(i))
course.course_name = args.get("course_" + str(i))
try:
course.save()

View File

@@ -1,12 +1,137 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.fields_dict['item'].get_query = function(doc, cdt, cdn) {
return {
query: "erpnext.controllers.queries.item_query",
filters:{
'is_stock_item': 1,
'has_batch_no': 1
frappe.ui.form.on('Batch', {
setup: (frm) => {
frm.fields_dict['item'].get_query = function(doc, cdt, cdn) {
return {
query: "erpnext.controllers.queries.item_query",
filters:{
'is_stock_item': 1,
'has_batch_no': 1
}
}
}
},
refresh: (frm) => {
if(!frm.is_new()) {
frm.add_custom_button(__("View Ledger"), () => {
frappe.route_options = {
batch_no: frm.doc.name
};
frappe.set_route("query-report", "Stock Ledger");
});
frm.trigger('make_dashboard');
}
},
make_dashboard: (frm) => {
if(!frm.is_new()) {
frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
args: {batch_no: frm.doc.name},
callback: (r) => {
if(!r.message) {
return;
}
var section = frm.dashboard.add_section(`<h5 style="margin-top: 0px;">
${ __("Stock Levels") }</a></h5>`);
// sort by qty
r.message.sort(function(a, b) { a.qty > b.qty ? 1 : -1 });
var rows = $('<div></div>').appendTo(section);
// show
(r.message || []).forEach(function(d) {
if(d.qty > 0) {
$(`<div class='row' style='margin-bottom: 10px;'>
<div class='col-sm-3 small' style='padding-top: 3px;'>${d.warehouse}</div>
<div class='col-sm-3 small text-right' style='padding-top: 3px;'>${d.qty}</div>
<div class='col-sm-6'>
<button class='btn btn-default btn-xs btn-move' style='margin-right: 7px;'
data-qty = "${d.qty}"
data-warehouse = "${d.warehouse}">
${__('Move')}</button>
<button class='btn btn-default btn-xs btn-split'
data-qty = "${d.qty}"
data-warehouse = "${d.warehouse}">
${__('Split')}</button>
</div>
</div>`).appendTo(rows);
}
});
// move - ask for target warehouse and make stock entry
rows.find('.btn-move').on('click', function() {
var $btn = $(this);
frappe.prompt({
fieldname: 'to_warehouse',
label: __('To Warehouse'),
fieldtype: 'Link',
options: 'Warehouse'
},
(data) => {
frappe.call({
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
args: {
item_code: frm.doc.item,
batch_no: frm.doc.name,
qty: $btn.attr('data-qty'),
from_warehouse: $btn.attr('data-warehouse'),
to_warehouse: data.to_warehouse
},
callback: (r) => {
frappe.show_alert(__('Stock Entry {0} created',
['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
frm.refresh();
},
});
},
__('Select Target Warehouse'),
__('Move')
)
});
// split - ask for new qty and batch ID (optional)
// and make stock entry via batch.batch_split
rows.find('.btn-split').on('click', function() {
var $btn = $(this);
frappe.prompt([{
fieldname: 'qty',
label: __('New Batch Qty'),
fieldtype: 'Float',
'default': $btn.attr('data-qty')
},
{
fieldname: 'new_batch_id',
label: __('New Batch ID (Optional)'),
fieldtype: 'Data',
}],
(data) => {
frappe.call({
method: 'erpnext.stock.doctype.batch.batch.split_batch',
args: {
item_code: frm.doc.item,
batch_no: frm.doc.name,
qty: data.qty,
warehouse: $btn.attr('data-warehouse'),
new_batch_id: data.new_batch_id
},
callback: (r) => {
frm.refresh();
},
});
},
__('Split Batch'),
__('Split')
)
})
frm.dashboard.show();
}
});
}
}
}
})

View File

@@ -1,8 +1,9 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "field:batch_id",
"autoname": "",
"beta": 0,
"creation": "2013-03-05 14:50:38",
"custom": 0,
@@ -17,12 +18,14 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.__islocal",
"fieldname": "batch_id",
"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": "Batch ID",
@@ -36,7 +39,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -52,6 +55,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Item",
@@ -71,6 +75,66 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "image",
"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": "eval:doc.parent_batch",
"fieldname": "parent_batch",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Parent Batch",
"length": 0,
"no_copy": 0,
"options": "Batch",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -82,6 +146,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@@ -109,6 +174,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expiry Date",
@@ -127,6 +193,153 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "source",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source",
"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,
"fieldname": "supplier",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Source Document Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -138,6 +351,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@@ -165,6 +379,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch Description",
@@ -185,18 +400,19 @@
"width": "300px"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-archive",
"idx": 1,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2016-11-07 05:50:33.973883",
"modified": "2017-04-20 03:22:19.888058",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch",
@@ -212,7 +428,6 @@
"export": 0,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@@ -224,9 +439,12 @@
"write": 1
}
],
"quick_entry": 0,
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "DESC",
"title_field": "item",
"track_changes": 0,
"track_seen": 0
}

View File

@@ -6,7 +6,27 @@ import frappe
from frappe import _
from frappe.model.document import Document
class UnableToSelectBatchError(frappe.ValidationError): pass
class Batch(Document):
def autoname(self):
'''Generate random ID for batch if not specified'''
if not self.batch_id:
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
temp = None
while not temp:
temp = frappe.generate_hash()[:7].upper()
if frappe.db.exists('Batch', temp):
temp = None
self.batch_id = temp
else:
frappe.throw(_('Batch ID is mandatory'), frappe.MandatoryError)
self.name = self.batch_id
def onload(self):
self.image = frappe.db.get_value('Item', self.item, 'image')
def validate(self):
self.item_has_batch_enabled()
@@ -14,3 +34,86 @@ class Batch(Document):
def item_has_batch_enabled(self):
if frappe.db.get_value("Item",self.item,"has_batch_no") == 0:
frappe.throw(_("The selected item cannot have Batch"))
@frappe.whitelist()
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
'''Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None
The user must pass either batch_no or batch_no + warehouse or item_code + warehouse
:param batch_no: Optional - give qty for this batch no
:param warehouse: Optional - give qty for this warehouse
:param item_code: Optional - give qty for this item'''
frappe.has_permission('Batch', throw=True)
out = 0
if batch_no and warehouse:
out = float(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
where warehouse=%s and batch_no=%s""",
(warehouse, batch_no))[0][0] or 0)
if batch_no and not warehouse:
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
from `tabStock Ledger Entry`
where batch_no=%s
group by warehouse''', batch_no, as_dict=1)
if not batch_no and item_code and warehouse:
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
from `tabStock Ledger Entry`
where item_code = %s and warehouse=%s
group by batch_no''', (item_code, warehouse), as_dict=1)
return out
@frappe.whitelist()
def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
'''Split the batch into a new batch'''
batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert()
stock_entry = frappe.get_doc(dict(
doctype='Stock Entry',
purpose='Repack',
items=[
dict(
item_code = item_code,
qty = float(qty or 0),
s_warehouse = warehouse,
batch_no = batch_no
),
dict(
item_code = item_code,
qty = float(qty or 0),
t_warehouse = warehouse,
batch_no = batch.name
),
]
))
stock_entry.insert()
stock_entry.submit()
return batch.name
def set_batch_nos(doc, warehouse_field, throw = False):
'''Automatically select `batch_no` for outgoing items in item table'''
for d in doc.items:
has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no')
warehouse = d.get(warehouse_field, None)
if has_batch_no and not d.batch_no and warehouse:
d.batch_no = get_batch_no(d.item_code, warehouse, d.qty, throw)
def get_batch_no(item_code, warehouse, qty, throw=False):
'''get the smallest batch with for the given item_code, warehouse and qty'''
batches = sorted(
get_batch_qty(item_code = item_code, warehouse = warehouse),
lambda a, b: 1 if a.qty > b.qty else -1)
batch_no = None
for b in batches:
if b.qty >= qty:
batch_no = b.batch_no
# found!
break
if not batch_no:
frappe.msgprint(_('Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement').format(frappe.bold(item_code)))
if throw: raise UnableToSelectBatchError
return batch_no

View File

@@ -6,10 +6,148 @@ import frappe
from frappe.exceptions import ValidationError
import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch",
"name": "_test Batch",
"item": "_Test Item"
}).save)
}).save)
def make_batch_item(self):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists('ITEM-BATCH-1'):
make_item('ITEM-BATCH-1', dict(has_batch_no = 1, create_new_batch = 1))
def test_purchase_receipt(self, batch_qty = 100):
'''Test automated batch creation from Purchase Receipt'''
self.make_batch_item()
receipt = frappe.get_doc(dict(
doctype = 'Purchase Receipt',
supplier = '_Test Supplier',
items = [
dict(
item_code = 'ITEM-BATCH-1',
qty = batch_qty,
rate = 10
)
]
)).insert()
receipt.submit()
self.assertTrue(receipt.items[0].batch_no)
self.assertEquals(get_batch_qty(receipt.items[0].batch_no,
receipt.items[0].warehouse), batch_qty)
return receipt
def test_stock_entry_incoming(self):
'''Test batch creation via Stock Entry (Production Order)'''
self.make_batch_item()
stock_entry = frappe.get_doc(dict(
doctype = 'Stock Entry',
purpose = 'Material Receipt',
company = '_Test Company',
items = [
dict(
item_code = 'ITEM-BATCH-1',
qty = 90,
t_warehouse = '_Test Warehouse - _TC',
cost_center = 'Main - _TC',
rate = 10
)
]
)).insert()
stock_entry.submit()
self.assertTrue(stock_entry.items[0].batch_no)
self.assertEquals(get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
def test_delivery_note(self):
'''Test automatic batch selection for outgoing items'''
batch_qty = 15
receipt = self.test_purchase_receipt(batch_qty)
delivery_note = frappe.get_doc(dict(
doctype = 'Delivery Note',
customer = '_Test Customer',
company = receipt.company,
items = [
dict(
item_code = 'ITEM-BATCH-1',
qty = batch_qty,
rate = 10,
warehouse = receipt.items[0].warehouse
)
]
)).insert()
delivery_note.submit()
# shipped with same batch
self.assertEquals(delivery_note.items[0].batch_no, receipt.items[0].batch_no)
# balance is 0
self.assertEquals(get_batch_qty(receipt.items[0].batch_no,
receipt.items[0].warehouse), 0)
def test_delivery_note_fail(self):
'''Test automatic batch selection for outgoing items'''
receipt = self.test_purchase_receipt(100)
delivery_note = frappe.get_doc(dict(
doctype = 'Delivery Note',
customer = '_Test Customer',
company = receipt.company,
items = [
dict(
item_code = 'ITEM-BATCH-1',
qty = 5000,
rate = 10,
warehouse = receipt.items[0].warehouse
)
]
))
self.assertRaises(UnableToSelectBatchError, delivery_note.insert)
def test_stock_entry_outgoing(self):
'''Test automatic batch selection for outgoing stock entry'''
batch_qty = 16
receipt = self.test_purchase_receipt(batch_qty)
stock_entry = frappe.get_doc(dict(
doctype = 'Stock Entry',
purpose = 'Material Issue',
company = receipt.company,
items = [
dict(
item_code = 'ITEM-BATCH-1',
qty = batch_qty,
s_warehouse = receipt.items[0].warehouse,
)
]
)).insert()
stock_entry.submit()
# assert same batch is selected
self.assertEqual(stock_entry.items[0].batch_no, receipt.items[0].batch_no)
# balance is 0
self.assertEquals(get_batch_qty(receipt.items[0].batch_no,
receipt.items[0].warehouse), 0)
def test_batch_split(self):
'''Test batch splitting'''
receipt = self.test_purchase_receipt()
from erpnext.stock.doctype.batch.batch import split_batch
new_batch = split_batch(receipt.items[0].batch_no, 'ITEM-BATCH-1', receipt.items[0].warehouse, 22)
self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78)
self.assertEquals(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)

View File

@@ -282,7 +282,7 @@
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -853,7 +853,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -883,7 +883,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -2514,7 +2514,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -2908,7 +2908,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
@@ -3288,7 +3288,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-04-10 12:03:29.645642",
"modified": "2017-04-18 18:41:53.065755",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -11,7 +11,7 @@ import frappe.defaults
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController
from frappe.desk.notifications import clear_doctype_notifications
from erpnext.stock.doctype.batch.batch import set_batch_nos
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -106,6 +106,9 @@ class DeliveryNote(SellingController):
self.validate_uom_is_integer("uom", "qty")
self.validate_with_previous_doc()
if self._action != 'submit':
set_batch_nos(self, 'warehouse', True)
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
make_packing_list(self)

View File

@@ -1644,7 +1644,7 @@
"width": "120px"
},
{
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -1894,7 +1894,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-04-05 23:26:03.390869",
"modified": "2017-04-19 11:53:42.656700",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",

View File

@@ -7,7 +7,7 @@
"beta": 0,
"creation": "2013-05-03 10:45:46",
"custom": 0,
"default_print_format": "Standard",
"default_print_format": "",
"description": "A Product or a Service that is bought, sold or kept in stock.",
"docstatus": 0,
"doctype": "DocType",
@@ -714,103 +714,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "eval:doc.is_stock_item",
"fieldname": "has_batch_no",
"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": "Has Batch No",
"length": 0,
"no_copy": 0,
"oldfieldname": "has_batch_no",
"oldfieldtype": "Select",
"options": "",
"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,
"default": "",
"depends_on": "eval:doc.is_stock_item",
"description": "Selecting \"Yes\" will give a unique identity to each entity of this item which can be viewed in the Serial No master.",
"fieldname": "has_serial_no",
"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": "Has Serial No",
"length": 0,
"no_copy": 0,
"oldfieldname": "has_serial_no",
"oldfieldtype": "Select",
"options": "",
"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,
"depends_on": "has_serial_no",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"fieldname": "serial_no_series",
"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": "Serial Number Series",
"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
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -1150,6 +1053,193 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no",
"columns": 0,
"depends_on": "is_stock_item",
"fieldname": "serial_nos_and_batches",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Serial Nos and Batches",
"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,
"default": "",
"depends_on": "eval:doc.is_stock_item",
"fieldname": "has_batch_no",
"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": "Has Batch No",
"length": 0,
"no_copy": 0,
"oldfieldname": "has_batch_no",
"oldfieldtype": "Select",
"options": "",
"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,
"depends_on": "has_batch_no",
"description": "",
"fieldname": "create_new_batch",
"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": "Automatically Create New Batch",
"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,
"fieldname": "column_break_37",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "eval:doc.is_stock_item",
"description": "",
"fieldname": "has_serial_no",
"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": "Has Serial No",
"length": 0,
"no_copy": 0,
"oldfieldname": "has_serial_no",
"oldfieldtype": "Select",
"options": "",
"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,
"depends_on": "has_serial_no",
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
"fieldname": "serial_no_series",
"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": "Serial Number Series",
"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
},
{
"allow_on_submit": 0,
"bold": 0,
@@ -2954,8 +3044,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
"modified": "2017-03-24 15:46:18.569291",
"modified_by": "d.ottenbreit@eso-electronic.de",
"modified": "2017-04-19 08:14:26.785497",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
"owner": "Administrator",

View File

@@ -24,6 +24,16 @@
"warehouse_reorder_qty": 20
}
],
"uoms": [
{
"uom": "_Test UOM",
"conversion_factor": 1.0
},
{
"uom": "_Test UOM 1",
"conversion_factor": 10.0
}
],
"stock_uom": "_Test UOM",
"show_in_website": 1,
"website_warehouse": "_Test Warehouse - _TC"

View File

@@ -352,7 +352,7 @@
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
@@ -2846,7 +2846,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-04-10 12:02:07.434102",
"modified": "2017-04-18 18:51:10.457027",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@@ -50,8 +50,11 @@ class PurchaseReceipt(BuyingController):
self.validate_posting_time()
super(PurchaseReceipt, self).validate()
if not self._action=="submit":
if self._action=="submit":
self.make_batches('warehouse')
else:
self.set_status()
self.po_required()
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
@@ -62,7 +65,6 @@ class PurchaseReceipt(BuyingController):
if getdate(self.posting_date) > getdate(nowdate()):
throw(_("Posting Date cannot be future date"))
def validate_with_previous_doc(self):
super(PurchaseReceipt, self).validate_with_previous_doc({
"Purchase Order": {

View File

@@ -9,6 +9,7 @@ from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate, formatdate
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError
from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor
from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
import json
@@ -48,6 +49,11 @@ class StockEntry(StockController):
self.validate_with_material_request()
self.validate_batch()
if self._action == 'submit':
self.make_batches('t_warehouse')
else:
set_batch_nos(self, 's_warehouse', True)
self.set_actual_qty()
self.calculate_rate_and_amount(update_finished_item_rate=False)
@@ -86,8 +92,10 @@ class StockEntry(StockController):
if item.item_code not in stock_items:
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
item_details = self.get_item_details(frappe._dict({"item_code": item.item_code,
"company": self.company, "project": self.project, "uom": item.uom}), for_update=True)
item_details = self.get_item_details(frappe._dict(
{"item_code": item.item_code, "company": self.company,
"project": self.project, "uom": item.uom, 's_warehouse': item.s_warehouse}),
for_update=True)
for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
"cost_center", "conversion_factor"):
@@ -462,7 +470,9 @@ class StockEntry(StockController):
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select stock_uom, description, image, item_name,
expense_account, buying_cost_center, item_group from `tabItem`
expense_account, buying_cost_center, item_group, has_serial_no,
has_batch_no
from `tabItem`
where name = %s
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""",
@@ -472,7 +482,7 @@ class StockEntry(StockController):
item = item[0]
ret = {
ret = frappe._dict({
'uom' : item.stock_uom,
'stock_uom' : item.stock_uom,
'description' : item.description,
@@ -486,8 +496,10 @@ class StockEntry(StockController):
'batch_no' : '',
'actual_qty' : 0,
'basic_rate' : 0,
'serial_no' : ''
}
'serial_no' : '',
'has_serial_no' : item.has_serial_no,
'has_batch_no' : item.has_batch_no
})
for d in [["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"]]:
company = frappe.db.get_value(d[0], ret.get(d[1]), "company")
@@ -507,6 +519,11 @@ class StockEntry(StockController):
stock_and_rate = args.get('warehouse') and get_warehouse_details(args) or {}
ret.update(stock_and_rate)
# automatically select batch for outgoing item
if (args.get('s_warehouse', None) and args.get('qty') and
ret.get('has_batch_no') and not args.get('batch_no')):
args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
return ret
def get_items(self):

View File

@@ -6,6 +6,20 @@ from frappe.utils import cint, flt
@frappe.whitelist()
def make_stock_entry(**args):
'''Helper function to make a Stock Entry
:item_code: Item to be moved
:qty: Qty to be moved
:from_warehouse: Optional
:to_warehouse: Optional
:rate: Optional
:serial_no: Optional
:batch_no: Optional
:posting_date: Optional
:posting_time: Optional
:do_not_save: Optional flag
:do_not_submit: Optional flag
'''
s = frappe.new_doc("Stock Entry")
args = frappe._dict(args)
@@ -71,6 +85,7 @@ def make_stock_entry(**args):
"basic_rate": args.rate or args.basic_rate,
"conversion_factor": 1.0,
"serial_no": args.serial_no,
'batch_no': args.batch_no,
'cost_center': args.cost_center,
'expense_account': args.expense_account
})

View File

@@ -58,7 +58,7 @@ class StockLedgerEntry(Document):
def validate_item(self):
item_det = frappe.db.sql("""select name, has_batch_no, docstatus,
is_stock_item, has_variants, stock_uom
is_stock_item, has_variants, stock_uom, create_new_batch
from tabItem where name=%s""", self.item_code, as_dict=True)
if not item_det:
@@ -75,7 +75,7 @@ class StockLedgerEntry(Document):
if not self.batch_no:
frappe.throw(_("Batch number is mandatory for Item {0}").format(self.item_code))
elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
elif item_det.has_batch_no ==0 and self.batch_no:
frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
@@ -116,7 +116,7 @@ class StockLedgerEntry(Document):
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
else:
from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.posting_date, self.fiscal_year, self.company,
validate_fiscal_year(self.posting_date, self.fiscal_year, self.company,
self.meta.get_label("posting_date"), self)
def block_transactions_against_group_warehouse(self):

View File

@@ -152,7 +152,6 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
setup: function() {
var me = this;
this.frm.get_docfield("items").allow_bulk_edit = 1;
this.setup_posting_date_time_check();

View File

@@ -10,9 +10,11 @@
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -43,6 +45,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -72,6 +75,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -101,6 +105,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -128,6 +133,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -159,6 +165,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -189,6 +196,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -218,6 +226,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -246,6 +255,7 @@
"unique": 0
},
{
"allow_bulk_edit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -276,6 +286,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -304,6 +315,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -334,6 +346,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -364,6 +377,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -392,6 +406,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -420,6 +435,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -461,7 +477,7 @@
"istable": 0,
"max_attachments": 1,
"menu_index": 0,
"modified": "2017-03-15 15:03:08.579943",
"modified": "2017-04-21 16:58:53.475787",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",

View File

@@ -143,13 +143,11 @@ class StockReconciliation(StockController):
# item should not be serialized
if item.has_serial_no == 1:
raise frappe.ValidationError, _("Serialized Item {0} cannot be updated \
using Stock Reconciliation").format(item_code)
raise frappe.ValidationError, _("Serialized Item {0} cannot be updated using Stock Reconciliation, please use Stock Entry").format(item_code)
# item managed batch-wise not allowed
if item.has_batch_no == 1:
raise frappe.ValidationError, _("Item: {0} managed batch-wise, can not be reconciled using \
Stock Reconciliation, instead use Stock Entry").format(item_code)
raise frappe.ValidationError, _("Batched Item {0} cannot be updated using Stock Reconciliation, instead use Stock Entry").format(item_code)
# docstatus should be < 2
validate_cancelled_item(item_code, item.docstatus, verbose=0)

View File

@@ -9,6 +9,7 @@ import json
from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item, set_transaction_type
from erpnext.setup.utils import get_exchange_rate
from frappe.model.meta import get_field_precision
from erpnext.stock.doctype.batch.batch import get_batch_no
@frappe.whitelist()
def get_item_details(args):
@@ -74,7 +75,12 @@ def get_item_details(args):
out.update(get_pricing_rule_for_item(args))
if args.get("doctype") in ("Sales Invoice", "Delivery Note") and out.stock_qty > 0:
out.serial_no = get_serial_no(out)
if out.has_serial_no:
out.serial_no = get_serial_no(out)
if out.has_batch_no:
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
if args.transaction_date and item.lead_time_days:
out.schedule_date = out.lead_time_date = add_days(args.transaction_date,
@@ -154,14 +160,15 @@ def get_basic_details(args, item):
"income_account": get_default_income_account(args, item),
"expense_account": get_default_expense_account(args, item),
"cost_center": get_default_cost_center(args, item),
'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no,
"batch_no": None,
"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in
item.get("taxes")))),
"uom": item.stock_uom,
"min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
"conversion_factor": 1.0,
"qty": args.qty or 1.0,
"stock_qty": 1.0,
"stock_qty": args.qty or 1.0,
"price_list_rate": 0.0,
"base_price_list_rate": 0.0,
"rate": 0.0,
@@ -172,14 +179,27 @@ def get_basic_details(args, item):
"net_amount": 0.0,
"discount_percentage": 0.0,
"supplier": item.default_supplier,
"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
"is_fixed_asset": item.is_fixed_asset
})
# calculate conversion factor
if item.stock_uom == args.uom:
out.conversion_factor = 1.0
else:
out.conversion_factor = args.conversion_factor or \
get_conversion_factor(item.item_code, args.uom).get("conversion_factor") or 1.0
args.conversion_factor = out.conversion_factor
out.stock_qty = out.qty * out.conversion_factor
# if default specified in item is for another company, fetch from company
for d in [["Account", "income_account", "default_income_account"],
for d in [
["Account", "income_account", "default_income_account"],
["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "warehouse", ""]]:
["Cost Center", "cost_center", "cost_center"],
["Warehouse", "warehouse", ""]]:
company = frappe.db.get_value(d[0], out.get(d[1]), "company")
if not out[d[1]] or (company and args.company != company):
out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None
@@ -227,6 +247,8 @@ def get_price_list_rate(args, item_doc, out):
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate)
out.price_list_rate = flt(out.price_list_rate * (args.conversion_factor or 1.0))
if not out.price_list_rate and args.transaction_type=="buying":
from erpnext.stock.doctype.item.item import get_last_purchase_details
out.update(get_last_purchase_details(item_doc.name,
@@ -347,15 +369,6 @@ def get_serial_nos_by_fifo(args):
"qty": abs(cint(args.stock_qty))
}))
def get_actual_batch_qty(batch_no,warehouse,item_code):
actual_batch_qty = 0
if batch_no:
actual_batch_qty = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
where warehouse=%s and item_code=%s and batch_no=%s""",
(warehouse, item_code, batch_no))[0][0])
return actual_batch_qty
@frappe.whitelist()
def get_conversion_factor(item_code, uom):
variant_of = frappe.db.get_value("Item", item_code, "variant_of")
@@ -391,10 +404,10 @@ def get_bin_details_and_serial_nos(item_code, warehouse, stock_qty=None, serial_
return bin_details_and_serial_nos
@frappe.whitelist()
def get_batch_qty(batch_no,warehouse,item_code):
actual_batch_qty = get_actual_batch_qty(batch_no,warehouse,item_code)
def get_batch_qty(batch_no, warehouse, item_code):
from frappe.stock.doctype.batch import batch
if batch_no:
return {'actual_batch_qty': actual_batch_qty}
return {'actual_batch_qty': batch.get_batch_qty(batch_no, warehouse)}
@frappe.whitelist()
def apply_price_list(args, as_doc=False):
@@ -532,6 +545,9 @@ def get_serial_no(args):
args = json.loads(args)
args = frappe._dict(args)
if args.get('doctype') == 'Sales Invoice' and not args.get('update_stock'):
return ""
if args.get('warehouse') and args.get('stock_qty') and args.get('item_code'):
if frappe.get_value('Item', {'item_code': args.item_code}, "has_serial_no") == 1:

View File

@@ -37,6 +37,12 @@ frappe.query_reports["Stock Ledger"] = {
"fieldtype": "Link",
"options": "Item"
},
{
"fieldname":"batch_no",
"label": __("Batch No"),
"fieldtype": "Link",
"options": "Batch"
},
{
"fieldname":"brand",
"label": __("Brand"),

View File

@@ -10,9 +10,9 @@ def execute(filters=None):
sl_entries = get_stock_ledger_entries(filters)
item_details = get_item_details(filters)
opening_row = get_opening_balance(filters, columns)
data = []
if opening_row:
data.append(opening_row)
@@ -25,7 +25,7 @@ def execute(filters=None):
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
sle.batch_no, sle.serial_no, sle.company])
return columns, data
def get_columns():
@@ -76,6 +76,8 @@ def get_sle_conditions(filters):
conditions.append(get_warehouse_condition(filters.get("warehouse")))
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
if filters.get("batch_no"):
conditions.append("batch_no=%(batch_no)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""
@@ -90,14 +92,14 @@ def get_opening_balance(filters, columns):
"posting_date": filters.from_date,
"posting_time": "00:00:00"
})
row = [""]*len(columns)
row[1] = _("'Opening'")
for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
row[i] = last_entry.get(v, 0)
return row
def get_warehouse_condition(warehouse):
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
if warehouse_details:

View File

@@ -20,10 +20,9 @@ def get_product_list(search=None, start=0, limit=12):
query = """select name, item_name, item_code, route, website_image, thumbnail, item_group,
description, web_long_description as website_description
from `tabItem`
where show_in_website = 1
where (show_in_website = 1 or show_variant_in_website = 1)
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
and (variant_of is null or variant_of = '')"""
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)"""
# search term condition
if search:

View File

@@ -143,6 +143,7 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
for d in doc.get_all_children(parenttype=child_dt):
if d.get(uom_field) in integer_uoms:
for f in qty_fields:
if d.get(f):
if cint(d.get(f))!=d.get(f):
frappe.throw(_("Quantity cannot be a fraction in row {0}").format(d.idx), UOMMustBeIntegerError)
qty = d.get(f)
if qty:
if abs(int(qty) - float(qty)) > 0.0000001:
frappe.throw(_("Quantity ({0}) cannot be a fraction in row {1}").format(qty, d.idx), UOMMustBeIntegerError)