Compare commits

..

130 Commits

Author SHA1 Message Date
mbauskar
fbbb8695f3 Merge branch 'hotfix' 2017-10-09 15:18:52 +05:30
mbauskar
f9577652a0 bumped to version 9.1.2 2017-10-09 15:48:51 +06:00
tundebabzy
2622d370c6 deal zero division possibility (#11098) 2017-10-09 12:44:24 +05:30
rohitwaghchaure
e181dd4c24 [patch] To add healthcare domain (#11105) 2017-10-09 12:43:36 +05:30
tundebabzy
7f9d75521e Confirmation desired before cancelling appointment (#10996) (#11106)
* shows confirm dialog when user clicks cancel

* indentation and frm as per review
2017-10-09 12:43:18 +05:30
Makarand Bauskar
eaf0abedd4 [hotfix] ignore if student email address field value is None (#11116) 2017-10-09 12:42:18 +05:30
rohitwaghchaure
6f5853b97a Merge pull request #11092 from rohitwaghchaure/pos_total_issue
Show total instead of net total
2017-10-06 11:17:24 +05:30
Rohit Waghchaure
62ce218fc2 Show total instead of net total 2017-10-06 11:16:46 +05:30
rohitwaghchaure
1b0f3ec666 Merge pull request #11089 from rohitwaghchaure/offline_pos_v9
[Fix] Discount field not displaying in offline POS
2017-10-06 09:51:30 +05:30
Rohit Waghchaure
beeba8b37a [Fix] Discount field not displaying in offline POS 2017-10-06 09:50:43 +05:30
rohitwaghchaure
d7636b2b19 Merge pull request #11083 from rohitwaghchaure/pos_print_format_issue
[fix] Online print format in pos profile field not displaying
2017-10-06 09:44:27 +05:30
Rohit Waghchaure
b870d0081b [fix] Online print format in pos profile field not displaying 2017-10-06 09:31:40 +05:30
rohitwaghchaure
e0dfd1608e [Fix] Auto add item in the cart if sinfle items found in the serach (#11072) 2017-10-05 18:30:51 +05:30
rohitwaghchaure
607b5d4985 [Fix] Negative qty issue in POS (#11070)
* [Fix] Negative qty issue in POS

* Update point_of_sale.js
2017-10-05 15:57:58 +05:30
Makarand Bauskar
f23788bb7d [hotfix] Show Make button only for Email communication (#10876) 2017-10-05 14:19:45 +05:30
mbauskar
671c6610de Merge branch 'hotfix' 2017-10-05 13:36:49 +05:30
mbauskar
8c3d19e2ab bumped to version 9.1.1 2017-10-05 14:06:49 +06:00
rohitwaghchaure
4a5ac7cea6 Merge pull request #11066 from rohitwaghchaure/pos_loading_issue
[Fix] Rate not fetched in the POS, User trying to add an item in the cart before loading of frm
2017-10-05 13:34:22 +05:30
Rohit Waghchaure
f7a856b913 [Fix] Rate not fetched in the POS, User trying to add an item in the cart before loading of frm 2017-10-05 13:31:29 +05:30
Nabin Hait
7d4fd35aa3 Add POS Settings link in Accounts module (#11060) 2017-10-05 12:20:33 +05:30
rohitwaghchaure
e87a076f1d Update link in the pos for offline and online mode from pos settings (#11061) 2017-10-05 12:20:20 +05:30
Nabin Hait
ff689a658f Merge branch 'staging' 2017-10-04 18:21:35 +05:30
Nabin Hait
b290e3a4e7 bumped to version 9.1.0 2017-10-04 18:51:34 +06:00
Nabin Hait
77f0822abe Merge branch 'develop' into staging 2017-10-04 18:11:16 +05:30
Nabin Hait
f83bc51e81 Merge branch 'master' into staging 2017-10-04 18:05:21 +05:30
Nabin Hait
fc712aea32 Merge branch 'master' into develop 2017-10-04 18:05:20 +05:30
Nabin Hait
7105c4b76c Merge branch 'hotfix' 2017-10-04 18:05:19 +05:30
Nabin Hait
54c725dcd1 bumped to version 9.0.9 2017-10-04 18:35:19 +06:00
rohitwaghchaure
1e2c554e61 [Fix] Stock entry multi uom batch validation issue (#11049) 2017-10-04 17:52:49 +05:30
tundebabzy
65dfd09947 fix wrong variable name (#11050) 2017-10-04 17:51:44 +05:30
Javier Wong
61287e3c53 [fix] Change Sample Item Error Message to Zero Valuation Rate (#10935)
Change Sample Item Error Message to Zero Valuation Rate
2017-10-04 16:01:34 +05:30
Makarand Bauskar
1b67d71139 [minor] add the Lead in email account -> append_to field (#10983) 2017-10-04 15:55:56 +05:30
Doridel Cahanap
32456b0f14 [minor edits] BOM Stock Report (#11012)
* HTML for BOM Stock Report to show filters in PDF

* Added BOM Stock Report in Manufacturing Config under Report
2017-10-04 15:54:44 +05:30
Nabin Hait
3d0d4b2157 update subscription period only if relevant date field exists (#11046) 2017-10-04 15:51:55 +05:30
Nabin Hait
7e5a9f5c0e Update new bom rate while replacing BOM (#11045) 2017-10-04 15:51:34 +05:30
KanchanChauhan
d3e21fff66 [Minor] Employee name as standard filter (#11043) 2017-10-04 15:20:34 +05:30
Nabin Hait
5171956646 Merge branch 'master' into staging 2017-10-04 14:52:33 +05:30
Nabin Hait
0cc93538ed Merge branch 'hotfix' 2017-10-04 14:52:32 +05:30
Nabin Hait
b779644493 Merge branch 'master' into develop 2017-10-04 14:52:32 +05:30
Nabin Hait
c83e793ce8 bumped to version 9.0.8 2017-10-04 15:22:31 +06:00
Revant Nandgaonkar
4d68e03a97 [Fix] Job Opening Web Form breadcrumb (#11029) 2017-10-04 14:35:21 +05:30
Makarand Bauskar
eaec4695f7 [hotfix] filter doctype if the doctype has subscription field (#11038) 2017-10-04 14:34:45 +05:30
rohitwaghchaure
5049edb494 [Fix] Discount and serial no search issue (#11040) 2017-10-04 14:33:12 +05:30
Makarand Bauskar
d17bea0a31 [hotfix] check minimum actual start time and actual end time before updating PO Operations (#11041)
* [hotfix] check minimum actual start time and actual end time before updating PO Operations

* Update production_order.py
2017-10-04 14:30:12 +05:30
Saurabh
444bfff1ff [fix] resolved merge conflicts 2017-10-03 18:03:18 +05:30
Saurabh
ee4a2dd26f [fix] resolved merge conflicts 2017-10-03 18:02:09 +05:30
Saurabh
f6580268e6 Merge branch 'hotfix' 2017-10-03 18:00:32 +05:30
Saurabh
e3a468ed1b bumped to version 9.0.7 2017-10-03 18:30:31 +06:00
rohitwaghchaure
9e6f2a49e8 Merge pull request #11033 from rohitwaghchaure/subscription_next_data_fix
[Fix] Subscription next schedule date, cancel issue, added from and to date
2017-10-03 17:48:53 +05:30
Rohit Waghchaure
445e8a2e57 [Fix] Subscription end date, cancel issue, added from and to date 2017-10-03 17:45:11 +05:30
Nabin Hait
7b6eaee05b Fixes to handle async events (#11018)
* Fixes to handle async events

* transaction.js code cleanup

* Don't map taxes and charges while making PO from SO for drop-ship

* Removed print
2017-10-03 01:09:46 +05:30
rohitwaghchaure
c26e3f1569 Merge pull request #11024 from rohitwaghchaure/payment_issue_in_pos
[Fix] Old invoice payment amount showing in the payment modal while making payment
2017-10-02 22:29:49 +05:30
Rohit Waghchaure
c4e52e5f95 [Fix] Old invoice payment amount showing in the payment modal while making payment 2017-10-02 21:20:54 +05:30
Nabin Hait
7eba1a35d3 Controller init args fix (#11015)
* Controller init args fix

* cleanup useless code
2017-10-02 15:59:27 +05:30
Nabin Hait
1f10d693e9 Don't set currency as company currency if default currency is different (#11011) 2017-10-02 13:20:51 +05:30
Nabin Hait
53e8989699 Merge branch 'master' into staging 2017-10-02 12:28:27 +05:30
Nabin Hait
8919669ac2 Merge branch 'master' into develop 2017-10-02 12:28:27 +05:30
Nabin Hait
d977333a99 Merge branch 'hotfix' 2017-10-02 12:28:26 +05:30
Nabin Hait
3a2834c7ad bumped to version 9.0.6 2017-10-02 12:58:26 +06:00
rohitwaghchaure
88491715e0 [Fix] select batch not displaying in the modal, duplicate invoice making after sync (#11005) 2017-10-02 12:13:36 +05:30
Nabin Hait
0bdf1e5ef1 Merge branch 'master' into staging 2017-10-02 12:09:01 +05:30
Nabin Hait
f059e7be35 Merge branch 'master' into develop 2017-10-02 12:09:00 +05:30
Nabin Hait
00a48ad4e5 Merge branch 'hotfix' 2017-10-02 12:09:00 +05:30
Nabin Hait
def308a433 bumped to version 9.0.5 2017-10-02 12:38:59 +06:00
Makarand Bauskar
cb38e599e5 [minor] don't create lead if customer contact is already created against contact_email (#10976)
* [minor] don't create lead if customer contact is already created against contact_email

* [tests] added tests cases for opportunity to check if lead is required or not
2017-10-02 11:40:43 +05:30
Nabin Hait
9b98d7fa14 Gst doctype roles (#10984)
* Removed default permission from GST doctypes

* default permission from GST doctypes
2017-10-02 11:38:40 +05:30
Makarand Bauskar
b1bf502119 [hotfix] validate company name on 'The Brand' Slide instead of 'Your Organization' (#11007) 2017-10-02 11:38:03 +05:30
Nabin Hait
cb48404bd2 Revert "Fixes for uom in get_item_details (#10986)" (#11009)
This reverts commit 5b58e489a8.
2017-10-02 11:37:18 +05:30
Nabin Hait
5b58e489a8 Fixes for uom in get_item_details (#10986) 2017-10-02 11:36:10 +05:30
Rushabh Mehta
4b99fe15cc [version] 9.x.x-develop 2017-09-30 11:41:26 +05:30
Rushabh Mehta
b9fe14631d [fix] healthcare setup 2017-09-30 10:46:56 +05:30
Nabin Hait
01b96a0e31 Fixes for uom in get_item_details 2017-09-29 18:57:41 +05:30
Nabin Hait
79a1d2a3b0 Fixes for uom in get_item_details 2017-09-29 18:15:40 +05:30
Nabin Hait
6488645d42 Fixed ui tests for production order 2017-09-29 16:38:28 +05:30
Rushabh Mehta
b46900a4cb Merge branch 'master' into staging 2017-09-29 16:22:02 +05:30
Rushabh Mehta
912ae24ca2 Merge branch 'master' into develop 2017-09-29 16:22:02 +05:30
Rushabh Mehta
e292c83114 Merge branch 'hotfix' 2017-09-29 16:22:01 +05:30
Rushabh Mehta
e1a4b3e4bc bumped to version 9.0.4 2017-09-29 16:52:01 +06:00
Rushabh Mehta
d3a48a83fd [fix] bom.py, dont use keyword 2017-09-29 15:50:17 +05:30
Rushabh Mehta
80d24f83f8 [fix] item variant description 2017-09-29 15:39:03 +05:30
Pawan Mehta
5d8fd477bd [fix] #10840 (#10844) 2017-09-29 15:23:54 +05:30
Javier Wong
bf37995745 [Enhancement] Sales and Purchase Default UOM (#10929)
Option for specifying an optional default Sales and Purchase UOM at the item master level.
2017-09-29 15:23:08 +05:30
tundebabzy
5510d0751d correctly set frm company currency (#10944) 2017-09-29 15:21:59 +05:30
Shreya Shah
b79c4a9ff6 Getting last purchase price of an item (#10897)
* Added a column last purchase rate

* Removed button last purchase rate

* Get last purchase rate on adding an item

* Added test case for last purchase rate

* Replaced cur_frm with frm

* Update purchase_order.js
2017-09-29 15:20:48 +05:30
Francisco Roldán
1b61dfd9ea translated (#10886) 2017-09-29 15:19:12 +05:30
Shridhar Patil
3f7d96ecba Unassign from todo. (#10896)
Unassign from todo when the status is changed to Closed or Cancelled
2017-09-29 15:18:43 +05:30
Faris Ansari
367b90e3ae Add Data Import Tool desktop icon (#10916)
fixes #8332
2017-09-29 15:17:48 +05:30
Nabin Hait
945f502748 Fixes for updating item variant from template (#10975)
* Fixes for updating item variant from template

* More fixes for test cases
2017-09-29 15:11:50 +05:30
Makarand Bauskar
9c339145b2 [Enhance] Custom notification messages for subscription documents (#10970)
* [minor] configurable subscription email message and subject for notification

* [minor] added description for subject field
2017-09-29 15:02:51 +05:30
Rushabh Mehta
3c14c5a16c [fix] tax_rule.py args 2017-09-29 13:21:22 +05:30
Nabin Hait
bdb4c542e7 default permission from GST doctypes 2017-09-29 10:39:32 +05:30
Nabin Hait
6d61a45f42 Removed default permission from GST doctypes 2017-09-29 10:35:21 +05:30
Nabin Hait
c314485d55 Fixes based on test case 2017-09-28 18:55:49 +05:30
Nabin Hait
3b04cfc812 minor fix 2017-09-28 18:55:49 +05:30
mbauskar
79ba422273 Merge branch 'develop' into staging 2017-09-28 16:32:33 +05:30
rohitwaghchaure
0dc3c1b114 Merge pull request #10953 from rohitwaghchaure/has_batch_item_in_demo
[minor] Added batch item in the demo data
2017-09-28 16:07:01 +05:30
schilgod
0f6fff6f0c item price track_changes default to 1 (#10958) 2017-09-28 15:28:59 +05:30
Rushabh Mehta
2b87d100fa [fix] https://github.com/frappe/erpnext/issues/10956 2017-09-28 15:21:36 +05:30
Rohit Waghchaure
c4ee77a3cc [minor] Added batch item in the demo data 2017-09-28 15:19:26 +05:30
Nabin Hait
c9f9e5235b Fixed merge conflict 2017-09-28 12:31:33 +05:30
Nabin Hait
edd4fd4692 Merge branch 'hotfix' 2017-09-28 12:30:48 +05:30
Nabin Hait
ca916a73de bumped to version 9.0.3 2017-09-28 13:00:48 +06:00
Makarand Bauskar
53e19075d1 [hotfix] fixes and refactored Quoted Item Comparison report (#10954) 2017-09-28 12:30:04 +05:30
Rushabh Mehta
a0ba5594f9 [fix] production_order.py via error report 2017-09-28 11:26:24 +05:30
rohitwaghchaure
16645803f9 [fix] Added billing address gstin number in the sales invoice and delivery note as well in the sales gst reports (#10872) 2017-09-28 11:05:03 +05:30
rohitwaghchaure
94799a8b93 [fix] ‘NoneType’ object is not iterable onload of POS (#10941) 2017-09-28 10:56:27 +05:30
Nabin Hait
fce14fdcf0 Fixed merge conflict 2017-09-27 17:21:21 +05:30
Nabin Hait
76e1ca35ad Merge branch 'master' into develop 2017-09-27 13:05:21 +05:30
Prateeksha Singh
946e182564 install company fixtures while creating company (#10904)
* install company fixtures while creating company

* [fix] remove from setup_complete
2017-09-27 11:44:39 +05:30
Frappe PR Bot
9aff73d156 [Translation] Updated Translations (#10901) 2017-09-26 17:18:42 +05:30
Nabin Hait
51a07d19c8 Merge branch 'master' into develop 2017-09-26 15:59:15 +05:30
Nabin Hait
afe9eabd3c Merge branch 'master' into develop 2017-09-25 16:36:47 +05:30
Nabin Hait
cb5e1e550f Merge branch 'master' into develop 2017-09-25 16:03:31 +05:30
Nabin Hait
3b61552836 Revert "Advance against expense claim (#10632)" (#10877)
This reverts commit cdd6ded790.
2017-09-25 11:27:39 +05:30
Nabin Hait
4ebac3380d reload print style 2017-09-23 15:50:39 +05:30
Nabin Hait
cdd6ded790 Advance against expense claim (#10632)
* Adds Whitelist Method for Advance Entry

* Adds changes required for managing Advance Payments in Expense Claim including new fields and documentation. Also resolved merge conflict by using the more recent modified date

* Adds changes for managing advance payments using Default Account and Party

* Removed console.log from the JS file

* Advance Payment Patch - Fixed Codacy errors

* Removed stray file

* Fixed conflicts due to changes in upstream

* Fixed Codacy errors

* Fixed Codacy errors

* Fixed Codacy errors

* Fixed Codacy errors

* Fixed Codacy errors

* Fixed pending Codacy error

* Updated JS code by removing cur_frm which is soon to be deprecated

* Advance against Expense Claim: cleanup and fixes

* Test case fixed
2017-09-21 18:03:45 +05:30
Nabin Hait
fb142f5283 Merge branch 'AravindPranera-develop' into develop 2017-09-21 15:52:06 +05:30
Nabin Hait
6d78f7b862 Fixed indentation 2017-09-21 15:51:42 +05:30
Nabin Hait
5478a7fa67 Merge branch 'develop' of https://github.com/AravindPranera/erpnext-1 into AravindPranera-develop 2017-09-21 15:48:18 +05:30
Nabin Hait
2851dfad99 Set fields in Item Variant Settings which should be copied from template to variant 2017-09-21 15:41:57 +05:30
Rohit Waghchaure
5b05335e89 added patch and set default fields after completion of setup wizard 2017-09-21 15:41:57 +05:30
Rohit Waghchaure
0e28fccb34 [Enahance] Update variants fields defined in the Item Varianst Settings, if template updated 2017-09-21 15:41:57 +05:30
tundebabzy
ab5b03011d Enhance Currency Exchange Management (#10482)
* add new settings in Accouts Settings

* patch for new settings

* refactor `get_exchange_rate`

* adds validation

* tests validation

* disables conversion rate field if stale rates not allowed

* more test cases

more test case...

test `get_exchange_rate` behaviour with stale not allowed in sett..

fix currency exchange test case

do housekeeping after running accounts settings test

* clean up

* documentation

* make use of correct api url

* Fix tests failing due to wrong exchange rate from fixer.io

* remove mandatory constraint from `allow_stale`

* added info to documentation
2017-09-21 14:50:39 +05:30
AravindPranera
cb6774e373 Files added with all functions 2017-09-18 17:37:37 +05:30
AravindPranera
1276893550 Files added with all functions 2017-09-18 17:30:20 +05:30
AravindPranera
27bbb561d2 removed whitespace 2017-09-15 11:04:14 +05:30
AravindPranera
2c1f44ecfa Trailing space removed in line 275 2017-09-15 10:57:23 +05:30
AravindPranera
c73383c34c Trailing whitespace in Line 207 and 276 2017-09-15 10:34:21 +05:30
AravindPranera
764bb30d2d Fetching Opportunity items into Request for Quotation 2017-08-24 18:34:45 +05:30
AravindPranera
6544a85f1e Opportunity Items fetching into Request for quotation 2017-08-24 18:32:40 +05:30
186 changed files with 49369 additions and 28792 deletions

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from erpnext.hooks import regional_overrides
__version__ = '9.0.2'
__version__ = '9.1.2'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -286,6 +286,99 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "currency_exchange_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Currency Exchange Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "allow_stale",
"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": "Allow Stale Exchange Rates",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"depends_on": "eval:doc.allow_stale==0",
"fieldname": "stale_days",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Stale Days",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
@@ -299,7 +392,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-16 17:39:50.614522",
"modified": "2017-09-05 10:10:03.117505",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -5,10 +5,20 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint, comma_and
from frappe.utils import cint
from frappe.model.document import Document
class AccountsSettings(Document):
def on_update(self):
pass
pass
def validate(self):
self.validate_stale_days()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
"Stale Days should start from 1.", title='Error', indicator='red',
raise_exception=1)

View File

@@ -0,0 +1,35 @@
QUnit.module('accounts');
QUnit.test("test: Accounts Settings doesn't allow negatives", function (assert) {
let done = assert.async();
assert.expect(2);
frappe.run_serially([
() => frappe.set_route('Form', 'Accounts Settings', 'Accounts Settings'),
() => frappe.timeout(2),
() => unchecked_if_checked(cur_frm, 'Allow Stale Exchange Rates', frappe.click_check),
() => cur_frm.set_value('stale_days', 0),
() => frappe.click_button('Save'),
() => frappe.timeout(2),
() => {
assert.ok(cur_dialog);
},
() => frappe.click_button('Close'),
() => cur_frm.set_value('stale_days', -1),
() => frappe.click_button('Save'),
() => frappe.timeout(2),
() => {
assert.ok(cur_dialog);
},
() => frappe.click_button('Close'),
() => done()
]);
});
const unchecked_if_checked = function(frm, field_name, fn){
if (frm.doc.allow_stale) {
return fn(field_name);
}
};

View File

@@ -0,0 +1,22 @@
import unittest
import frappe
class TestAccountsSettings(unittest.TestCase):
def tearDown(self):
# Just in case `save` method succeeds, we need to take things back to default so that other tests
# don't break
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 1
cur_settings.save()
def test_stale_days(self):
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
cur_settings.allow_stale = 0
cur_settings.stale_days = 0
self.assertRaises(frappe.ValidationError, cur_settings.save)
cur_settings.stale_days = -1
self.assertRaises(frappe.ValidationError, cur_settings.save)

View File

@@ -12,8 +12,8 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
from erpnext.hr.doctype.employee_loan.employee_loan import update_disbursement_status
class JournalEntry(AccountsController):
def __init__(self, arg1, arg2=None):
super(JournalEntry, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(*args, **kwargs)
def get_feed(self):
return self.voucher_type

View File

@@ -403,6 +403,13 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_difference_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale",
function(d){
frm.set_df_property("source_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
}
);
},
target_exchange_rate: function(frm) {
@@ -421,6 +428,13 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_difference_amount(frm);
}
frm.set_paid_amount_based_on_received_amount = false;
// Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale",
function(d){
frm.set_df_property("target_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
}
);
},
paid_amount: function(frm) {

View File

@@ -37,10 +37,10 @@ frappe.ui.form.on('POS Profile', {
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
});
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
is_online = r && cint(r.is_online)
frm.toggle_display('offline_pos_section', !is_online);
frm.toggle_display('print_format_for_online', is_online);
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'use_pos_in_offline_mode', (r) => {
is_offline = r && cint(r.use_pos_in_offline_mode)
frm.toggle_display('offline_pos_section', is_offline);
frm.toggle_display('print_format_for_online', !is_offline);
});
},

View File

@@ -3,7 +3,17 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class POSSettings(Document):
pass
def validate(self):
self.set_link_for_pos()
def set_link_for_pos(self):
link = 'pos' if self.use_pos_in_offline_mode else 'point-of-sale'
desktop_icon = frappe.db.get_value('Desktop Icon',
{'standard': 1, 'module_name': 'POS'}, 'name')
if desktop_icon:
frappe.db.set_value('Desktop Icon', desktop_icon, 'link', link)

View File

@@ -22,8 +22,8 @@ form_grid_templates = {
}
class PurchaseInvoice(BuyingController):
def __init__(self, arg1, arg2=None):
super(PurchaseInvoice, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Purchase Invoice Item',
'target_dt': 'Purchase Order Item',

View File

@@ -88,7 +88,7 @@ def update_pos_profile_data(doc, pos_profile, company_data):
doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head
doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0
doc.apply_discount_on = pos_profile.get('apply_discount_on') if pos_profile.get('apply_discount') else ''
doc.apply_discount_on = pos_profile.get('apply_discount_on') or ''
doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group')
doc.territory = pos_profile.get('territory') or get_root('Territory')
doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
@@ -417,6 +417,7 @@ def make_contact(args,customer):
'link_doctype': 'Customer',
'link_name': customer
})
doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True)
def make_address(args, customer):
@@ -441,6 +442,7 @@ def make_address(args, customer):
address.is_primary_address = 1
address.is_shipping_address = 1
address.update(args)
address.flags.ignore_mandatory = True
address.save(ignore_permissions = True)
def make_email_queue(email_queue):

View File

@@ -520,6 +520,24 @@ frappe.ui.form.on('Sales Invoice', {
};
});
},
//When multiple companies are set up. in case company name is changed set default company address
company:function(frm){
if (frm.doc.company)
{
frappe.call({
method:"frappe.contacts.doctype.address.address.get_default_address",
args:{ doctype:'Company',name:frm.doc.company},
callback: function(r){
if (r.message){
frm.set_value("company_address",r.message)
}
else {
frm.set_value("company_address","")
}
}
})
}
},
project: function(frm){
frm.call({

View File

@@ -27,8 +27,8 @@ form_grid_templates = {
}
class SalesInvoice(SellingController):
def __init__(self, arg1, arg2=None):
super(SalesInvoice, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(SalesInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Sales Invoice Item',
'target_field': 'billed_amt',

View File

@@ -1084,7 +1084,7 @@ class TestSalesInvoice(unittest.TestCase):
si.items[0].price_list_rate = price_list_rate
si.items[0].margin_type = 'Percentage'
si.items[0].margin_rate_or_amount = 25
si.insert()
si.save()
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
def test_outstanding_amount_after_advance_jv_cancelation(self):

View File

@@ -3,6 +3,12 @@
frappe.ui.form.on('Subscription', {
setup: function(frm) {
frm.fields_dict['reference_doctype'].get_query = function(doc) {
return {
query: "erpnext.accounts.doctype.subscription.subscription.subscription_doctype_query"
};
};
frm.fields_dict['reference_document'].get_query = function() {
return {
filters: {

View File

@@ -135,66 +135,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on Creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -286,12 +226,12 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_schedule_date",
"fieldtype": "Date",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -299,14 +239,44 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Schedule Date",
"label": "Submit on Creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -320,7 +290,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "frequency_detail",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -329,7 +299,95 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"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": 0,
"in_standard_filter": 0,
"label": "From Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_13",
"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,
@@ -375,35 +433,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
@@ -437,10 +466,40 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_schedule_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Schedule Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "notification",
"fieldtype": "Section Break",
"hidden": 0,
@@ -495,6 +554,38 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.notify_by_email",
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>New {{ doc.doctype }} #{{ doc.name }}</code></pre></div>",
"fieldname": "subject",
"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": "Subject",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -593,6 +684,69 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"depends_on": "eval:doc.notify_by_email",
"fieldname": "section_break_20",
"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": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
"fieldname": "message",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"depends_on": "eval: !doc.__islocal",
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"hidden": 0,
@@ -690,7 +844,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-14 12:09:38.471458",
"modified": "2017-10-03 17:20:26.919630",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
@@ -700,7 +854,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -720,7 +874,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -740,7 +894,7 @@
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,

View File

@@ -7,9 +7,10 @@ import frappe
import calendar
from frappe import _
from frappe.desk.form import assign_to
from frappe.utils.jinja import validate_template
from dateutil.relativedelta import relativedelta
from frappe.utils.user import get_system_managers
from frappe.utils import cstr, getdate, split_emails, add_days, today
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
from frappe.model.document import Document
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
@@ -20,16 +21,30 @@ class Subscription(Document):
self.validate_next_schedule_date()
self.validate_email_id()
validate_template(self.subject or "")
validate_template(self.message or "")
def before_submit(self):
self.set_next_schedule_date()
def on_submit(self):
self.update_subscription_id()
# self.update_subscription_id()
self.update_subscription_data()
def on_update_after_submit(self):
self.update_subscription_data()
self.validate_dates()
self.set_next_schedule_date()
def before_cancel(self):
self.unlink_subscription_id()
def unlink_subscription_id(self):
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
if doc.meta.get_field('subscription'):
doc.subscription = None
doc.db_update()
def validate_dates(self):
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
frappe.throw(_("End date must be greater than start date"))
@@ -64,6 +79,21 @@ class Subscription(Document):
self.next_schedule_date = get_next_schedule_date(self.start_date,
self.frequency, self.repeat_on_day)
def update_subscription_data(self):
update_doc = False
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
if frappe.get_meta(self.reference_doctype).get_field("from_date"):
doc.from_date = self.from_date
doc.to_date = self.to_date
update_doc = True
if not doc.subscription:
doc.subscription = self.name
update_doc = True
if update_doc:
doc.db_update()
def update_subscription_id(self):
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
if not doc.meta.get_field('subscription'):
@@ -112,21 +142,31 @@ def get_subscription_entries(date):
def create_documents(data, schedule_date):
try:
doc = make_new_document(data, schedule_date)
if getattr(doc, "from_date", None):
update_subscription_period(data, doc)
if data.notify_by_email and data.recipients:
print_format = data.print_format or "Standard"
send_notification(doc, print_format, data.recipients)
send_notification(doc, data, print_format=print_format)
frappe.db.commit()
except Exception:
frappe.db.rollback()
frappe.db.begin()
frappe.log_error(frappe.get_traceback())
disabled_subscription(data)
disable_subscription(data)
frappe.db.commit()
if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data)
def disabled_subscription(data):
def update_subscription_period(data, doc):
from_date = doc.from_date
to_date = doc.to_date
frappe.db.set_value('Subscription', data.name, 'from_date', from_date)
frappe.db.set_value('Subscription', data.name, 'to_date', to_date)
def disable_subscription(data):
subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1)
@@ -160,9 +200,24 @@ def update_doc(new_document, reference_doc, args, schedule_date):
if new_document.meta.get_field('set_posting_time'):
new_document.set('set_posting_time', 1)
mcount = month_map.get(args.frequency)
if new_document.meta.get_field('subscription'):
new_document.set('subscription', args.name)
if args.from_date and args.to_date:
from_date = get_next_date(args.from_date, mcount)
if (cstr(get_first_day(args.from_date)) == cstr(args.from_date)) and \
(cstr(get_last_day(args.to_date)) == cstr(args.to_date)):
to_date = get_last_day(get_next_date(args.to_date, mcount))
else:
to_date = get_next_date(args.to_date, mcount)
if new_document.meta.get_field('from_date'):
new_document.set('from_date', from_date)
new_document.set('to_date', to_date)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
for data in new_document.meta.fields:
if data.fieldtype == 'Date' and data.reqd:
@@ -174,14 +229,25 @@ def get_next_date(dt, mcount, day=None):
return dt
def send_notification(new_rv, print_format='Standard', recipients=None):
def send_notification(new_rv, subscription_doc, print_format='Standard'):
"""Notify concerned persons about recurring document generation"""
print_format = print_format
frappe.sendmail(recipients,
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
if not subscription_doc.subject:
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
elif "{" in subscription_doc.subject:
subject = frappe.render_template(subscription_doc.subject, {'doc': new_rv})
if not subscription_doc.message:
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name)
elif "{" in subscription_doc.message:
message = frappe.render_template(subscription_doc.message, {'doc': new_rv})
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name,
file_name=new_rv.name, print_format=print_format)]
frappe.sendmail(subscription_doc.recipients,
subject=subject, message=message, attachments=attachments)
def notify_errors(doc, doctype, party, owner, name):
recipients = get_system_managers(only_name=True)
@@ -225,4 +291,20 @@ def stop_resume_subscription(subscription, status):
doc.update_status(status)
doc.save()
return doc.status
return doc.status
def subscription_doctype_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select parent from `tabDocField`
where fieldname = 'subscription'
and parent like %(txt)s
order by
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
parent
limit %(start)s, %(page_len)s""".format(**{
'key': searchfield,
}), {
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
})

View File

@@ -113,6 +113,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
});
this.page.add_menu_item(__("Sync Offline Invoices"), function () {
me.freeze_screen = true;
me.sync_sales_invoice()
});
@@ -1684,6 +1685,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
set_interval_for_si_sync: function () {
var me = this;
setInterval(function () {
me.freeze_screen = false;
me.sync_sales_invoice()
}, 60000)
},
@@ -1697,9 +1699,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.freeze = this.customer_doc.display
}
freeze_screen = this.freeze_screen || false;
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
frappe.call({
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
freeze: freeze_screen,
args: {
doc_list: me.si_docs,
email_queue_list: me.email_queue_list,

View File

@@ -68,7 +68,8 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address"
out[billing_address_field] = get_default_address(party_type, party.name)
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
if doctype:
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
# address display
out.address_display = get_address_display(out[billing_address_field])
@@ -77,7 +78,8 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
if party_type in ["Customer", "Lead"]:
out.shipping_address_name = get_default_address(party_type, party.name, 'is_shipping_address')
out.shipping_address = get_address_display(out["shipping_address_name"])
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
if doctype:
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
out.update(get_company_address(company))
@@ -320,11 +322,15 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
args = {
party_type.lower(): party,
"customer_group": customer_group,
"supplier_type": supplier_type,
"company": company
}
if customer_group:
args['customer_group'] = customer_group
if supplier_type:
args['supplier_type'] = supplier_type
if billing_address or shipping_address:
args.update(get_party_details(party, party_type, {"billing_address": billing_address, \
"shipping_address": shipping_address }))

View File

@@ -1,3 +1,4 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
@@ -24,6 +25,18 @@ frappe.ui.form.on("Purchase Order", {
},
});
frappe.ui.form.on("Purchase Order Item", {
item_code: function(frm) {
frappe.call({
method: "get_last_purchase_rate",
doc: frm.doc,
callback: function(r, rt) {
frm.trigger('calculate_taxes_and_totals');
}
})
}
});
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
refresh: function(doc, cdt, cdn) {
var me = this;
@@ -214,17 +227,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
delivered_by_supplier: function(){
cur_frm.cscript.update_status('Deliver', 'Delivered')
},
get_last_purchase_rate: function() {
frappe.call({
"method": "get_last_purchase_rate",
"doc": cur_frm.doc,
callback: function(r, rt) {
cur_frm.dirty();
cur_frm.cscript.calculate_taxes_and_totals();
}
})
}
});

View File

@@ -1206,37 +1206,6 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
"fieldname": "get_last_purchase_rate",
"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 last purchase rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -3458,7 +3427,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-19 11:22:30.190589",
"modified": "2017-09-22 16:11:49.856808",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -20,8 +20,8 @@ form_grid_templates = {
}
class PurchaseOrder(BuyingController):
def __init__(self, arg1, arg2=None):
super(PurchaseOrder, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(PurchaseOrder, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Purchase Order Item',
'target_dt': 'Material Request Item',
@@ -116,14 +116,13 @@ class PurchaseOrder(BuyingController):
d.discount_percentage = last_purchase_details['discount_percentage']
d.base_rate = last_purchase_details['base_rate'] * (flt(d.conversion_factor) or 1.0)
d.price_list_rate = d.base_price_list_rate / conversion_rate
d.rate = d.base_rate / conversion_rate
d.last_purchase_rate = d.base_rate / conversion_rate
else:
msgprint(_("Last purchase rate not found"))
item_last_purchase_rate = frappe.db.get_value("Item", d.item_code, "last_purchase_rate")
if item_last_purchase_rate:
d.base_price_list_rate = d.base_rate = d.price_list_rate \
= d.rate = item_last_purchase_rate
= d.last_purchase_rate = item_last_purchase_rate
# Check for Closed status
def check_for_closed_status(self):

View File

@@ -0,0 +1,99 @@
QUnit.module('Buying');
QUnit.test("test: purchase order with last purchase rate", function(assert) {
assert.expect(5);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make('Purchase Order', [
{supplier: 'Test Supplier'},
{is_subcontracted: 'No'},
{currency: 'INR'},
{items: [
[
{"item_code": 'Test Product 4'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 800},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
],
[
{"item_code": 'Test Product 1'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 400},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]}
]);
},
() => {
// Get item details
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item 1 name correct");
assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Item 2 name correct");
},
() => frappe.timeout(1),
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(3),
() => frappe.tests.click_button('Close'),
() => frappe.timeout(1),
() => {
return frappe.tests.make('Purchase Order', [
{supplier: 'Test Supplier'},
{is_subcontracted: 'No'},
{currency: 'INR'},
{items: [
[
{"item_code": 'Test Product 4'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 600},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
],
[
{"item_code": 'Test Product 1'},
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
{"qty": 1},
{"rate": 200},
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
]
]}
]);
},
() => frappe.timeout(2),
// Get the last purchase rate of items
() => {
assert.ok(cur_frm.doc.items[0].last_purchase_rate == 800, "Last purchase rate of item 1 correct");
},
() => {
assert.ok(cur_frm.doc.items[1].last_purchase_rate == 400, "Last purchase rate of item 2 correct");
},
() => frappe.tests.click_button('Submit'),
() => frappe.tests.click_button('Yes'),
() => frappe.timeout(3),
() => frappe.tests.click_button('Close'),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");
},
() => done()
]);
});

View File

@@ -655,6 +655,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "last_purchase_rate",
"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": "Last Purchase Rate",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -1714,7 +1745,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-08-02 22:15:47.411235",
"modified": "2017-09-22 16:47:08.783546",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",

View File

@@ -254,6 +254,21 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
}
})
}, __("Get items from"));
// Get items from Opportunity
this.frm.add_custom_button(__('Opportunity'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
source_doctype: "Opportunity",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
enquiry_type: "Sales"
}
})
}, __("Get items from"));
// Get items from open Material Requests based on supplier
this.frm.add_custom_button(__('Possible Supplier'), function() {
// Create a dialog window for the user to pick their supplier

View File

@@ -2,29 +2,29 @@
// For license information, please see license.txt
frappe.query_reports["Quoted Item Comparison"] = {
"filters": [
filters: [
{
"fieldname": "supplier_quotation",
"label": __("Supplier Quotation"),
"fieldtype": "Link",
"options": "Supplier Quotation",
"default": "",
"get_query": function () {
fieldtype: "Link",
label: __("Supplier Quotation"),
options: "Supplier Quotation",
fieldname: "supplier_quotation",
default: "",
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
},
{
"fieldname": "item",
"label": __("Item"),
"fieldtype": "Link",
"options": "Item",
"default": "",
"reqd": 1,
"get_query": function () {
var quote = frappe.query_report_filters_by_name.supplier_quotation.get_value();
reqd: 1,
default: "",
options: "Item",
label: __("Item"),
fieldname: "item",
fieldtype: "Link",
get_query: () => {
let quote = frappe.query_report_filters_by_name.supplier_quotation.get_value();
if (quote != "") {
return {
query: "erpnext.buying.doctype.quality_inspection.quality_inspection.item_query",
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
filters: {
"from": "Supplier Quotation Item",
"parent": quote
@@ -39,47 +39,50 @@ frappe.query_reports["Quoted Item Comparison"] = {
}
}
],
onload: function (report) {
onload: (report) => {
// Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), function () {
var reporter = frappe.query_reports["Quoted Item Comparison"];
report.page.add_inner_button(__("Select Default Supplier"), () => {
let reporter = frappe.query_reports["Quoted Item Comparison"];
//Always make a new one so that the latest values get updated
reporter.make_default_supplier_dialog(report);
report.dialog.show();
setTimeout(function () { report.dialog.input.focus(); }, 1000);
}, 'Tools');
},
"make_default_supplier_dialog": function (report) {
make_default_supplier_dialog: (report) => {
// Get the name of the item to change
var filters = report.get_values();
var item_code = filters.item;
if(!report.data) return;
let filters = report.get_values();
let item_code = filters.item;
// Get a list of the suppliers (with a blank as well) for the user to select
var select_options = "";
for (let supplier of report.data) {
select_options += supplier.supplier_name + '\n'
}
let suppliers = $.map(report.data, (row, idx)=>{ return row.supplier_name })
// Create a dialog window for the user to pick their supplier
var d = new frappe.ui.Dialog({
let dialog = new frappe.ui.Dialog({
title: __('Select Default Supplier'),
fields: [
{ fieldname: 'supplier', fieldtype: 'Select', label: 'Supplier', reqd: 1, options: select_options },
{ fieldname: 'ok_button', fieldtype: 'Button', label: 'Set Default Supplier' },
{
reqd: 1,
label: 'Supplier',
fieldtype: 'Link',
options: 'Supplier',
fieldname: 'supplier',
get_query: () => {
return {
filters: {
'name': ['in', suppliers]
}
}
}
}
]
});
// On the user clicking the ok button
d.fields_dict.ok_button.input.onclick = function () {
var btn = d.fields_dict.ok_button.input;
var v = report.dialog.get_values();
if (v) {
$(btn).set_working();
dialog.set_primary_action("Set Default Supplier", () => {
let values = dialog.get_values();
if(values) {
// Set the default_supplier field of the appropriate Item to the selected supplier
frappe.call({
method: "frappe.client.set_value",
@@ -87,17 +90,17 @@ frappe.query_reports["Quoted Item Comparison"] = {
doctype: "Item",
name: item_code,
fieldname: "default_supplier",
value: v.supplier,
value: values.supplier,
},
callback: function (r) {
$(btn).done_working();
freeze: true,
callback: (r) => {
frappe.msgprint("Successfully Set Supplier");
report.dialog.hide();
dialog.hide();
}
});
}
}
report.dialog = d;
});
dialog.show();
}
}

View File

@@ -8,53 +8,55 @@ import frappe
def execute(filters=None):
qty_list = get_quantity_list(filters.item)
data = get_quote_list(filters.item, qty_list)
columns = get_columns(qty_list)
return columns, data
def get_quote_list(item, qty_list):
out = []
if item:
price_data = []
suppliers = []
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
# Get the list of suppliers
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` where item_code=%s and docstatus < 2""", item, as_dict=1):
for splr in frappe.db.sql("""SELECT supplier from `tabSupplier Quotation` where name =%s and docstatus < 2""", root.parent, as_dict=1):
ip = frappe._dict({
if not item:
return []
suppliers = []
price_data = []
company_currency = frappe.db.get_default("currency")
float_precision = cint(frappe.db.get_default("float_precision")) or 2
# Get the list of suppliers
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item`
where item_code=%s and docstatus < 2""", item, as_dict=1):
for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation`
where name =%s and docstatus < 2""", root.parent, as_dict=1):
ip = frappe._dict({
"supplier": splr.supplier,
"qty": root.qty,
"parent": root.parent,
"rate": root.rate})
price_data.append(ip)
suppliers.append(splr.supplier)
#Add a row for each supplier
for root in set(suppliers):
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else:
exchange_rate = 1
row = frappe._dict({
"supplier_name": root
"rate": root.rate
})
for col in qty_list:
# Get the quantity for this row
for item_price in price_data:
if str(item_price.qty) == col.key and item_price.supplier == root:
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
row[col.key + "QUOTE"] = item_price.parent
break
else:
row[col.key] = ""
row[col.key + "QUOTE"] = ""
out.append(row)
price_data.append(ip)
suppliers.append(splr.supplier)
#Add a row for each supplier
for root in set(suppliers):
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
if supplier_currency:
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
else:
exchange_rate = 1
row = frappe._dict({
"supplier_name": root
})
for col in qty_list:
# Get the quantity for this row
for item_price in price_data:
if str(item_price.qty) == col.key and item_price.supplier == root:
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
row[col.key + "QUOTE"] = item_price.parent
break
else:
row[col.key] = ""
row[col.key + "QUOTE"] = ""
out.append(row)
return out
@@ -62,7 +64,8 @@ def get_quantity_list(item):
out = []
if item:
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1)
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item`
where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1)
qty_list.sort(reverse=False)
for qt in qty_list:
col = frappe._dict({
@@ -98,4 +101,4 @@ def get_columns(qty_list):
"width": 90
})
return columns
return columns

View File

@@ -315,11 +315,16 @@ def get_data():
"name": "Payment Gateway Account",
"description": _("Setup Gateway accounts.")
},
{
"type": "doctype",
"name": "POS Settings",
"description": _("Setup mode of POS (Online / Offline)")
},
{
"type": "doctype",
"name": "POS Profile",
"label": _("Point-of-Sale Profile"),
"description": _("Rules to calculate shipping amount for a sale")
"description": _("Setup default values for POS Invoices")
},
{
"type": "doctype",

View File

@@ -268,5 +268,13 @@ def get_data():
"icon": "octicon octicon-plus",
"type": "module",
"label": _("Healthcare")
}
},
{
"module_name": "Data Import Tool",
"color": "#7f8c8d",
"icon": "octicon octicon-circuit-board",
"type": "page",
"link": "data-import-tool",
"label": _("Data Import Tool")
},
]

View File

@@ -123,6 +123,12 @@ def get_data():
"is_query_report": True,
"name": "BOM Search",
"doctype": "BOM"
},
{
"type": "report",
"is_query_report": True,
"name": "BOM Stock Report",
"doctype": "BOM"
}
]
},

View File

@@ -105,6 +105,11 @@ def get_data():
"name": "Pricing Rule",
"description": _("Rules for applying pricing and discount.")
},
{
"type": "doctype",
"name": "Item Variant Settings",
"description": _("Item Variant Settings."),
},
]
},

View File

@@ -15,8 +15,8 @@ from erpnext.exceptions import InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
class AccountsController(TransactionBase):
def __init__(self, arg1, arg2=None):
super(AccountsController, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(AccountsController, self).__init__(*args, **kwargs)
@property
def company_currency(self):
@@ -187,9 +187,6 @@ class AccountsController(TransactionBase):
if stock_qty != len(get_serial_nos(item.get('serial_no'))):
item.set(fieldname, value)
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
item.set(fieldname, value)
if ret.get("pricing_rule"):
# if user changed the discount percentage then set user's discount percentage ?
item.set("discount_percentage", ret.get("discount_percentage"))

View File

@@ -61,7 +61,7 @@ class BuyingController(StockController):
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions))
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, doctype=self.doctype, company=self.company))
self.set_missing_item_details(for_validate)

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr, flt
import json
import json, copy
class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass
@@ -174,18 +174,30 @@ def copy_attributes_to_variant(item, variant):
# copy non no-copy fields
exclude_fields = ["item_code", "item_name", "show_in_website"]
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
if item.variant_based_on=='Manufacturer':
# don't copy manufacturer values if based on part no
exclude_fields += ['manufacturer', 'manufacturer_part_no']
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
if "variant_based_on" not in allow_fields:
allow_fields.append("variant_based_on")
for field in item.meta.fields:
# "Table" is part of `no_value_field` but we shouldn't ignore tables
if (field.fieldtype == 'Table' or field.fieldtype not in no_value_fields) \
and (not field.no_copy) and field.fieldname not in exclude_fields:
if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields:
if variant.get(field.fieldname) != item.get(field.fieldname):
variant.set(field.fieldname, item.get(field.fieldname))
if field.fieldtype == "Table":
variant.set(field.fieldname, [])
for d in item.get(field.fieldname):
row = copy.deepcopy(d)
if row.get("name"):
row.name = None
variant.append(field.fieldname, row)
else:
variant.set(field.fieldname, item.get(field.fieldname))
variant.variant_of = item.name
variant.has_variants = 0
if not variant.description:
@@ -195,7 +207,7 @@ def copy_attributes_to_variant(item, variant):
if variant.attributes:
variant.description += "\n"
for d in variant.attributes:
variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
variant.description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code"""

View File

@@ -49,7 +49,8 @@ class SellingController(StockController):
if getattr(self, "customer", None):
from erpnext.accounts.party import _get_party_details
party_details = _get_party_details(self.customer,
ignore_permissions=self.flags.ignore_permissions)
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company)
if not self.meta.get_field("sales_team"):
party_details.pop("sales_team")

View File

@@ -4,6 +4,7 @@ import frappe
import json
import unittest
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
# python 3 compatibility stuff
@@ -54,5 +55,7 @@ def make_item_variant():
class TestItemVariant(unittest.TestCase):
def test_tables_in_template_copied_to_variant(self):
fields = [{'field_name': 'quality_parameters'}]
set_item_variant_settings(fields)
variant = make_item_variant()
self.assertNotEqual(variant.get("quality_parameters"), [])

View File

@@ -42,10 +42,28 @@ class Opportunity(TransactionBase):
if not self.with_items:
self.items = []
def make_new_lead_if_required(self):
"""Set lead against new opportunity"""
if not (self.lead or self.customer) and self.contact_email:
# check if customer is already created agains the self.contact_email
customer = frappe.db.sql("""select
distinct `tabDynamic Link`.link_name as customer
from
`tabContact`,
`tabDynamic Link`
where `tabContact`.email_id='{0}'
and
`tabContact`.name=`tabDynamic Link`.parent
and
ifnull(`tabDynamic Link`.link_name, '')<>''
and
`tabDynamic Link`.link_doctype='Customer'
""".format(self.contact_email), as_dict=True)
if customer and customer[0].customer:
self.customer = customer[0].customer
self.enquiry_from = "Customer"
return
lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
if not lead_name:
sender_name = get_fullname(self.contact_email)
@@ -245,6 +263,27 @@ def make_quotation(source_name, target_doc=None):
return doclist
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
doclist = get_mapped_doc("Opportunity", source_name, {
"Opportunity": {
"doctype": "Request for Quotation",
"validation": {
"enquiry_type": ["=", "Sales"]
}
},
"Opportunity Item": {
"doctype": "Request for Quotation Item",
"field_map": [
["name", "opportunity_item"],
["parent", "opportunity"],
["uom", "uom"]
]
}
}, target_doc)
return doclist
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
doclist = get_mapped_doc("Opportunity", source_name, {
@@ -284,4 +323,4 @@ def auto_close_opportunity():
doc.status = "Closed"
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.save()
doc.save()

View File

@@ -4,6 +4,7 @@ from __future__ import unicode_literals
import frappe
from frappe.utils import today
from erpnext.crm.doctype.lead.lead import make_customer
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
import unittest
@@ -25,12 +26,45 @@ class TestOpportunity(unittest.TestCase):
doc = frappe.get_doc('Opportunity', doc.name)
self.assertEquals(doc.status, "Quotation")
def test_make_new_lead_if_required(self):
args = {
"doctype": "Opportunity",
"contact_email":"new.opportunity@example.com",
"enquiry_type": "Sales",
"with_items": 0,
"transaction_date": today()
}
# new lead should be created against the new.opportunity@example.com
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.lead)
self.assertEquals(opp_doc.enquiry_from, "Lead")
self.assertEquals(frappe.db.get_value("Lead", opp_doc.lead, "email_id"),
'new.opportunity@example.com')
# create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.lead).insert(ignore_permissions=True)
frappe.get_doc({
"doctype": "Contact",
"email_id": "new.opportunity@example.com",
"first_name": "_Test Opportunity Customer",
"links": [{
"link_doctype": "Customer",
"link_name": customer.name
}]
}).insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.customer)
self.assertEquals(opp_doc.enquiry_from, "Customer")
self.assertEquals(opp_doc.customer, customer.name)
def make_opportunity(**args):
args = frappe._dict(args)
opp_doc = frappe.get_doc({
"doctype": "Opportunity",
"enquiry_from": "Customer" or args.enquiry_from,
"enquiry_from": args.enquiry_from or "Customer",
"enquiry_type": "Sales",
"with_items": args.with_items or 0,
"transaction_date": today()

View File

@@ -278,5 +278,16 @@
"item_code": "Autocad",
"item_name": "Autocad",
"item_group": "All Item Groups"
},
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"valuation_rate": 200,
"default_warehouse": "Stores",
"description": "Corrugated Box",
"item_code": "Corrugated Box",
"item_name": "Corrugated Box",
"item_group": "All Item Groups"
}
]

View File

@@ -16,6 +16,7 @@ def setup(domain):
setup_user()
setup_employee()
setup_user_roles()
setup_role_permissions()
employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
@@ -91,7 +92,8 @@ def setup_fiscal_year():
pass
# set the last fiscal year (current year) as default
fiscal_year.set_as_default()
if fiscal_year:
fiscal_year.set_as_default()
def setup_holiday_list():
"""Setup Holiday List for the current year"""
@@ -374,6 +376,22 @@ def setup_pos_profile():
pos.insert()
def setup_role_permissions():
role_permissions = {'Batch': ['Accounts User', 'Item Manager']}
for doctype, roles in role_permissions.items():
for role in roles:
if not frappe.db.get_value('Custom DocPerm',
{'parent': doctype, 'role': role}):
frappe.get_doc({
'doctype': 'Custom DocPerm',
'role': role,
'read': 1,
'write': 1,
'create': 1,
'delete': 1,
'parent': doctype
}).insert(ignore_permissions=True)
def import_json(doctype, submit=False, values=None):
frappe.flags.in_import = True
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',

View File

@@ -7,6 +7,7 @@ import frappe, random
from frappe.desk import query_report
from erpnext.stock.stock_ledger import NegativeStockError
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError, SerialNoQtyError
from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
@@ -59,7 +60,7 @@ def make_delivery_note():
try:
dn.submit()
frappe.db.commit()
except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError):
except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError):
frappe.db.rollback()
def make_stock_reconciliation():

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -13,4 +13,8 @@
* Unlink Payment on Cancellation of Invoice: If checked, system will unlink the payment against the invoice. Otherwise, it will show the link error.
* Allow Stale Exchange Rate: This should be unchecked if you want ERPNext to check the age of records fetched from Currency Exchange in foreign currency transactions. If it is unchecked, the exchange rate field will be read-only in documents.
* Stale Days: The number of days to use when deciding if a Currency Exchange record is stale. E.g If Currency Exchange records are to be updated every day, the Stale Days should be set as 1.
{next}

View File

@@ -48,4 +48,11 @@ When you make a new Variant, the system will prompt you to select a Manufacturer
<img class='screenshot' alt='Setup Item Variant by Manufacturer'
src='/docs/assets/img/stock/set-variant-by-mfg.png'>
The naming of the variant will be the name (ID) of the template Item with a number suffix. e.g. "ITEM000" will have variant "ITEM000-1"
The naming of the variant will be the name (ID) of the template Item with a number suffix. e.g. "ITEM000" will have variant "ITEM000-1"
### Update Variants Based on Template
To update the value in the variants items from the template item, select the respective fields first in the Item Variant Settings page. After that system will update the value of that fields in the variants if that values has been changed in the template item.
To set the fields Goto Stock > Item Variant Settings
<img class='screenshot' alt='Item Variant Settings'
src='/docs/assets/img/stock/item_variants_settings.png'>

View File

@@ -183,16 +183,20 @@ var btn_create_vital_signs = function (frm) {
var btn_update_status = function(frm, status){
var doc = frm.doc;
frappe.call({
method:
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status",
args: {appointmentId: doc.name, status:status},
callback: function(data){
if(!data.exc){
cur_frm.reload_doc();
}
frappe.confirm(__('Are you sure you want to cancel this appointment?'),
function() {
frappe.call({
method:
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status",
args: {appointmentId: doc.name, status:status},
callback: function(data){
if(!data.exc){
frm.reload_doc();
}
}
});
}
});
);
};
var btn_invoice_consultation = function(frm){

View File

@@ -11,7 +11,7 @@ app_email = "info@erpnext.com"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
develop_version = '8.x.x-beta'
develop_version = '9.x.x-develop'
error_report_email = "support@erpnext.com"
@@ -27,7 +27,6 @@ doctype_js = {
# setup wizard
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_complete"
setup_wizard_success = "erpnext.setup.setup_wizard.setup_wizard.setup_success"
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
before_install = "erpnext.setup.install.check_setup_wizard_not_completed"
@@ -48,7 +47,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou
update_website_context = "erpnext.shopping_cart.utils.update_website_context"
my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"]
calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Holiday List"]

View File

@@ -149,7 +149,7 @@
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"in_standard_filter": 1,
"label": "Full Name",
"length": 0,
"no_copy": 0,
@@ -431,7 +431,7 @@
"no_copy": 0,
"oldfieldname": "gender",
"oldfieldtype": "Select",
"options": "Gender",
"options": "Gender",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -2432,7 +2432,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-13 14:29:13.694009",
"modified": "2017-10-04 11:42:02.495731",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",

View File

@@ -21,7 +21,7 @@ class JobOpening(WebsiteGenerator):
self.route = frappe.scrub(self.job_title).replace('_', '-')
def get_context(self, context):
context.parents = [{'name': 'jobs', 'title': _('All Jobs') }]
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
def get_list_context(context):
context.title = _("Jobs")

View File

@@ -284,7 +284,7 @@ class ProcessPayroll(Document):
})
# Deductions
for acc, amt in deductions.items():
for acc, amount in deductions.items():
payable_amount -= flt(amount, precision)
accounts.append({
"account": acc,

View File

@@ -36,7 +36,7 @@ def execute(filters=None):
status_map = {"Present": "P", "Absent": "A", "Half Day": "HD", "On Leave": "L", "None": "", "Holiday":"<b>H</b>"}
if status == "None" and holiday_map:
emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list
if (day+1) in holiday_map[emp_holiday_list]:
if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list]:
status = "Holiday"
row.append(status_map[status])
@@ -45,7 +45,7 @@ def execute(filters=None):
elif status == "Absent":
total_a += 1
elif status == "On Leave":
total_l += 1
total_l += 1
elif status == "Half Day":
total_p += 0.5
total_a += 0.5

View File

@@ -95,8 +95,8 @@ class BOM(WebsiteGenerator):
self.validate_bom_currecny(item)
ret = self.get_bom_material_detail({
"item_code": item.item_code,
"item_name": item.item_name,
"item_code": item.item_code,
"item_name": item.item_name,
"bom_no": item.bom_no,
"stock_qty": item.stock_qty
})
@@ -128,7 +128,7 @@ class BOM(WebsiteGenerator):
'uom' : item and args['stock_uom'] or '',
'conversion_factor': 1,
'bom_no' : args['bom_no'],
'rate' : rate / self.conversion_rate,
'rate' : rate / self.conversion_rate if self.conversion_rate else rate,
'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
'base_rate' : rate
@@ -312,7 +312,7 @@ class BOM(WebsiteGenerator):
li.append("{0} on row {1}".format(i.item_code, i.idx))
duplicate_list = '<br>' + '<br>'.join(li)
frappe.throw(_("Same item has been entered multiple times. {list}").format(list=duplicate_list))
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
def check_recursion(self):
""" Check whether recursion occurs in any bom"""
@@ -346,7 +346,7 @@ class BOM(WebsiteGenerator):
count = 0
if not bom_list:
bom_list = []
if self.name not in bom_list:
bom_list.append(self.name)
@@ -374,7 +374,7 @@ class BOM(WebsiteGenerator):
if d.workstation:
if not d.hour_rate:
hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate"))
d.hour_rate = hour_rate / flt(self.conversion_rate)
d.hour_rate = hour_rate / flt(self.conversion_rate) if self.conversion_rate else hour_rate
if d.hour_rate and d.time_in_mins:
d.base_hour_rate = flt(d.hour_rate) * flt(self.conversion_rate)

View File

@@ -30,12 +30,13 @@ class BOMUpdateTool(Document):
frappe.throw(_("The selected BOMs are not for the same item"))
def update_new_bom(self):
current_bom_unitcost = frappe.db.sql("""select total_cost/quantity
from `tabBOM` where name = %s""", self.current_bom)
current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0
new_bom_unitcost = frappe.db.sql("""select total_cost/quantity
from `tabBOM` where name = %s""", self.new_bom)
new_bom_unitcost = flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0
frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2""",
(self.new_bom, current_bom_unitcost, current_bom_unitcost, self.current_bom))
(self.new_bom, new_bom_unitcost, new_bom_unitcost, self.current_bom))
def get_parent_boms(self):
return [d[0] for d in frappe.db.sql("""select distinct parent

View File

@@ -51,9 +51,9 @@ class ProductionOrder(Document):
def validate_sales_order(self):
if self.sales_order:
so = frappe.db.sql("""
select so.name, so_item.delivery_date, so.project
select so.name, so_item.delivery_date, so.project
from `tabSales Order` so, `tabSales Order Item` so_item
where so.name=%s and so.name=so_item.parent
where so.name=%s and so.name=so_item.parent
and so.docstatus = 1 and so_item.item_code=%s
""", (self.sales_order, self.production_item), as_dict=1)
@@ -112,7 +112,7 @@ class ProductionOrder(Document):
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
"over_production_allowance_percentage"))
if total_qty > so_qty + (allowance_percentage/100 * so_qty):
frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
.format(self.production_item, so_qty), OverProductionError)
@@ -217,27 +217,27 @@ class ProductionOrder(Document):
def set_production_order_operations(self):
"""Fetch operations from BOM and set in 'Production Order'"""
self.set('operations', [])
if not self.bom_no \
or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
return
if self.use_multi_level_bom:
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
else:
bom_list = [self.bom_no]
operations = frappe.db.sql("""
select
select
operation, description, workstation, idx,
base_hour_rate as hour_rate, time_in_mins,
base_hour_rate as hour_rate, time_in_mins,
"Pending" as status, parent as bom
from
`tabBOM Operation`
where
parent in (%s) order by idx
""" % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1)
self.set('operations', operations)
self.calculate_time()
@@ -277,7 +277,7 @@ class ProductionOrder(Document):
timesheet.set('time_logs', [])
for i, d in enumerate(self.operations):
if d.status != 'Completed':
self.set_start_end_time_for_workstation(d, i)
@@ -370,8 +370,13 @@ class ProductionOrder(Document):
self.actual_start_date = None
self.actual_end_date = None
if self.get("operations"):
self.actual_start_date = min([d.actual_start_time for d in self.get("operations")])
self.actual_end_date = max([d.actual_end_time for d in self.get("operations")])
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
if actual_start_dates:
self.actual_start_date = min(actual_start_dates)
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
def delete_timesheet(self):
for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}):
@@ -411,18 +416,18 @@ class ProductionOrder(Document):
if d.source_warehouse:
stock_bin = get_bin(d.item_code, d.source_warehouse)
stock_bin.update_reserved_qty_for_production()
def get_items_and_operations_from_bom(self):
self.set_required_items()
self.set_production_order_operations()
return check_if_scrap_warehouse_mandatory(self.bom_no)
def set_available_qty(self):
for d in self.get("required_items"):
if d.source_warehouse:
d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse)
if self.wip_warehouse:
d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse)
@@ -439,7 +444,7 @@ class ProductionOrder(Document):
'required_qty': item.qty,
'source_warehouse': item.source_warehouse or item.default_warehouse
})
self.set_available_qty()
def update_transaferred_qty_for_required_items(self):
@@ -463,12 +468,12 @@ class ProductionOrder(Document):
def get_item_details(item, project = None):
res = frappe.db.sql("""
select stock_uom, description
from `tabItem`
where disabled=0
from `tabItem`
where disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
and name=%s
""", (nowdate(), item), as_dict=1)
if not res:
return {}
@@ -611,14 +616,14 @@ def make_new_timesheet(source_name, target_doc=None):
@frappe.whitelist()
def stop_unstop(production_order, status):
""" Called from client side on Stop/Unstop event"""
if not frappe.has_permission("Production Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
pro_order = frappe.get_doc("Production Order", production_order)
pro_order.update_status(status)
pro_order.update_planned_qty()
frappe.msgprint(_("Production Order has been {0}").format(status))
pro_order.notify_update()
return pro_order.status
return pro_order.status

View File

@@ -59,8 +59,6 @@ QUnit.test("test: production order", function (assert) {
// Confirm the production order timesheet, save and submit it
() => frappe.click_link("TS-00"),
() => frappe.timeout(1),
() => frappe.click_button("Save"),
() => frappe.timeout(1),
() => frappe.click_button("Submit"),
() => frappe.timeout(1),
() => frappe.click_button("Yes"),

View File

@@ -12,10 +12,6 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
class ProductionPlanningTool(Document):
def __init__(self, arg1, arg2=None):
super(ProductionPlanningTool, self).__init__(arg1, arg2)
self.item_dict = {}
def clear_table(self, table_name):
self.set(table_name, [])
@@ -398,6 +394,9 @@ class ProductionPlanningTool(Document):
return bom_wise_item_details
def make_items_dict(self, item_list):
if not getattr(self, "item_dict", None):
self.item_dict = {}
for i in item_list:
self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])

View File

@@ -0,0 +1,27 @@
<h1 class="text-left"><b>{%= __("BOM Stock Report") %}</b></h1>
<h5 class="text-left">{%= filters.bom %}</h5>
<h5 class="text-left">{%= filters.warehouse %}</h5>
<hr>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 15%">{%= __("Item") %}</th>
<th style="width: 35%">{%= __("Description") %}</th>
<th style="width: 14%">{%= __("Required Qty") %}</th>
<th style="width: 13%">{%= __("In Stock Qty") %}</th>
<th style="width: 23%">{%= __("Enough Parts to Build") %}</th>
</tr>
</thead>
<tbody>
{% for(var i=0, l=data.length; i<l; i++) { %}
<tr>
<td>{%= data[i][ __("Item")] %}</td>
<td>{%= data[i][ __("Description")] %} </td>
<td align="right">{%= data[i][ __("Required Qty")] %} </td>
<td align="right">{%= data[i][ __("In Stock Qty")] %} </td>
<td align="right">{%= data[i][ __("Enough Parts to Build")] %} </td>
</tr>
{% } %}
</tbody>
</table>

View File

@@ -434,14 +434,19 @@ erpnext.patches.v8_5.update_customer_group_in_POS_profile
erpnext.patches.v8_6.update_timesheet_company_from_PO
erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager
erpnext.patches.v8_5.remove_project_type_property_setter
erpnext.patches.v8_7.add_more_gst_fields
erpnext.patches.v8_7.add_more_gst_fields #21-09-2017
erpnext.patches.v8_7.fix_purchase_receipt_status
erpnext.patches.v8_6.rename_bom_update_tool
erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17
erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017
erpnext.patches.v8_9.rename_company_sales_target_field
erpnext.patches.v8_8.set_bom_rate_as_per_uom
erpnext.patches.v8_8.add_new_fields_in_accounts_settings
erpnext.patches.v8_9.set_print_zero_amount_taxes
erpnext.patches.v8_9.set_default_customer_group
erpnext.patches.v8_9.remove_employee_from_salary_structure_parent
erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts
erpnext.patches.v8_9.set_default_fields_in_variant_settings
erpnext.patches.v8_9.update_billing_gstin_for_indian_account
erpnext.patches.v9_0.fix_subscription_next_date
erpnext.patches.v9_0.add_healthcare_domain

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql(
"INSERT INTO `tabSingles` (`doctype`, `field`, `value`) VALUES ('Accounts Settings', 'allow_stale', '1'), "
"('Accounts Settings', 'stale_days', '1')"
)

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('stock', 'doctype', 'item_variant_settings')
frappe.reload_doc('stock', 'doctype', 'variant_field')
doc = frappe.get_doc('Item Variant Settings')
doc.set_default_fields()
doc.save()

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if company:
for doctype in ['Sales Invoice', 'Delivery Note']:
frappe.db.sql(""" update `tab{0}`
set billing_address_gstin = (select gstin from `tabAddress`
where name = customer_address)
where customer_address is not null and customer_address != ''""".format(doctype))

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute():
domain = _('Healthcare')
if not frappe.db.exists('Domain', domain):
frappe.get_doc({
'doctype': 'Domain',
'domain': domain
}).insert(ignore_permissions=True)

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doctype('Subscription')
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
for data in frappe.get_all('Subscription', fields = ["name", "reference_doctype", "reference_document"],
filters = {'reference_doctype': ('in', doctypes)}):
doc = frappe.get_doc('Subscription', data.name)
fields = ['transaction_date']
if doc.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
fields = ['posting_date']
fields.extend(['from_date', 'to_date'])
reference_data = frappe.db.get_value(data.reference_doctype,
data.reference_document, fields, as_dict=1)
if reference_data:
doc.start_date = reference_data.get('posting_date') or reference_data.get('transaction_date')
doc.from_date = reference_data.get('from_date')
doc.to_date = reference_data.get('to_date')
doc.set_next_schedule_date()
doc.db_update()

View File

@@ -53,12 +53,17 @@ class Project(Document):
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
def validate(self):
self.validate_project_name()
self.validate_dates()
self.validate_weights()
self.sync_tasks()
self.tasks = []
self.send_welcome_email()
def validate_project_name(self):
if self.get("__islocal") and frappe.db.exists("Project", self.project_name):
frappe.throw(_("Project {0} already exists").format(self.project_name))
def validate_dates(self):
if self.expected_start_date and self.expected_end_date:
if getdate(self.expected_end_date) < getdate(self.expected_start_date):

View File

@@ -47,7 +47,7 @@ class Task(Document):
from frappe.desk.form.assign_to import clear
clear(self.doctype, self.name)
def validate_progress(self):
if self.progress > 100:
frappe.throw(_("Progress % for a task cannot be more than 100."))
@@ -63,6 +63,12 @@ class Task(Document):
self.check_recursion()
self.reschedule_dependent_tasks()
self.update_project()
self.unassign_todo()
def unassign_todo(self):
if self.status == "Closed" or self.status == "Cancelled":
from frappe.desk.form.assign_to import clear
clear(self.doctype, self.name)
def update_total_expense_claim(self):
self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
@@ -120,7 +126,7 @@ class Task(Document):
def has_webform_permission(doc):
project_user = frappe.db.get_value("Project User", {"parent": doc.project, "user":frappe.session.user} , "user")
if project_user:
return True
return True
@frappe.whitelist()
def get_events(start, end, filters=None):
@@ -154,7 +160,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
order by name
limit %(start)s, %(page_len)s """ % {'key': searchfield,
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype),
'start': start, 'page_len': page_len})
'start': start, 'page_len': page_len})
@frappe.whitelist()
@@ -170,4 +176,5 @@ def set_tasks_as_overdue():
where exp_end_date is not null
and exp_end_date < CURDATE()
and `status` not in ('Closed', 'Cancelled')""")

View File

@@ -1,39 +1,45 @@
frappe.ui.form.on("Communication", {
refresh: function(frm) {
refresh: (frm) => {
// setup custom Make button only if Communication is Email
if(frm.doc.communication_medium == "Email" && frm.doc.sent_or_received == "Received") {
frm.events.setup_custom_buttons(frm);
}
},
setup_custom_buttons: (frm) => {
let confirm_msg = "Are you sure you want to create {0} from this email";
if(frm.doc.reference_doctype !== "Issue") {
frm.add_custom_button(__("Issue"), function() {
frappe.confirm("Are you sure you want to create Issue from this email", function(){
frm.add_custom_button(__("Issue"), () => {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication');
})
}, "Make");
}
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
frm.add_custom_button(__("Lead"), function() {
frappe.confirm("Are you sure you want to create Lead from this email", function(){
frm.add_custom_button(__("Lead"), () => {
frappe.confirm(__(confirm_msg, [__("Lead")]), () => {
frm.trigger('make_lead_from_communication');
})
}, "Make");
frm.add_custom_button(__("Opportunity"), function() {
frappe.confirm("Are you sure you want to create Opportunity from this email", function(){
frm.add_custom_button(__("Opportunity"), () => {
frappe.confirm(__(confirm_msg, [__("Opportunity")]), () => {
frm.trigger('make_opportunity_from_communication');
})
}, "Make");
}
frm.page.set_inner_btn_group_as_primary(__("Make"));
},
make_lead_from_communication: function(frm) {
make_lead_from_communication: (frm) => {
return frappe.call({
method: "frappe.email.inbox.make_lead_from_communication",
args: {
communication: frm.doc.name
},
freeze: true,
callback: function(r) {
callback: (r) => {
if(r.message) {
frm.reload_doc()
}
@@ -41,14 +47,14 @@ frappe.ui.form.on("Communication", {
})
},
make_issue_from_communication: function(frm) {
make_issue_from_communication: (frm) => {
return frappe.call({
method: "frappe.email.inbox.make_issue_from_communication",
args: {
communication: frm.doc.name
},
freeze: true,
callback: function(r) {
callback: (r) => {
if(r.message) {
frm.reload_doc()
}
@@ -56,14 +62,14 @@ frappe.ui.form.on("Communication", {
})
},
make_opportunity_from_communication: function(frm) {
make_opportunity_from_communication: (frm) => {
return frappe.call({
method: "frappe.email.inbox.make_opportunity_from_communication",
args: {
communication: frm.doc.name
},
freeze: true,
callback: function(r) {
callback: (r) => {
if(r.message) {
frm.reload_doc()
}

View File

@@ -101,27 +101,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return me.set_query_for_batch(doc, cdt, cdn)
});
}
},
onload: function() {
var me = this;
if(this.frm.doc.__islocal) {
var today = frappe.datetime.get_today(),
currency = frappe.defaults.get_user_default("currency");
$.each({
currency: currency,
price_list_currency: currency,
status: "Draft",
is_subcontracted: "No",
}, function(fieldname, value) {
if(me.frm.fields_dict[fieldname] && !me.frm.doc[fieldname])
me.frm.set_value(fieldname, value);
});
if(this.frm.doc.company && !this.frm.doc.amended_from) {
this.frm.trigger("company");
}
}
if(this.frm.fields_dict["taxes"]) {
this["taxes_remove"] = this.calculate_taxes_and_totals;
@@ -153,11 +132,36 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return {
filters: filters
}
};
});
}
},
onload: function() {
var me = this;
this.setup_quality_inspection();
if(this.frm.doc.__islocal) {
var currency = frappe.defaults.get_user_default("currency");
let set_value = (fieldname, value) => {
if(me.frm.fields_dict[fieldname] && !me.frm.doc[fieldname]) {
return me.frm.set_value(fieldname, value);
}
};
return frappe.run_serially([
() => set_value('currency', currency),
() => set_value('price_list_currency', currency),
() => set_value('status', 'Draft'),
() => set_value('is_subcontracted', 'No'),
() => {
if(this.frm.doc.company && !this.frm.doc.amended_from) {
this.frm.trigger("company");
}
}
]);
}
},
setup_quality_inspection: function() {
@@ -195,13 +199,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
onload_post_render: function() {
var me = this;
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
this.apply_default_taxes();
frappe.after_ajax(() => this.apply_default_taxes());
} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
&& !this.frm.doc.is_pos) {
me.calculate_taxes_and_totals();
frappe.after_ajax(() => this.calculate_taxes_and_totals());
}
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
this.setup_item_selector();
@@ -378,6 +381,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
if(me.frm.doc.company && me.frm.fields_dict.currency) {
var company_currency = me.get_company_currency();
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (!me.frm.doc.currency) {
me.frm.set_value("currency", company_currency);
}
@@ -519,6 +523,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
conversion_rate: function() {
const me = this.frm;
if(this.frm.doc.currency === this.get_company_currency()) {
this.frm.set_value("conversion_rate", 1.0);
}
@@ -536,6 +541,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
}
// Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale",
function(d){
me.set_df_property("conversion_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
}
);
},
set_actual_charges_based_on_currency: function() {

View File

@@ -37,7 +37,6 @@
<div class="cell price-cell text-right tax-table">
</div>
</div>
{% if (apply_discount_on) { %}
<div class="pos-list-row discount-amount-area">
<div class="cell"></div>
<div class="cell text-right">{%= __("Discount") %}</div>
@@ -52,7 +51,6 @@
</div>
</div>
</div>
{% } %}
<div class="pos-list-row grand-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
<div class="cell">
<a class="">

View File

@@ -86,6 +86,10 @@ erpnext.setup.slides_settings = [
});
},
validate: function() {
if ((this.values.company_name || "").toLowerCase() == "company") {
frappe.msgprint(__("Company Name cannot be Company"));
return false;
}
if (!this.values.company_abbr) {
return false;
}
@@ -135,10 +139,6 @@ erpnext.setup.slides_settings = [
frappe.msgprint(__("Please enter valid Financial Year Start and End Dates"));
return false;
}
if ((this.values.company_name || "").toLowerCase() == "company") {
frappe.msgprint(__("Company Name cannot be Company"));
return false;
}
return true;
},

View File

@@ -96,7 +96,17 @@ erpnext.SerialNoBatchSelector = Class.extend({
if(this.show_dialog) {
let d = this.item;
this.dialog.set_value('serial_no', d.serial_no);
if (d.has_serial_no && d.serial_no) {
this.dialog.set_value('serial_no', d.serial_no);
} else if (d.batch_no) {
this.dialog.fields_dict.batches.df.data.push({
'batch_no': d.batch_no,
'actual_qty': d.actual_qty,
'selected_qty': d.qty
});
this.dialog.fields_dict.batches.grid.refresh();
}
}
this.dialog.show();
@@ -116,8 +126,10 @@ erpnext.SerialNoBatchSelector = Class.extend({
}
values.batches.map((batch, i) => {
if(!batch.selected_qty || batch.selected_qty === 0 ) {
frappe.throw(__("Please select quantity on row " + (i+1)));
return false;
if (!this.show_dialog) {
frappe.throw(__("Please select quantity on row " + (i+1)));
return false;
}
}
});
return true;
@@ -125,9 +137,11 @@ erpnext.SerialNoBatchSelector = Class.extend({
} else {
let serial_nos = values.serial_no || '';
if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
frappe.throw(__("Please enter serial numbers for serialized item "
+ values.item_code));
return false;
if (!this.show_dialog) {
frappe.throw(__("Please enter serial numbers for serialized item "
+ values.item_code));
return false;
}
}
return true;
}

View File

@@ -84,34 +84,13 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-08-31 14:38:52.220743",
"modified_by": "ewdszx@ed.ews",
"modified": "2017-09-29 14:38:52.220743",
"modified_by": "Administrator",
"module": "Regional",
"name": "GST HSN Code",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,

View File

@@ -83,34 +83,13 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-08-31 14:39:15.625952",
"modified_by": "ewdszx@ed.ews",
"modified": "2017-09-29 14:39:15.625952",
"modified_by": "Administrator",
"module": "Regional",
"name": "GST Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,

View File

@@ -12,7 +12,7 @@ def setup(company=None, patch=True):
make_custom_fields()
add_permissions()
add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes')
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
add_print_formats()
if not patch:
update_address_template()
@@ -72,7 +72,6 @@ def add_custom_roles_for_reports():
def add_permissions():
for doctype in ('GST HSN Code', 'GST Settings'):
add_permission(doctype, 'Accounts Manager', 0)
add_permission(doctype, 'All', 0)
def add_print_formats():
@@ -113,12 +112,15 @@ def make_custom_fields():
]
sales_invoice_gst_fields = [
dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
fieldtype='Data', insert_after='customer_address',
options='customer_address.gstin', print_hide=1),
dict(fieldname='customer_gstin', label='Customer GSTIN',
fieldtype='Data', insert_after='shipping_address',
options='shipping_address_name.gstin', print_hide=1),
dict(fieldname='place_of_supply', label='Place of Supply',
fieldtype='Data', insert_after='customer_gstin', print_hide=1,
options='shipping_address_name.gst_state_number', read_only=1),
options='shipping_address_name.gst_state_number', read_only=0),
dict(fieldname='company_gstin', label='Company GSTIN',
fieldtype='Data', insert_after='company_address',
options='company_address.gstin', print_hide=1)

View File

@@ -8,6 +8,7 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
def execute(filters=None):
return _execute(filters, additional_table_columns=[
dict(fieldtype='Data', label='Customer GSTIN', width=120),
dict(fieldtype='Data', label='Billing Address GSTIN', width=140),
dict(fieldtype='Data', label='Company GSTIN', width=120),
dict(fieldtype='Data', label='Place of Supply', width=120),
dict(fieldtype='Data', label='Reverse Charge', width=120),
@@ -17,6 +18,7 @@ def execute(filters=None):
dict(fieldtype='Data', label='HSN Code', width=120)
], additional_query_columns=[
'customer_gstin',
'billing_address_gstin',
'company_gstin',
'place_of_supply',
'reverse_charge',

View File

@@ -8,6 +8,7 @@ from erpnext.accounts.report.sales_register.sales_register import _execute
def execute(filters=None):
return _execute(filters, additional_table_columns=[
dict(fieldtype='Data', label='Customer GSTIN', width=120),
dict(fieldtype='Data', label='Billing Address GSTIN', width=140),
dict(fieldtype='Data', label='Company GSTIN', width=120),
dict(fieldtype='Data', label='Place of Supply', width=120),
dict(fieldtype='Data', label='Reverse Charge', width=120),
@@ -16,6 +17,7 @@ def execute(filters=None):
dict(fieldtype='Data', label='E-Commerce GSTIN', width=130)
], additional_query_columns=[
'customer_gstin',
'billing_address_gstin',
'company_gstin',
'place_of_supply',
'reverse_charge',

View File

@@ -50,6 +50,7 @@ class Fees(AccountsController):
select g.email_address
from `tabGuardian` g, `tabStudent Guardian` sg
where g.name = sg.guardian and sg.parent = %s and sg.parenttype = 'Student'
and ifnull(g.email_address, '')!=''
""", self.student)
student_email_id = frappe.db.get_value("Student", self.student, "student_email_id")

View File

@@ -13,7 +13,6 @@ class StudentApplicant(Document):
if self.student_admission:
naming_series = frappe.db.get_value('Student Admission', self.student_admission,
'naming_series_for_student_applicant')
print(naming_series)
if naming_series:
self.naming_series = naming_series

View File

@@ -12,8 +12,8 @@ from erpnext.stock.utils import get_valid_serial_nos
from erpnext.utilities.transaction_base import TransactionBase
class InstallationNote(TransactionBase):
def __init__(self, arg1, arg2=None):
super(InstallationNote, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(InstallationNote, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Installation Note Item',
'target_dt': 'Delivery Note Item',

View File

@@ -32,7 +32,7 @@ class Quotation(SellingController):
self.validate_valid_till()
if self.items:
self.with_items = 1
def validate_valid_till(self):
if self.valid_till and self.valid_till < self.transaction_date:
frappe.throw(_("Valid till date cannot be before transaction date"))
@@ -79,15 +79,10 @@ class Quotation(SellingController):
else:
frappe.throw(_("Cannot set as Lost as Sales Order is made."))
def check_item_table(self):
if not self.get('items'):
frappe.throw(_("Please enter item details"))
def on_submit(self):
self.check_item_table()
# Check for Approving Authority
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total, self)
#update enquiry status
self.update_opportunity()

View File

@@ -22,8 +22,8 @@ form_grid_templates = {
class WarehouseRequired(frappe.ValidationError): pass
class SalesOrder(SellingController):
def __init__(self, arg1, arg2=None):
super(SalesOrder, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(SalesOrder, self).__init__(*args, **kwargs)
def validate(self):
super(SalesOrder, self).validate()
@@ -696,7 +696,8 @@ def make_purchase_order_for_drop_shipment(source_name, for_supplier, target_doc=
"contact_display",
"contact_mobile",
"contact_email",
"contact_person"
"contact_person",
"taxes_and_charges"
],
"validation": {
"docstatus": ["=", 1]

View File

@@ -494,7 +494,7 @@ class TestSalesOrder(unittest.TestCase):
so.items[0].price_list_rate = price_list_rate = 100
so.items[0].margin_type = 'Percentage'
so.items[0].margin_rate_or_amount = 25
so.insert()
so.save()
new_so = frappe.copy_doc(so)
new_so.save(ignore_permissions=True)

View File

@@ -49,11 +49,12 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.set_online_status();
},
() => this.setup_pos_profile(),
() => this.make_new_invoice(),
() => {
frappe.timeout(1);
this.make_items();
this.bind_events();
},
() => this.make_new_invoice(),
() => this.page.set_title(__('Point of Sale'))
]);
}
@@ -89,6 +90,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.cart = new POSCart({
frm: this.frm,
wrapper: this.wrapper.find('.cart-container'),
pos_profile: this.pos_profile,
events: {
on_customer_change: (customer) => this.frm.set_value('customer', customer),
on_field_change: (item_code, field, value) => {
@@ -98,6 +100,17 @@ erpnext.pos.PointOfSale = class PointOfSale {
if (value == 'Pay') {
if (!this.payment) {
this.make_payment_modal();
} else {
const mop_field = this.payment.default_mop;
let amount = 0.0;
this.frm.doc.payments.map(p => {
if (p.mode_of_payment == mop_field) {
amount = p.amount;
return;
}
});
this.payment.dialog.set_value(mop_field, flt(amount));
}
this.payment.open_modal();
}
@@ -196,6 +209,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.update_item_in_frm(item)
.then(() => {
// update cart
this.remove_item_from_cart(item);
this.update_cart_data(item);
});
}, true);
@@ -208,6 +222,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
update_item_in_frm(item, field, value) {
if (field == 'qty' && value < 0) {
frappe.msgprint(__("Quantity must be positive"));
value = item.qty;
}
if (field) {
frappe.model.set_value(item.doctype, item.name, field, value);
}
@@ -215,12 +234,18 @@ erpnext.pos.PointOfSale = class PointOfSale {
return this.frm.script_manager
.trigger('qty', item.doctype, item.name)
.then(() => {
if (field === 'qty' && value === 0) {
frappe.model.clear_doc(item.doctype, item.name);
if (field === 'qty') {
this.remove_item_from_cart(item);
}
});
}
remove_item_from_cart(item) {
if (item.qty === 0) {
frappe.model.clear_doc(item.doctype, item.name);
}
}
make_payment_modal() {
this.payment = new Payment({
frm: this.frm,
@@ -363,10 +388,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
};
class POSCart {
constructor({frm, wrapper, events}) {
constructor({frm, wrapper, pos_profile, events}) {
this.frm = frm;
this.wrapper = wrapper;
this.events = events;
this.pos_profile = pos_profile;
this.make();
this.bind_events();
}
@@ -428,6 +454,12 @@ class POSCart {
this.$taxes_and_totals.html(this.get_taxes_and_totals());
this.numpad && this.numpad.reset_value();
this.customer_field.set_value("");
this.wrapper.find('.grand-total-value').text(
format_currency(this.frm.doc.grand_total, this.frm.currency));
const customer = this.frm.doc.customer || this.pos_profile.customer;
this.customer_field.set_value(customer);
}
get_grand_total() {
@@ -491,7 +523,7 @@ class POSCart {
// Update totals
this.$taxes_and_totals.find('.net-total')
.html(format_currency(this.frm.doc.net_total, currency));
.html(format_currency(this.frm.doc.total, currency));
// Update taxes
const taxes_html = this.frm.doc.taxes.map(tax => {
@@ -514,6 +546,7 @@ class POSCart {
}
make_customer_field() {
let customer = this.frm.doc.customer || this.pos_profile['customer'];
this.customer_field = frappe.ui.form.make_control({
df: {
fieldtype: 'Link',
@@ -521,7 +554,6 @@ class POSCart {
fieldname: 'customer',
options: 'Customer',
reqd: 1,
default: this.frm.doc.customer,
onchange: () => {
this.events.on_customer_change(this.customer_field.get_value());
}
@@ -529,6 +561,10 @@ class POSCart {
parent: this.wrapper.find('.customer-field'),
render_input: true
});
if (customer) {
this.customer_field.set_value(customer);
}
}
make_numpad() {
@@ -733,28 +769,41 @@ class POSCart {
// });
this.wrapper.find('.additional_discount_percentage').on('change', (e) => {
const discount_percentage = flt(e.target.value,
precision("additional_discount_percentage"));
frappe.model.set_value(this.frm.doctype, this.frm.docname,
'additional_discount_percentage', e.target.value)
'additional_discount_percentage', discount_percentage)
.then(() => {
let discount_wrapper = this.wrapper.find('.discount_amount');
discount_wrapper.val(this.frm.doc.discount_amount);
discount_wrapper.val(flt(this.frm.doc.discount_amount,
precision('discount_amount')));
discount_wrapper.trigger('change');
});
});
this.wrapper.find('.discount_amount').on('change', (e) => {
const discount_amount = flt(e.target.value, precision('discount_amount'));
frappe.model.set_value(this.frm.doctype, this.frm.docname,
'discount_amount', flt(e.target.value));
'discount_amount', discount_amount);
this.frm.trigger('discount_amount')
.then(() => {
let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
discount_wrapper.val(this.frm.doc.additional_discount_percentage);
this.update_discount_fields();
this.update_taxes_and_totals();
this.update_grand_total();
});
});
}
update_discount_fields() {
let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
let discount_amt_wrapper = this.wrapper.find('.discount_amount');
discount_wrapper.val(flt(this.frm.doc.additional_discount_percentage,
precision('additional_discount_percentage')));
discount_amt_wrapper.val(flt(this.frm.doc.discount_amount,
precision('discount_amount')));
}
set_selected_item($item) {
this.selected_item = $item;
this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
@@ -818,7 +867,7 @@ class POSItems {
this.search_field = frappe.ui.form.make_control({
df: {
fieldtype: 'Data',
label: 'Search Item (Ctrl + I)',
label: 'Search Item ( Ctrl + i )',
placeholder: 'Search by item code, serial number, batch no or barcode'
},
parent: this.wrapper.find('.search-field'),
@@ -899,6 +948,7 @@ class POSItems {
if (this.search_index[search_term]) {
const items = this.search_index[search_term];
this.render_items(items);
this.set_item_in_the_cart(items);
return;
}
} else if (item_group == "All Item Groups") {
@@ -912,19 +962,37 @@ class POSItems {
}
this.render_items(items);
if(serial_no) {
this.events.update_cart(items[0].item_code,
'serial_no', serial_no);
this.search_field.set_value('');
}
if(batch_no) {
this.events.update_cart(items[0].item_code,
'batch_no', serial_no);
this.search_field.set_value('');
}
this.set_item_in_the_cart(items, serial_no, batch_no);
});
}
set_item_in_the_cart(items, serial_no, batch_no) {
if (serial_no) {
this.events.update_cart(items[0].item_code,
'serial_no', serial_no);
this.reset_search_field();
return;
}
if (batch_no) {
this.events.update_cart(items[0].item_code,
'batch_no', batch_no);
this.reset_search_field();
return;
}
if (items.length === 1) {
this.events.update_cart(items[0].item_code,
'qty', '+1');
this.reset_search_field();
}
}
reset_search_field() {
this.search_field.set_value('');
this.search_field.$input.trigger("input");
}
bind_events() {
var me = this;
this.wrapper.on('click', '.pos-item-wrapper', function() {
@@ -1166,6 +1234,10 @@ class Payment {
const me = this;
let fields = this.frm.doc.payments.map(p => {
if (p.default) {
this.default_mop = p.mode_of_payment;
}
return {
fieldtype: 'Currency',
label: __(p.mode_of_payment),

View File

@@ -3,12 +3,15 @@
from __future__ import unicode_literals
import frappe, json
from frappe.utils.nestedset import get_root_of
@frappe.whitelist()
def get_items(start, page_length, price_list, item_group, search_value=""):
serial_no = ""
batch_no = ""
item_code = search_value
if not frappe.db.exists('Item Group', item_group):
item_group = get_root_of('Item Group')
if search_value:
# search serial no
@@ -21,6 +24,8 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
if batch_no_data:
batch_no, item_code = batch_no_data
item_code, condition = get_conditions(item_code, serial_no, batch_no)
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
# locate function is used to sort by closest match from the beginning of the value
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
@@ -33,11 +38,11 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
and (i.item_code like %(item_code)s
or i.item_name like %(item_code)s or i.barcode like %(item_code)s)
limit {start}, {page_length}""".format(start=start, page_length=page_length, lft=lft, rgt=rgt),
and {condition}
limit {start}, {page_length}""".format(start=start,
page_length=page_length, lft=lft, rgt=rgt, condition=condition),
{
'item_code': '%%%s%%'%(frappe.db.escape(item_code)),
'item_code': item_code,
'price_list': price_list
} , as_dict=1)
@@ -57,6 +62,15 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
return res
def get_conditions(item_code, serial_no, batch_no):
if serial_no or batch_no:
return frappe.db.escape(item_code), "i.item_code = %(item_code)s"
condition = """(i.item_code like %(item_code)s
or i.item_name like %(item_code)s or i.barcode like %(item_code)s)"""
return '%%%s%%'%(frappe.db.escape(item_code)), condition
@frappe.whitelist()
def submit_invoice(doc):
if isinstance(doc, basestring):

View File

@@ -75,11 +75,7 @@ class Company(Document):
if not frappe.local.flags.ignore_chart_of_accounts:
self.create_default_accounts()
self.create_default_warehouses()
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
# In the case of setup, fixtures should be installed after setup_success
# This also prevents db commits before setup is successful
install_country_fixtures(self.name)
install_country_fixtures(self.name)
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
self.create_default_cost_center()

View File

@@ -1,9 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe, unittest
from erpnext.setup.utils import get_exchange_rate
test_records = frappe.get_test_records('Currency Exchange')
@@ -28,11 +28,21 @@ def save_new_records(test_records):
class TestCurrencyExchange(unittest.TestCase):
def test_exchnage_rate(self):
from erpnext.setup.utils import get_exchange_rate
def clear_cache(self):
cache = frappe.cache()
key = "currency_exchange_rate:{0}:{1}".format("USD", "INR")
cache.delete(key)
def tearDown(self):
frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
self.clear_cache()
def test_exchange_rate(self):
save_new_records(test_records)
frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
# Start with allow_stale is True
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01")
self.assertEqual(exchange_rate, 60.0)
@@ -43,6 +53,51 @@ class TestCurrencyExchange(unittest.TestCase):
self.assertEqual(exchange_rate, 62.9)
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
self.assertFalse(exchange_rate == 60)
self.assertEqual(exchange_rate, 66.894)
self.assertEqual(exchange_rate, 66.894)
def test_exchange_rate_strict(self):
# strict currency settings
frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01")
self.assertEqual(exchange_rate, 60.0)
# Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
self.assertEqual(exchange_rate, 67.79)
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30")
self.assertEqual(exchange_rate, 62.9)
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
self.assertEqual(exchange_rate, 66.894)
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10")
self.assertEqual(exchange_rate, 65.1)
# NGN is not available on fixer.io so these should return 0
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09")
self.assertEqual(exchange_rate, 0)
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11")
self.assertEqual(exchange_rate, 0)
def test_exchange_rate_strict_switched(self):
# Start with allow_stale is True
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
self.assertEqual(exchange_rate, 65.1)
frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
# Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
self.assertEqual(exchange_rate, 67.79)

View File

@@ -33,5 +33,12 @@
"exchange_rate": 62.9,
"from_currency": "USD",
"to_currency": "INR"
},
{
"doctype": "Currency Exchange",
"date": "2016-01-10",
"exchange_rate": 65.1,
"from_currency": "INR",
"to_currency": "NGN"
}
]

View File

@@ -16,14 +16,13 @@ user_specific_content = ["calendar_events", "todo_list"]
from frappe.model.document import Document
class EmailDigest(Document):
def __init__(self, arg1, arg2=None):
super(EmailDigest, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(EmailDigest, self).__init__(*args, **kwargs)
self.from_date, self.to_date = self.get_from_to_date()
self.set_dates()
self._accounts = {}
self.currency = frappe.db.get_value("Company", self.company,
"default_currency")
self.currency = frappe.db.get_value("Company", self.company, "default_currency")
def get_users(self):
"""get list of users"""

View File

@@ -191,21 +191,21 @@ def create_healthcare_item_groups():
def create_lab_test_items():
records = [
{"doctype": "Item", "item_code": "MCH", "item_name": "MCH", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "LDL", "item_name": "LDL", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "GTT", "item_name": "GTT", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "HDL", "item_name": "HDL", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "BILT", "item_name": "BILT", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "BILD", "item_name": "BILD", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "BP", "item_name": "BP", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
{"doctype": "Item", "item_code": "BS", "item_name": "BS", "item_group": "Laboratory",
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1}
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1}
]
insert_record(records)

View File

@@ -33,6 +33,7 @@ def setup_complete(args=None):
create_feed_and_todo()
create_email_digest()
create_letter_head(args)
set_no_copy_fields_in_variant_settings()
if args.get('domain').lower() == 'education':
create_academic_year()
@@ -65,10 +66,6 @@ def setup_complete(args=None):
pass
def setup_success(args=None):
company = frappe.db.sql("select name from tabCompany", as_dict=True)[0]["name"]
install_country_fixtures(company)
def create_fiscal_year_and_company(args):
if (args.get('fy_start_date')):
curr_fiscal_year = get_fy_details(args.get('fy_start_date'), args.get('fy_end_date'))
@@ -354,6 +351,12 @@ def create_letter_head(args):
fileurl = save_file(filename, content, "Letter Head", _("Standard"), decode=True).file_url
frappe.db.set_value("Letter Head", _("Standard"), "content", "<img src='%s' style='max-width: 100%%;'>" % fileurl)
def set_no_copy_fields_in_variant_settings():
# set no copy fields of an item doctype to item variant settings
doc = frappe.get_doc('Item Variant Settings')
doc.set_default_fields()
doc.save()
def create_logo(args):
if args.get("attach_logo"):
attach_logo = args.get("attach_logo").split(",")

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils import flt, add_days
from frappe.utils import get_datetime_str, nowdate
def get_root_of(doctype):
@@ -56,8 +56,6 @@ def before_tests():
@frappe.whitelist()
def get_exchange_rate(from_currency, to_currency, transaction_date=None):
if not transaction_date:
transaction_date = nowdate()
if not (from_currency and to_currency):
# manqala 19/09/2016: Should this be an empty return or should it throw and exception?
return
@@ -65,13 +63,27 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None):
if from_currency == to_currency:
return 1
if not transaction_date:
transaction_date = nowdate()
currency_settings = frappe.get_doc("Accounts Settings").as_dict()
allow_stale_rates = currency_settings.get("allow_stale")
filters = [
["date", "<=", get_datetime_str(transaction_date)],
["from_currency", "=", from_currency],
["to_currency", "=", to_currency]
]
if not allow_stale_rates:
stale_days = currency_settings.get("stale_days")
checkpoint_date = add_days(transaction_date, -stale_days)
filters.append(["date", ">", get_datetime_str(checkpoint_date)])
# cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency.
entries = frappe.get_all("Currency Exchange", fields = ["exchange_rate"],
filters=[
["date", "<=", get_datetime_str(transaction_date)],
["from_currency", "=", from_currency],
["to_currency", "=", to_currency]
], order_by="date desc", limit=1)
entries = frappe.get_all(
"Currency Exchange", fields=["exchange_rate"], filters=filters, order_by="date desc",
limit=1)
if entries:
return flt(entries[0].exchange_rate)
@@ -108,8 +120,9 @@ def enable_all_roles_and_domains():
_role.save()
# add all roles to users
user = frappe.get_doc("User", "Administrator")
user.add_roles(*[role.get("name") for role in roles])
if roles:
user = frappe.get_doc("User", "Administrator")
user.add_roles(*[role.get("name") for role in roles])
domains = frappe.get_list("Domain")
if not domains:

View File

@@ -103,7 +103,7 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
def set_batch_nos(doc, warehouse_field, throw = False):
'''Automatically select `batch_no` for outgoing items in item table'''
for d in doc.items:
qty = d.get('stock_qty') or d.get('qty') or 0
qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
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 warehouse and qty > 0:

View File

@@ -21,8 +21,8 @@ form_grid_templates = {
}
class DeliveryNote(SellingController):
def __init__(self, arg1, arg2=None):
super(DeliveryNote, self).__init__(arg1, arg2)
def __init__(self, *args, **kwargs):
super(DeliveryNote, self).__init__(*args, **kwargs)
self.status_updater = [{
'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item',

View File

@@ -97,6 +97,12 @@ frappe.ui.form.on("Item", {
}
frappe.set_route('Form', 'Item', new_item.name);
});
if(frm.doc.has_variants) {
frm.add_custom_button(__("Item Variant Settings"), function() {
frappe.set_route("Form", "Item Variant Settings");
}, __("View"));
}
},
validate: function(frm){

View File

@@ -1473,6 +1473,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_uom",
"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": "Default Purchase Unit of Measure",
"length": 0,
"no_copy": 0,
"options": "UOM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -2069,6 +2100,37 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_uom",
"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": "Default Sales Unit of Measure",
"length": 0,
"no_copy": 0,
"options": "UOM",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -3143,7 +3205,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
"modified": "2017-07-06 18:28:36.645217",
"modified": "2017-09-27 14:08:02.948326",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -100,6 +100,7 @@ class Item(WebsiteGenerator):
def on_update(self):
invalidate_cache_for_item(self)
self.validate_name_with_item_group()
self.update_variants()
self.update_item_price()
self.update_template_item()
@@ -607,9 +608,24 @@ class Item(WebsiteGenerator):
if not template_item.show_in_website:
template_item.show_in_website = 1
template_item.flags.dont_update_variants = True
template_item.flags.ignore_permissions = True
template_item.save()
def update_variants(self):
if self.flags.dont_update_variants:
return
if self.has_variants:
updated = []
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
for d in variants:
variant = frappe.get_doc("Item", d)
copy_attributes_to_variant(self, variant)
variant.save()
updated.append(d.item_code)
if updated:
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
def validate_has_variants(self):
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
if frappe.db.exists("Item", {"variant_of": self.name}):

View File

@@ -119,6 +119,39 @@ class TestItem(unittest.TestCase):
variant.item_code = "_Test Variant Item-L-duplicate"
self.assertRaises(ItemVariantExistsError, variant.save)
def test_copy_fields_from_template_to_variants(self):
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XL", force=1)
fields = [{'field_name': 'item_group'}, {'field_name': 'is_stock_item'}]
allow_fields = [d.get('field_name') for d in fields]
set_item_variant_settings(fields)
if not frappe.db.get_value('Item Attribute Value',
{'parent': 'Test Size', 'attribute_value': 'Extra Large'}, 'name'):
item_attribute = frappe.get_doc('Item Attribute', 'Test Size')
item_attribute.append('item_attribute_values', {
'attribute_value' : 'Extra Large',
'abbr': 'XL'
})
item_attribute.save()
variant = create_variant("_Test Variant Item", {"Test Size": "Extra Large"})
variant.item_code = "_Test Variant Item-XL"
variant.item_name = "_Test Variant Item-XL"
variant.save()
template = frappe.get_doc('Item', '_Test Variant Item')
template.item_group = "_Test Item Group D"
template.save()
variant = frappe.get_doc('Item', '_Test Variant Item-XL')
for fieldname in allow_fields:
self.assertEquals(template.get(fieldname), variant.get(fieldname))
template = frappe.get_doc('Item', '_Test Variant Item')
template.item_group = "_Test Item Group Desktops"
template.save()
def test_make_item_variant_with_numeric_values(self):
# cleanup
for d in frappe.db.get_all('Item', filters={'variant_of':
@@ -194,6 +227,9 @@ class TestItem(unittest.TestCase):
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
def test_item_variant_by_manufacturer(self):
fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}]
set_item_variant_settings(fields)
if frappe.db.exists('Item', '_Test Variant Mfg'):
frappe.delete_doc('Item', '_Test Variant Mfg')
if frappe.db.exists('Item', '_Test Variant Mfg-1'):
@@ -227,6 +263,10 @@ class TestItem(unittest.TestCase):
self.assertEquals(variant.manufacturer, 'MSG1')
self.assertEquals(variant.manufacturer_part_no, '007')
def set_item_variant_settings(fields):
doc = frappe.get_doc('Item Variant Settings')
doc.set('fields', fields)
doc.save()
def make_item_variant():
if not frappe.db.exists("Item", "_Test Variant Item-S"):

View File

@@ -1,5 +1,5 @@
{
"allow_copy": 0,
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "ITEM-PRICE-.#####",
@@ -165,7 +165,7 @@
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
@@ -357,19 +357,19 @@
"set_only_once": 0,
"unique": 0
}
],
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-flag",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-20 13:27:23.896148",
"modified": "2017-09-28 03:56:20.814993",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Price",
@@ -422,6 +422,6 @@
"show_name_in_global_search": 0,
"sort_order": "ASC",
"title_field": "item_code",
"track_changes": 0,
"track_changes": 1,
"track_seen": 0
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Item Variant Settings', {
setup: function(frm) {
const allow_fields = [];
const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"];
frappe.model.with_doctype('Item', () => {
frappe.get_meta('Item').fields.forEach(d => {
if(!in_list(['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'], d.fieldtype)
&& !d.no_copy && !in_list(exclude_fields, d.fieldname)) {
allow_fields.push(d.fieldname);
}
});
const child = frappe.meta.get_docfield("Variant Field", "field_name", frm.doc.name);
child.options = allow_fields;
});
}
});

View File

@@ -0,0 +1,143 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-08-29 16:38:31.173830",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "copy_fields_to_variant",
"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": "Copy Fields to Variant",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "fields",
"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": "Fields",
"length": 0,
"no_copy": 0,
"options": "Variant Field",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-11 12:05:16.288601",
"modified_by": "rohit@erpnext.com",
"module": "Stock",
"name": "Item Variant Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "Item Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ItemVariantSettings(Document):
def set_default_fields(self):
self.fields = []
fields = frappe.get_meta('Item').fields
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
"variant_of", "valuation_rate", "description",
"website_image", "thumbnail", "website_specifiations", "web_long_description"]
for d in fields:
if not d.no_copy and d.fieldname not in exclude_fields and \
d.fieldtype not in ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only']:
self.append('fields', {
'field_name': d.fieldname
})

Some files were not shown because too many files have changed in this diff Show More