Compare commits

...

207 Commits

Author SHA1 Message Date
mbauskar
9d5b1b0e8f Merge branch 'hotfix' 2017-07-28 16:03:52 +05:30
mbauskar
2b420f7038 bumped to version 8.6.4 2017-07-28 16:33:52 +06:00
rohitwaghchaure
353af64197 [Fix] Unable to save asset because of float error issue (#10157) 2017-07-28 15:55:46 +05:30
Makarand Bauskar
8bccaed35a Revert "[Fix] Error in sales invoice and POS if customer group not defined in the customer (#10148)" (#10159)
This reverts commit 4d2e782e42.
2017-07-28 15:43:23 +05:30
rohitwaghchaure
4d2e782e42 [Fix] Error in sales invoice and POS if customer group not defined in the customer (#10148) 2017-07-28 15:21:22 +05:30
Saurabh
734e635ef6 Merge branch 'hotfix' 2017-07-27 17:50:27 +05:30
Saurabh
6f9ef5b890 bumped to version 8.6.3 2017-07-27 18:20:27 +06:00
Makarand Bauskar
145393b12f [hotfix] set the account name in GL entry instead of warehouse (#10134) 2017-07-27 16:46:40 +05:30
Makarand Bauskar
9c6e2c3637 [hotfix] included the get_purchase_trends_filters instead of get_sales_trends_filters in Purchase Receipts trends (#10127) 2017-07-27 16:23:57 +05:30
mbauskar
cfc2693b2e Merge branch 'hotfix' 2017-07-27 12:18:15 +05:30
mbauskar
448d919cc1 bumped to version 8.6.2 2017-07-27 12:48:15 +06:00
tundebabzy
87ec6a12ef adds options to currency fields (#10118) 2017-07-27 11:38:17 +05:30
rohitwaghchaure
059f99e621 [Fix] Wrong avg. buying rate in the Gross Profit report (#10110) 2017-07-27 11:37:30 +05:30
Saurabh
4c28fa77bd Merge branch 'hotfix' 2017-07-25 18:02:58 +05:30
Saurabh
394dbca0e4 bumped to version 8.6.1 2017-07-25 18:32:58 +06:00
Saurabh
33ebd9f88e Merge pull request #10088 from saurabh6790/hot_fix_v_8_6_0
[hot][fix] updated modified date for sales order
2017-07-25 17:58:12 +05:30
Saurabh
4c1caa7e98 [hot][fix] updated modified date for sales order 2017-07-25 17:35:14 +05:30
Saurabh
08c3b3c925 Merge branch 'staging' 2017-07-25 16:42:27 +05:30
Saurabh
bb5812cf0f bumped to version 8.6.0 2017-07-25 17:12:26 +06:00
Saurabh
36645e4e2f Merge branch 'master' into staging 2017-07-25 16:15:51 +05:30
Saurabh
b296bb1551 Merge branch 'hotfix' 2017-07-25 16:15:50 +05:30
Saurabh
e7c14fcc3d bumped to version 8.5.5 2017-07-25 16:45:49 +06:00
Saurabh
31bb34bbae Merge pull request #10081 from rohitwaghchaure/calendar_view_SO_issue_fix
[Fix] Calendar view for sales order
2017-07-25 15:54:09 +05:30
Saurabh
6436b9d089 Merge pull request #10080 from rohitwaghchaure/hotfix
[Fix] Negative amount showing in the grand total for multicurrency if discount has applied
2017-07-25 15:43:00 +05:30
Rohit Waghchaure
d62fa84ed9 [Fix] Calendar view for sales order 2017-07-25 15:34:20 +05:30
Rohit Waghchaure
baa937aa52 [Fix] Negative amount showing in the grand total for multicurrency if discount has applied 2017-07-25 15:26:01 +05:30
Frappe PR Bot
3b128cabb2 [Translation] Updated Translations (#10047) 2017-07-25 11:46:16 +05:30
Makarand Bauskar
dfc5a454b3 [minor] fixed the delivery date issue in Ordered Item to be Dellivered report (#10028) 2017-07-25 10:48:43 +05:30
bcornwellmott
1c32f5ace9 Whitelist method for adding production orders ops (#9997) 2017-07-24 22:43:44 +05:30
Prateeksha Singh
283d5550e6 [fix] set sales base field as base_grand_total (#10008) 2017-07-21 14:21:35 +05:30
Rushabh Mehta
dda608dd00 [fix] update_company_current_month_sales in company.py (#10005) 2017-07-21 11:58:14 +05:30
Faris Ansari
e355f99786 Remove ellipsis in title and description (#9992) 2017-07-20 17:51:19 +05:30
mbauskar
27334c28a9 Merge branch 'hotfix' 2017-07-20 16:49:30 +05:30
mbauskar
6c8d4678db Merge branch 'master' into develop 2017-07-20 16:49:30 +05:30
mbauskar
ea50c9d1be bumped to version 8.5.4 2017-07-20 17:19:30 +06:00
Rushabh Mehta
35da7d1fb4 [minor] we buy is also checked by default 2017-07-20 15:58:20 +05:30
Rushabh Mehta
ced14cc789 [fix] setup-wizard 2017-07-20 15:55:54 +05:30
Rushabh Mehta
9d27cf3c62 [tests] refactored (#9984)
* [tests] refactored

* [fix] test_quotation.js

* [fix] tests.text

* [fix] fiscal year not needed

* [test] add long test

* [fix] add timeout in lead
2017-07-20 15:35:03 +05:30
mbauskar
0e6933a1e8 resolved merge conflicts 2017-07-20 14:19:47 +05:30
mbauskar
51a76885b8 Merge branch 'hotfix' 2017-07-20 14:16:57 +05:30
mbauskar
a919be111a bumped to version 8.5.3 2017-07-20 14:46:57 +06:00
Nabin Hait
9c42161061 Itemised tax breakup fix in docs other than invoice (#9961)
* Itemised tax breakup fix in docs other than invoice

* Set itemised tax breakup and hsn code in existing docs
2017-07-20 13:32:01 +05:30
rohitwaghchaure
79d6266c7b [Fix] Unable to create production order from the sales order for the bundle items (#9976) 2017-07-20 10:35:51 +05:30
tundebabzy
11d23f84d7 Can't set Start and End Dates in Salary Slip (#9513) (#9944)
* remove trigger from end_date

* adds new function `get_end_date`:
- it tries to calculate the appropriate end date for a given frequency
- returns an empty string if frequency is 'biweekly'
- adds test cases

* changes logic in `set_start_end_dates`:
- if start_date is empty in form, call process_payroll.get_start_end_dates
- else, call process_payroll.get_end_date

* `get_end_date` should return a dict

* changed "biweekly" to "bimonthly"

* change the behaviour of process payroll start and end date:
- when payroll frequency is changed, change start/end date as usual
- if start date is manually changed, use the frequency to calculate the end date

* clean up

* further cleanup

* in `get_end_date`, if `frequency` isn"t given, "monthly"

* remove end_date from cscript and introduce `set_end_date`

* fix tests

* removed whitespaces
2017-07-20 10:33:03 +05:30
Nabin Hait
8e0f23efc7 Multiple delivery dates in Sales Order and make DN based on selected delivery dates (#9933)
* Multiple delivery dates in Sales Order and make DN based on selected delivery dates

* Test case and some other minor fixes

* Updated docs for multi delivery date

* removed the trailing whitespace

* removed the trailing whitespace

* removed trailing whitespace
2017-07-20 10:30:59 +05:30
Makarand Bauskar
ac9b1332d2 [minor] set no copy to sales goal fields (#9956) 2017-07-19 18:55:12 +05:30
Doridel Cahanap
7b021e0fac Show Expected End Date in Project List View (#9964) 2017-07-19 18:21:31 +05:30
KanchanChauhan
d6dd25a666 Added indicators on Project Tasks (#9952) 2017-07-19 18:06:52 +05:30
rohitwaghchaure
f86100a734 [Fix] Calendar view not working for leave application (#9963) 2017-07-19 17:09:30 +05:30
_JG_
96bb6099d6 [fix] correctly choice 'Credit Limit' (#9932)
* [fix] correctly choice 'credit limit'

If 'Credit Days Based On' is empty in 'Supplier Type/Customer Group' then value 'Due Date' in 'Purchase/Sales Invoice' doesn't taken from the default settings

* Update party.py

* [fix] correctly choice 'Credit Limit'
2017-07-19 10:27:09 +05:30
Nabin Hait
e6c2ae3682 Merge pull request #9940 from rmehta/workstation-form
[ux] workstation form layout
2017-07-19 10:24:28 +05:30
Nabin Hait
8d56f2959b Merge pull request #9942 from bcornwellmott/bix_bom_qty
Update qty from stock_qty before validate materials
2017-07-19 10:22:00 +05:30
Rushabh Mehta
d2d24554b3 [ux] workstation form layout 2017-07-18 22:29:59 +05:30
Ben Cornwell-Mott
c7d2bc67e8 Update qty from stock_qty before validate materials 2017-07-18 08:56:25 -07:00
Rushabh Mehta
1283f6308d [fix] company graph based on base currency 2017-07-18 18:22:16 +05:30
mbauskar
d36c136fc6 Merge branch 'master' into develop 2017-07-18 16:38:24 +05:30
mbauskar
3951f6971e Merge branch 'hotfix' 2017-07-18 16:38:23 +05:30
mbauskar
75e65e7079 bumped to version 8.5.2 2017-07-18 17:08:23 +06:00
Saurabh
7f95d587b2 [fix] escape company filter (#9924) 2017-07-18 16:09:34 +05:30
Makarand Bauskar
75b145fe2c [hotfix] used frappe.db.set value instead of frappe.set_value (#9923) 2017-07-18 16:05:52 +05:30
Nabin Hait
bc3acdd0ba Merge pull request #9510 from tundebabzy/issue-9166
Unable to create Manual Depreciation of Asset due to Error #9166
2017-07-18 15:44:18 +05:30
mbauskar
8f42f60dc9 Merge branch 'hotfix' 2017-07-18 15:10:19 +05:30
mbauskar
823b3ca540 bumped to version 8.5.1 2017-07-18 15:40:19 +06:00
Makarand Bauskar
5e75e3ba03 [hotfix] set_restrict_to_domain_for_module_def patch fixes (#9921) 2017-07-18 15:09:30 +05:30
mbauskar
113df55e64 Merge branch 'develop' 2017-07-18 13:07:01 +05:30
mbauskar
3e4b2743c6 bumped to version 8.5.0 2017-07-18 13:37:00 +06:00
Nabin Hait
338c28e78e Merge pull request #9902 from rohitwaghchaure/sales_invoice_serial_no_issue_from_dn
[Fix] Sales invoice serial no validation issue
2017-07-18 13:05:50 +05:30
Rohit Waghchaure
7e14996995 [Fix] Sales invoice serial no validation issue 2017-07-18 12:59:55 +05:30
Makarand Bauskar
6e30f04181 [domainify] patch to set the restrict to domain for module_def (#9912) 2017-07-18 12:30:57 +05:30
Nabin Hait
4a10f18ee3 Merge pull request #9913 from mbauskar/quotation
[minor] fixes for TypeError: get_lead_details() takes at least 1 argument (2 given)
2017-07-18 12:20:47 +05:30
Nabin Hait
f37d43d0c1 Remove newline from serial no values 2017-07-18 12:15:16 +05:30
pratu16x7
aea60f349f [minor] default qty 0, fixes frappe/erpnext#9880 2017-07-18 12:15:16 +05:30
pratu16x7
90bd5681d1 [batch modal] bind serial_no field in onchange 2017-07-18 12:15:16 +05:30
mbauskar
30e03cc4c8 [minor] fixes for TypeError: get_lead_details() takes at least 1 argument (2 given) 2017-07-18 11:45:53 +05:30
Nabin Hait
4c40a416e6 Merge pull request #9900 from frappe/fixes_9899
[fix] #9899
2017-07-18 11:17:45 +05:30
Nabin Hait
3020c8086c Update stock_balance.py 2017-07-18 11:17:32 +05:30
Nabin Hait
8b3ef1e70a Merge pull request #9896 from rohitwaghchaure/rejected_expense_claim_issue
[Fix] Expense claim status issue
2017-07-18 11:10:52 +05:30
Makarand Bauskar
c446bf6117 Merge branch 'develop' into rejected_expense_claim_issue 2017-07-18 10:54:13 +05:30
Rushabh Mehta
660de515b5 [fix] filters for calendars frappe/erpnext#9850 (#9870) 2017-07-18 10:50:30 +05:30
Prateeksha Singh
e012e24423 Sales Goal by Company (#9723)
* [sales goal] in company; dashboard, graph, notifs, wiz

* [test] target notifications

* cache past year monthly sales of every company daily, patch

* [minor] query fixes

* update sales goal docs
2017-07-18 10:35:12 +05:30
Nabin Hait
e2d0d0a0c1 Merge pull request #9904 from nabinhait/hotfix777
Removed a deprecated function call
2017-07-17 20:29:55 +05:30
Nabin Hait
22e82dff20 Removed a deprecated function call 2017-07-17 20:28:30 +05:30
tunde
78d2f542d0 code fix as per review 2017-07-17 15:00:45 +01:00
Nabin Hait
b962fc1573 Show hsn code in tax breakup for India and render via template (#9866)
* Show hsn code in tax breakup for India and render via template

* tax breakup if gst_tax_field does not exists

* Fixed tax-breakup test cases
2017-07-17 18:02:31 +05:30
tunde
1ee534889f Revert "adds if statement for reference_type === "Asset""
This reverts commit c7e3a09cfb.
2017-07-17 13:27:59 +01:00
Frappe PR Bot
fa04236c8d [Translation] Updated Translations (#9898) 2017-07-17 17:50:36 +05:30
pawan
36025468a1 [fix] #9899 2017-07-17 17:28:44 +05:30
Rohit Waghchaure
0e376a464b test cases 2017-07-17 16:47:01 +05:30
Rohit Waghchaure
8333b5754b [Fix] Expense claim status issue 2017-07-17 16:38:20 +05:30
Rushabh Mehta
dab1172a18 [refactor] party.js get_party_details (#9888) 2017-07-17 15:31:17 +05:30
rohitwaghchaure
ea4497c8d2 Renamed the report Support Hours to Support Hours Distribution (#9874) 2017-07-17 14:55:42 +05:30
rohitwaghchaure
b994b3dcda Allow to select asset account in the payable in the expense claim for imprest management (#9891) 2017-07-17 14:33:33 +05:30
Makarand Bauskar
805a41d06c [minor] fixed the set_portal_settings patch (#9890) 2017-07-17 13:44:56 +05:30
Nabin Hait
e06526ffff Add indexes in some transaction doctypes (#9889) 2017-07-17 13:28:27 +05:30
Nabin Hait
2df7db0346 Merge pull request #9884 from mbauskar/patch-fixes
[hotfix] fixed GST code for Uttarakhand
2017-07-17 13:04:54 +05:30
Narciso E. Núñez Arias
c9877c5c1e Translation of ERPNext manual .md files (#9872)
* Translate Do I Need An ERP page

* Translate getting started with erpnext Page

* Translate Implementation Strategy Page

* Translate Spanish Index page

* Translate Flow Chart of transactions page

* Translate open source page

* Translate The Champion Page

* Fix spanish translation on Index page
2017-07-17 12:10:47 +05:30
strixaluco
372a881d8c Make 'Financial Year' translatable in Setup wizard (#9879) 2017-07-17 12:06:25 +05:30
mbauskar
71b5250cbd [hotfix] fixed the state code for Uttarakhand 2017-07-16 21:28:39 +05:30
mbauskar
ece7881ab1 Merge branch 'hotfix' 2017-07-14 17:47:07 +05:30
mbauskar
3ceebaec3f Merge branch 'master' into develop 2017-07-14 17:47:07 +05:30
mbauskar
30e987a835 bumped to version 8.4.3 2017-07-14 18:17:07 +06:00
Makarand Bauskar
087da2e571 Fixed patch (#9862) (#9871) 2017-07-14 17:44:26 +05:30
Makarand Bauskar
ad7eb9d03c [minor] check mode_of_payment in Payment entry (#9869)
* [minor] don't trigger the expense type trigger if value is not set

* [minor] check if account is selected or not in Payment Entry

* [minor] check mode_of_payment in Payment entry
2017-07-14 17:31:36 +05:30
Nabin Hait
35d0de8276 Merge pull request #9858 from mbauskar/expense-claim
[minor] don't trigger the expense type trigger if value is not set
2017-07-14 15:28:34 +05:30
Rushabh Mehta
812853aa86 [refactor] account.js to new style (#9787)
* [fix] conference site update

* [test] run all js tests
2017-07-14 15:28:04 +05:30
KanchanChauhan
319c58266b Changes Quotes to Quotations in website sidebar because that seems more legit (#9825) 2017-07-14 14:30:42 +05:30
rohitwaghchaure
dcf10ee4f6 Fixed patch (#9862) 2017-07-14 13:02:38 +05:30
mbauskar
1394a6557d [minor] check mode_of_payment in Payment entry 2017-07-14 12:09:17 +05:30
mbauskar
00e825a8af [minor] check if account is selected or not in Payment Entry 2017-07-14 11:38:39 +05:30
mbauskar
ed89a83584 [minor] don't trigger the expense type trigger if value is not set 2017-07-14 11:21:27 +05:30
Faris Ansari
2c5b3e83f5 New design for daily work summary (#9844)
* New design for daily work summary

* Update tests
2017-07-13 18:37:18 +05:30
Rushabh Mehta
8e2531e2bb Update Test Runner to run tests one by one (#9843)
* [update] tests as per new api

* [test] unset test_quotation.js

* [test] unset test_quotation.js

* [test] unset test_quotation.js

* [test] unset test_quotation.js
2017-07-13 18:22:20 +05:30
Nabin Hait
d5dd9f1706 Merge pull request #9839 from rmehta/regional-decorators
[feature] override a function regionally by adding a decorator
2017-07-13 17:51:29 +05:30
mbauskar
394c4d718d Merge branch 'develop' 2017-07-13 16:16:21 +05:30
mbauskar
ae20748dec bumped to version 8.4.2 2017-07-13 16:46:20 +06:00
Rushabh Mehta
3df2c9421a [fix] setup wont be called if declared inside setup 2017-07-13 16:11:54 +05:30
Rushabh Mehta
393becce0b [fix] name decorator as allow_regional 2017-07-13 15:49:37 +05:30
mbauskar
777b16ffda Merge branch 'develop' 2017-07-13 15:14:29 +05:30
mbauskar
efaf9f59db bumped to version 8.4.1 2017-07-13 15:44:29 +06:00
Rushabh Mehta
8f2e21def2 [feature] override a function regionally by adding a decorator 2017-07-13 15:07:51 +05:30
Rushabh Mehta
7231f29e78 [feature] override a function regionally by adding a decorator 2017-07-13 15:00:56 +05:30
Nabin Hait
239c9387d1 Merge pull request #9838 from saurabh6790/patch_fix
[patch][fix] update gst_state only if company country is india
2017-07-13 14:49:58 +05:30
Saurabh
e74e4b18c7 [patch][fix] update gst_state only if company country is india 2017-07-13 14:38:43 +05:30
mbauskar
012f5b0a50 Merge branch 'develop' 2017-07-13 14:05:51 +05:30
mbauskar
05d62127d0 bumped to version 8.4.0 2017-07-13 14:35:51 +06:00
Nabin Hait
7549a83b9b Merge pull request #9801 from manassolanki/refractor-report
changes in the assessment report
2017-07-13 13:46:19 +05:30
rohitwaghchaure
c3153655eb Fixed patch create_warehouse_nestedset (#9833) 2017-07-13 12:17:12 +05:30
Makarand Bauskar
96488b0f34 [patch] removed create company address patch (#9832) 2017-07-13 12:16:28 +05:30
Nabin Hait
1a60931435 Added SAC codes and 2/4 digit HSN codes, regional setup can be called multiple times (#9820) 2017-07-13 12:16:04 +05:30
Makarand Bauskar
8f507a984e [minor] check if the company is selected or not before triggering get_bank_cash_account (#9830)
* [minor] check if the company is selected or not before triggering get_bank_cash_account

* [minor] check if production order is available

* [minor] fixed codacy errors
2017-07-13 11:44:29 +05:30
Nabin Hait
57d3cecd68 Merge pull request #9811 from rohitwaghchaure/pricing_rule_issue_for_discount
[Fix] Manual discount is not applying in the transaction if discount amount is zero in the pricing rule
2017-07-13 11:14:53 +05:30
Nabin Hait
de609a2fb6 Merge pull request #9815 from nabinhait/develop
Set company address while making invoice from SO, don't show taxes in print if amount is zero, fixed state code
2017-07-13 11:05:55 +05:30
Nabin Hait
7312186c76 Set corrected states list in GST State field's options 2017-07-13 10:41:15 +05:30
Nabin Hait
0a32b7a6eb Set company address while making invoice from SO, don't show taxes in print if amount is zero, fixed state code 2017-07-12 19:21:05 +05:30
rohitwaghchaure
c1a1e62c0d [Fix] Data not showing in the heatmap of the customer (#9819) 2017-07-12 18:57:23 +05:30
Rohit Waghchaure
b12f2109b5 [Fix] Manual discount is not applying on the pricing rule if discount amount is zero in the pricing rule 2017-07-12 18:02:02 +05:30
Manas Solanki
a13c6a1bef codacy cleanup 2017-07-12 17:49:59 +05:30
Manas Solanki
79ed58fd36 chnages in the assessment report 2017-07-12 17:49:59 +05:30
rohitwaghchaure
860144feb7 [Fix] User not able to search an Item if description is null in the item (#9793) 2017-07-12 15:01:56 +05:30
Makarand Bauskar
1efb05233c [minor] don't trigger source_warehouse if item_code is not set in Production Order (#9806) 2017-07-12 14:53:14 +05:30
Makarand Bauskar
ef8d6dc8f8 [minor] ingore mandatory while auto closing issues and opportunity (#9783) 2017-07-12 14:52:32 +05:30
Nabin Hait
79c2191aa3 [fix] Don't add newline at the end, onchange of serial_no and filter warehouse based on company and is_group (#9809) 2017-07-12 14:10:43 +05:30
Nabin Hait
f012a9db70 Merge pull request #9812 from adityaduggal/wrong_state_codes
Changed state codes and added state
2017-07-12 14:03:39 +05:30
Aditya Duggal
4e1a3c1d58 Changed state codes and added state
-Andaman 35 added
-UP changed to 09 from 35
-Uttrakhand changed to 05 from 36
2017-07-12 13:40:11 +05:30
Nabin Hait
fb8e59234b Merge pull request #9652 from bcornwellmott/sq_lead_time
Added lead time field to supplier quotation
2017-07-11 23:39:27 +05:30
Umair Sayed
31af0849db Update navigation.md (#9798) 2017-07-11 21:07:03 +05:30
Frappe PR Bot
bfdb726072 [translation] translation updates (#9788) 2017-07-11 15:20:11 +05:30
mbauskar
a8406e1544 Merge branch 'develop' 2017-07-11 13:02:15 +05:30
mbauskar
b2aa867b70 bumped to version 8.3.6 2017-07-11 13:32:14 +06:00
Makarand Bauskar
30f2bcbccc [hotfix] allow only 5 character in company_abbr field in setup wizard (#9779) 2017-07-11 12:36:57 +05:30
Prateeksha Singh
7d885432eb [wiz] add user slide, enforce first entry, static labels (#9744) 2017-07-11 12:36:34 +05:30
Nabin Hait
3b5f774144 Merge pull request #9410 from manassolanki/patch-company
Patch for the PR 8754, fixes #9011
2017-07-11 11:35:29 +05:30
Nabin Hait
3a200bbc44 Update create_address_doc_from_address_field_in_company.py 2017-07-11 11:34:27 +05:30
Nabin Hait
2bedca04ae Merge pull request #9750 from rohitwaghchaure/taxable_amount_issue
[Fix] Taxable amount in tax breakup showing wrong value for duplicate items in the invoice
2017-07-11 11:16:56 +05:30
Manas Solanki
1a0536bff4 changes as per review 2017-07-11 11:16:16 +05:30
Manas Solanki
cdba021802 patch for removing the address field from company and creating address doc, fixes #9011 2017-07-11 11:16:16 +05:30
Shivam Mishra
3fe5ecc611 Added Lead fixture, Added test for Lead, Opportunity (#9607)
* Added fixtures for Address and Contact

* Fixed formatting errors

* Added Test for terms, Added Tax and Terms fixture

* Add tests for tax

* Added Test for Print Format
2017-07-11 10:33:07 +05:30
Rohit Waghchaure
296fbfeaac [Fix] Taxable amount in tax breakup showing wrong value for duplicate items in the invoice 2017-07-10 23:06:49 +05:30
Rushabh Mehta
4a7b4efbec [tests] update travis.yml to run ui-tests (#9748)
* [tests] update travis.yml to run ui-tests

* [tests] update travis.yml to run ui-tests

* [test] fix item_group.py

* [test] fix item_group.py

* [check] daily work summary fails?

* [check] daily work summary fails?

* [check] daily work summary fails?

* [check] daily work summary fails?

* [check] daily work summary fails?

* [check] daily work summary fails?

* [check] no scheduled jobs for tests

* [check] daily work summary fails?

* [check] daily work summary fails?
2017-07-10 23:00:01 +05:30
Ben Cornwell-Mott
b1f0fd4ac3 Lead time matches Item master 2017-07-10 09:09:29 -07:00
Nabin Hait
f7d2a59c18 Merge pull request #9538 from sagarvora/fix-bom-issue
[minor] fix bom price resetting on validation
2017-07-10 20:41:16 +05:30
Nabin Hait
0ac8542eaa Merge pull request #9772 from tundebabzy/issue-9595
Wrong Exchange Rate when making payment against Journal Entry (#9595)
2017-07-10 19:12:48 +05:30
Nabin Hait
c8b6d3badb Merge pull request #9713 from manassolanki/stud-dashb
changes in the student dashboard
2017-07-10 19:10:57 +05:30
Nabin Hait
cdf4320b3b Merge pull request #9673 from rohitwaghchaure/serial_no_sales_return_issue
Remove sales invoice from serial number while making sales return entery using delivery note
2017-07-10 19:04:30 +05:30
Nabin Hait
5b2d3222f3 Merge pull request #9710 from rohitwaghchaure/sales_invoice_dn_link_revert
Revert sales invoice dn link issue
2017-07-10 18:47:38 +05:30
Manas Solanki
5d202ca31a change in the student type 2017-07-10 18:40:53 +05:30
Nabin Hait
7088db9e1f Merge pull request #9773 from mbauskar/docs
[docs] corrected the filename from sms_setting2 -> sms_settings2
2017-07-10 18:17:28 +05:30
Nabin Hait
ec344ffa96 Merge pull request #9761 from mbauskar/depends-on
[minor] fixed depends on for stock entry expense_account and purchase receipt item's cost_center field
2017-07-10 18:16:15 +05:30
mbauskar
a52e726b6b [docs] corrected the filename from sms_setting2 -> sms_settings2 2017-07-10 18:11:32 +05:30
Nabin Hait
cf82c3828e Merge pull request #9769 from manassolanki/payment-entry
frappe call if posting date is defined
2017-07-10 17:57:26 +05:30
Nabin Hait
6fcfbaa1f9 Merge pull request #9766 from nabinhait/migration_fixes
minor fixes while upgrading an old instance
2017-07-10 17:45:25 +05:30
Nabin Hait
a6c733d06c Merge pull request #9753 from rohitwaghchaure/budget_cost_center_issue
[Fix] Budget against the field in the validation message
2017-07-10 17:45:02 +05:30
tunde
46ef26df71 treat use case where Journal entry is contained in outstanding_invoices in get_outstanding_reference_documents function 2017-07-10 13:05:29 +01:00
Manas Solanki
99d571a786 frappe call if posting date is defined 2017-07-10 17:33:27 +05:30
Rohit Waghchaure
e3ae600277 [Fix] Budget against the field in the validation message 2017-07-10 17:06:54 +05:30
Nabin Hait
dd7a723214 minor fixes while upgrading an old instance 2017-07-10 16:41:59 +05:30
mbauskar
ccaf36a00f [minor] fixed depends on for stock entry expense_account and purchase receipt item's cost_center field 2017-07-10 16:24:24 +05:30
Manas Solanki
101a021f7b added student type in program enrollment and changes in the student dashboard 2017-07-10 16:21:44 +05:30
Nabin Hait
68ed0488a3 Merge pull request #9757 from mbauskar/breadcrumb-fixes
[hotfix] check if the item group exist or not
2017-07-10 15:32:52 +05:30
mbauskar
75a233b472 [hotfix] check if the item group exist or not 2017-07-10 14:36:42 +05:30
Nabin Hait
5a174d61bc Merge pull request #9754 from creamdory/develop
Added Quotation as Standard Sidebar Menu
2017-07-10 14:13:35 +05:30
creamdory
7a2815299e Added Quotation as Standard Sidebar Menu 2017-07-10 16:09:03 +08:00
Manas Solanki
195d2b577f add few details in the student log (#9670) 2017-07-10 12:29:46 +05:30
Britlog
8c85562ceb Breadcrumbs management (#9544) 2017-07-10 12:14:05 +05:30
Manas Solanki
77aa4762b8 don't request another ajax call if one is pending (#9620) 2017-07-10 12:12:16 +05:30
Nabin Hait
35ecab6a52 Merge branch 'develop' 2017-07-08 13:55:41 +05:30
Nabin Hait
097da8cc89 bumped to version 8.3.5 2017-07-08 14:25:41 +06:00
Nabin Hait
1d52a4df22 Merge pull request #9732 from adityaduggal/gstin_NA
First 2 digit validation would disregard NA values
2017-07-08 13:52:35 +05:30
Nabin Hait
cd61a20fb4 Update utils.py 2017-07-08 13:52:13 +05:30
Nabin Hait
8f7eb358b8 Merge pull request #9730 from nabinhait/prod_fix
Finish prod order when skipped material transfer
2017-07-08 13:50:52 +05:30
Aditya Duggal
0e285265b1 First 2 digit validation would disregard NA values 2017-07-08 13:35:01 +05:30
Nabin Hait
b866fcf14f Finish prod order when skipped material transfer 2017-07-08 12:57:13 +05:30
Manas Solanki
3d190a15ab changes in the student dashboard 2017-07-07 17:29:29 +05:30
mbauskar
ab59e4769b Merge branch 'develop' 2017-07-07 17:12:37 +05:30
mbauskar
7b5ca3e494 bumped to version 8.3.4 2017-07-07 17:42:36 +06:00
Rohit Waghchaure
8f2c8f6e9d Revert sales invoice dn link issue 2017-07-07 17:07:59 +05:30
Nabin Hait
76c5924cbe Merge pull request #9699 from manassolanki/productn
frappe call only when there is item in production order
2017-07-07 15:48:37 +05:30
Nabin Hait
6eb55042d8 Merge pull request #9704 from rohitwaghchaure/serial_no_not_found_issue
Fixed the issue Serial No serial no not found
2017-07-07 15:47:25 +05:30
Rohit Waghchaure
9589527784 Fixed the issue Serial No serial no not found 2017-07-07 15:34:28 +05:30
Nabin Hait
dba3f0048b Merge pull request #9702 from mbauskar/installation-note
[hotfix] fixed the customer address set filter on Installation Note
2017-07-07 15:28:23 +05:30
mbauskar
3f6a5b2539 [hotfix] fixed the customer address set filter on Installation Note 2017-07-07 15:24:01 +05:30
Manas Solanki
cf7f72e586 frappe call only when there is item 2017-07-07 15:09:57 +05:30
Rohit Waghchaure
934e69fe0b Remove sales invoice from serial number while making sales return entery using delivery note 2017-07-06 18:19:18 +05:30
Ben Cornwell-Mott
19d9381197 Added lead time field to supplier quotation 2017-07-05 13:17:13 -07:00
Sagar Vora
7640858510 [minor] fix bom price resetting on validation 2017-06-29 22:20:07 +05:30
tunde
690de64734 Merge branch 'develop' into issue-9166 2017-06-28 13:37:04 +01:00
tunde
c7e3a09cfb adds if statement for reference_type === "Asset" 2017-06-28 13:23:18 +01:00
249 changed files with 42946 additions and 30922 deletions

View File

@@ -1,6 +1,12 @@
language: python
dist: trusty
group: deprecated-2017Q2
addons:
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
python:
- "2.7"
@@ -8,43 +14,43 @@ python:
services:
- mysql
before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
install:
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- nvm install v7.10.0
# - wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
# - sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis --bench-branch develop
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
- sudo python install.py --develop --user travis --without-bench-setup
- sudo pip install -e ~/bench
# - sudo pip install --upgrade pip
- rm $TRAVIS_BUILD_DIR/.git/shallow
- bash $TRAVIS_BUILD_DIR/travis/bench_init.sh
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
script:
before_script:
- wget http://chromedriver.storage.googleapis.com/2.27/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- sudo apt-get install libnss3
- sudo apt-get --only-upgrade install google-chrome-stable
- sudo cp chromedriver /usr/local/bin/.
- sudo chmod +x /usr/local/bin/chromedriver
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- sleep 3
- mysql -u root -ptravis -e 'create database test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
- cd ~/frappe-bench
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench use test_site
- bench reinstall --yes
- bench build
- bench scheduler disable
- bench start &
- sleep 10
- bench --verbose run-tests --driver Firefox
before_script:
- mysql -u root -ptravis -e 'create database test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/92b3bea86d8c5397beef
on_success: always
on_failure: always
on_start: never
script:
- set -e
- bench --verbose run-tests
- sleep 5
- bench --verbose run-ui-tests --app erpnext

View File

@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import inspect
import frappe
from erpnext.hooks import regional_overrides
__version__ = '8.3.3'
__version__ = '8.6.4'
def get_default_company(user=None):
'''Get default company for user'''
@@ -65,3 +67,34 @@ def is_perpetual_inventory_enabled(company):
company, "enable_perpetual_inventory") or 0
return frappe.local.enable_perpetual_inventory[company]
def get_region(company=None):
'''Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company`
'''
if company or frappe.flags.company:
return frappe.db.get_value('Company',
company or frappe.flags.company, 'country')
elif frappe.flags.country:
return frappe.flags.country
else:
return frappe.get_system_settings('country')
def allow_regional(fn):
'''Decorator to make a function regionally overridable
Example:
@erpnext.allow_regional
def myfunction():
pass'''
def caller(*args, **kwargs):
region = get_region()
fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
if region in regional_overrides and fn_name in regional_overrides[region]:
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else:
return fn(*args, **kwargs)
return caller

View File

@@ -1,94 +1,94 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.cscript.refresh = function (doc, cdt, cdn) {
if (doc.__islocal) {
frappe.msgprint(__("Please create new account from Chart of Accounts."));
throw "cannot create";
}
cur_frm.toggle_display('account_name', doc.__islocal);
// hide fields if group
cur_frm.toggle_display(['account_type', 'tax_rate'], cint(doc.is_group) == 0)
// disable fields
cur_frm.toggle_enable(['account_name', 'is_group', 'company'], false);
if (cint(doc.is_group) == 0) {
cur_frm.toggle_display('freeze_account', doc.__onload && doc.__onload.can_freeze_account);
}
// read-only for root accounts
if (!doc.parent_account) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root account and cannot be edited."));
} else {
// credit days and type if customer or supplier
cur_frm.set_intro(null);
cur_frm.cscript.account_type(doc, cdt, cdn);
// show / hide convert buttons
cur_frm.cscript.add_toolbar_buttons(doc);
}
}
cur_frm.add_fetch('parent_account', 'report_type', 'report_type');
cur_frm.add_fetch('parent_account', 'root_type', 'root_type');
cur_frm.cscript.account_type = function (doc, cdt, cdn) {
if (doc.is_group == 0) {
cur_frm.toggle_display(['tax_rate'], doc.account_type == 'Tax');
cur_frm.toggle_display('warehouse', doc.account_type == 'Stock');
}
}
cur_frm.cscript.add_toolbar_buttons = function (doc) {
cur_frm.add_custom_button(__('Chart of Accounts'),
function () { frappe.set_route("Tree", "Account"); });
if (doc.is_group == 1) {
cur_frm.add_custom_button(__('Group to Non-Group'),
function () { cur_frm.cscript.convert_to_ledger(); }, 'fa fa-retweet', 'btn-default');
} else if (cint(doc.is_group) == 0) {
cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": doc.name,
"from_date": frappe.sys_defaults.year_start_date,
"to_date": frappe.sys_defaults.year_end_date,
"company": doc.company
frappe.ui.form.on('Account', {
setup: function(frm) {
frm.add_fetch('parent_account', 'report_type', 'report_type');
frm.add_fetch('parent_account', 'root_type', 'root_type');
},
onload: function(frm) {
frm.set_query('parent_account', function(doc) {
return {
filters: {
"is_group": 1,
"company": doc.company
}
};
frappe.set_route("query-report", "General Ledger");
});
},
refresh: function(frm) {
if (frm.doc.__islocal) {
frappe.msgprint(__("Please create new account from Chart of Accounts."));
throw "cannot create";
}
frm.toggle_display('account_name', frm.doc.__islocal);
// hide fields if group
frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
// disable fields
frm.toggle_enable(['account_name', 'is_group', 'company'], false);
if (cint(frm.doc.is_group) == 0) {
frm.toggle_display('freeze_account', frm.doc.__onload
&& frm.doc.__onload.can_freeze_account);
}
// read-only for root accounts
if (!frm.doc.parent_account) {
frm.set_read_only();
frm.set_intro(__("This is a root account and cannot be edited."));
} else {
// credit days and type if customer or supplier
frm.set_intro(null);
frm.trigger('account_type');
// show / hide convert buttons
frm.trigger('add_toolbar_buttons');
}
},
account_type: function (frm) {
if (frm.doc.is_group == 0) {
frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
}
},
add_toolbar_buttons: function(frm) {
frm.add_custom_button(__('Chart of Accounts'),
function () { frappe.set_route("Tree", "Account"); });
if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Group to Non-Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_group_to_ledger',
callback: function() {
frm.refresh();
}
});
});
} else if (cint(frm.doc.is_group) == 0) {
cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date,
"to_date": frappe.sys_defaults.year_end_date,
"company": frm.doc.company
};
frappe.set_route("query-report", "General Ledger");
});
frm.add_custom_button(__('Non-Group to Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_ledger_to_group',
callback: function() {
frm.refresh();
}
});
});
}
cur_frm.add_custom_button(__('Non-Group to Group'),
function () { cur_frm.cscript.convert_to_group(); }, 'fa fa-retweet', 'btn-default')
}
}
cur_frm.cscript.convert_to_ledger = function (doc, cdt, cdn) {
return $c_obj(cur_frm.doc, 'convert_group_to_ledger', '', function (r, rt) {
if (r.message == 1) {
cur_frm.refresh();
}
});
}
cur_frm.cscript.convert_to_group = function (doc, cdt, cdn) {
return $c_obj(cur_frm.doc, 'convert_ledger_to_group', '', function (r, rt) {
if (r.message == 1) {
cur_frm.refresh();
}
});
}
cur_frm.fields_dict['parent_account'].get_query = function (doc) {
return {
filters: {
"is_group": 1,
"company": doc.company
}
}
}
});

View File

@@ -0,0 +1,27 @@
QUnit.module('accounts');
QUnit.test("test account", function(assert) {
assert.expect(4);
let done = assert.async();
frappe.run_serially([
() => frappe.set_route('Tree', 'Account'),
() => frappe.click_button('Expand All'),
() => frappe.click_link('Debtors'),
() => frappe.click_button('Edit'),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.root_type=='Asset');
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
assert.ok(cur_frm.doc.account_type=='Receivable');
},
() => frappe.click_button('Ledger'),
() => frappe.timeout(1),
() => {
// check if general ledger report shown
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
window.history.back();
return frappe.timeout(1);
},
() => done()
]);
});

View File

@@ -147,8 +147,9 @@ class Asset(Document):
accumulated_depreciation_after_full_schedule = \
max([d.accumulated_depreciation_amount for d in self.get("schedules")])
asset_value_after_full_schedule = (flt(self.gross_purchase_amount) -
flt(accumulated_depreciation_after_full_schedule))
asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) -
flt(accumulated_depreciation_after_full_schedule),
self.precision('expected_value_after_useful_life'))
if self.expected_value_after_useful_life < asset_value_after_full_schedule:
frappe.throw(_("Expected value after useful life must be greater than or equal to {0}")

View File

@@ -83,7 +83,7 @@ def validate_expense_against_budget(args):
budget_records = frappe.db.sql("""
select
b.{budget_against_field}, ba.budget_amount, b.monthly_distribution,
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
b.action_if_annual_budget_exceeded,
b.action_if_accumulated_monthly_budget_exceeded
from
@@ -111,15 +111,15 @@ def validate_budget_records(args, budget_records):
args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(args, budget_amount,
_("Accumulated Monthly"), monthly_action)
_("Accumulated Monthly"), monthly_action, budget.budget_against)
if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
and yearly_action != monthly_action:
compare_expense_with_budget(args, flt(budget.budget_amount),
_("Annual"), yearly_action)
_("Annual"), yearly_action, budget.budget_against)
def compare_expense_with_budget(args, budget_amount, action_for, action):
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against):
actual_expense = get_actual_expense(args)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
@@ -127,7 +127,7 @@ def compare_expense_with_budget(args, budget_amount, action_for, action):
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
_(action_for), frappe.bold(args.account), args.budget_against_field,
frappe.bold(args.budget_against),
frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)),
frappe.bold(fmt_money(diff, currency=currency)))

View File

@@ -140,6 +140,33 @@ class TestBudget(unittest.TestCase):
budget.load_from_db()
budget.cancel()
def test_monthly_budget_against_parent_group_cost_center(self):
cost_center = "_Test Cost Center 3 - _TC"
if not frappe.db.exists("Cost Center", cost_center):
frappe.get_doc({
'doctype': 'Cost Center',
'cost_center_name': '_Test Cost Center 3',
'parent_cost_center': "_Test Company - _TC",
'company': '_Test Company',
'is_group': 0
}).insert(ignore_permissions=True)
budget = make_budget("Cost Center", cost_center)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, cost_center)
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
jv.cancel()
frappe.delete_doc('Journal Entry', jv.name)
frappe.delete_doc('Cost Center', cost_center)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project":
budget_against = "_Test Project"
@@ -167,7 +194,8 @@ def make_budget(budget_against=None, cost_center=None):
if budget_against == "Project":
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")})
else:
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Cost Center - _TC/_Test Fiscal Year 2013%")})
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013")
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)

View File

@@ -96,7 +96,14 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
// expense claim
if(jvd.reference_type==="Expense Claim") {
return {};
return {
filters: {
'approval_status': 'Approved',
'total_sanctioned_amount': ['>', 0],
'status': ['!=', 'Paid'],
'docstatus': 1
}
};
}
// journal entry
@@ -122,10 +129,11 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
// account filter
frappe.model.validate_missing(jvd, "account");
var party_account_field = jvd.reference_type==="Sales Invoice" ? "debit_to": "credit_to";
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
} else {
}
if(in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
// party_type and party mandatory
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");

View File

@@ -291,37 +291,39 @@ frappe.ui.form.on('Payment Entry', {
set_account_currency_and_balance: function(frm, account, currency_field,
balance_field, callback_function) {
frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
args: {
"account": account,
"date": frm.doc.posting_date
},
callback: function(r, rt) {
if(r.message) {
frm.set_value(currency_field, r.message['account_currency']);
frm.set_value(balance_field, r.message['account_balance']);
if (frm.doc.posting_date && account) {
frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
args: {
"account": account,
"date": frm.doc.posting_date
},
callback: function(r, rt) {
if(r.message) {
frm.set_value(currency_field, r.message['account_currency']);
frm.set_value(balance_field, r.message['account_balance']);
if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") {
frm.toggle_reqd(["reference_no", "reference_date"],
(r.message['account_type'] == "Bank" ? 1 : 0));
if(!frm.doc.received_amount && frm.doc.paid_amount)
frm.events.paid_amount(frm);
} else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") {
frm.toggle_reqd(["reference_no", "reference_date"],
(r.message['account_type'] == "Bank" ? 1 : 0));
if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") {
frm.toggle_reqd(["reference_no", "reference_date"],
(r.message['account_type'] == "Bank" ? 1 : 0));
if(!frm.doc.received_amount && frm.doc.paid_amount)
frm.events.paid_amount(frm);
} else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") {
frm.toggle_reqd(["reference_no", "reference_date"],
(r.message['account_type'] == "Bank" ? 1 : 0));
if(!frm.doc.paid_amount && frm.doc.received_amount)
frm.events.received_amount(frm);
if(!frm.doc.paid_amount && frm.doc.received_amount)
frm.events.received_amount(frm);
}
if(callback_function) callback_function(frm);
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
}
if(callback_function) callback_function(frm);
frm.events.hide_unhide_fields(frm);
frm.events.set_dynamic_labels(frm);
}
}
});
});
}
},
paid_from_account_currency: function(frm) {

View File

@@ -492,9 +492,13 @@ def get_outstanding_reference_documents(args):
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency \
and d.voucher_type in ("Sales Invoice", "Purchase Invoice"):
if party_account_currency != company_currency:
if d.voucher_type in ("Sales Invoice", "Purchase Invoice"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
# Get all SO / PO which are not fully billed or aginst which full advance not paid
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"),

View File

@@ -185,7 +185,7 @@ def get_pricing_rule_for_item(args):
"discount_percentage": 0.0
})
else:
item_details.discount_percentage = pricing_rule.discount_percentage
item_details.discount_percentage = pricing_rule.discount_percentage or args.discount_percentage
elif args.get('pricing_rule'):
item_details = remove_pricing_rule_for_item(args.get("pricing_rule"), item_details)

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import unittest
import frappe
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.get_item_details import get_item_details
from frappe import MandatoryError
@@ -248,4 +249,51 @@ class TestPricingRule(unittest.TestCase):
so.submit()
so = frappe.get_doc('Sales Order', so.name)
self.assertEquals(so.items[0].discount_percentage, 0)
self.assertEquals(so.items[0].rate, 100)
self.assertEquals(so.items[0].rate, 100)
def test_pricing_rule_with_margin_and_discount(self):
make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10)
si = create_sales_invoice(do_not_save=True)
si.items[0].price_list_rate = 1000
si.insert(ignore_permissions=True)
item = si.items[0]
self.assertEquals(item.rate, 1100)
self.assertEquals(item.margin_rate_or_amount, 10)
# With discount
item.discount_percentage = 10
si.save()
item = si.items[0]
self.assertEquals(item.rate, 990)
self.assertEquals(item.discount_percentage, 10)
frappe.db.sql("delete from `tabPricing Rule`")
def make_pricing_rule(**args):
args = frappe._dict(args)
doc = frappe.get_doc({
"doctype": "Pricing Rule",
"title": args.title or "_Test Pricing Rule",
"company": args.company or "_Test Company",
"apply_on": args.apply_on or "Item Code",
"item_code": args.item_code or "_Test Item",
"applicable_for": args.applicable_for,
"selling": args.selling or 0,
"buying": args.buying or 0,
"min_qty": args.min_qty or 0.0,
"max_qty": args.max_qty or 0.0,
"price_or_discount": args.price_or_discount or "Discount Percentage",
"discount_percentage": args.discount_percentage or 0.0,
"price": args.price or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
}).insert(ignore_permissions=True)
apply_on = doc.apply_on.replace(' ', '_').lower()
if args.get(apply_on) and apply_on != "item_code":
doc.db_set(apply_on, args.get(apply_on))
applicable_for = doc.applicable_for.replace(' ', '_').lower()
if args.get(applicable_for):
doc.db_set(applicable_for, args.get(applicable_for))

View File

@@ -1516,6 +1516,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "sec_tax_breakup",
"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": "Tax Breakup",
"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,
@@ -1523,7 +1553,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "other_charges_calculation",
"fieldtype": "HTML",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1533,12 +1563,12 @@
"in_standard_filter": 0,
"label": "Taxes and Charges Calculation",
"length": 0,
"no_copy": 0,
"no_copy": 1,
"oldfieldtype": "HTML",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -3767,7 +3797,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-06-29 10:48:09.707735",
"modified": "2017-07-19 13:53:48.673757",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -98,6 +98,26 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.set_default_print_format();
},
on_submit: function(doc, dt, dn) {
var me = this;
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(this.frm.doc.is_pos) {
this.frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
this.frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
},
set_default_print_format: function() {
// set default print format to POS type
if(cur_frm.doc.is_pos) {
@@ -306,7 +326,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.refresh_fields();
},
company_address: function() {
var me = this;
if(this.frm.doc.company_address) {
@@ -343,13 +363,6 @@ cur_frm.cscript.hide_fields = function(doc) {
}
}
var item_fields_stock = ['batch_no', 'actual_batch_qty', 'actual_qty', 'expense_account',
'warehouse', 'expense_account', 'quality_inspection']
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
// India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']);
@@ -445,24 +458,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center");
}
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(cur_frm.doc.is_pos) {
cur_frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
cur_frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
}
cur_frm.set_query("debit_to", function(doc) {
// filter on Account
if (doc.customer) {

View File

@@ -18,6 +18,7 @@ from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timeshe
from erpnext.accounts.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -83,10 +84,10 @@ class SalesInvoice(SellingController):
if not self.is_opening:
self.is_opening = 'No'
if self._action != 'submit' and self.update_stock and not self.is_return:
set_batch_nos(self, 'warehouse', True)
self.set_against_income_account()
self.validate_c_form()
@@ -98,7 +99,7 @@ class SalesInvoice(SellingController):
self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project()
self.set_status()
def before_save(self):
set_account_for_mode_of_payment(self)
@@ -139,6 +140,8 @@ class SalesInvoice(SellingController):
self.update_time_sheet(self.name)
frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', company=self.company)
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -803,19 +806,27 @@ class SalesInvoice(SellingController):
continue
for serial_no in item.serial_no.split("\n"):
sno = frappe.get_doc('Serial No', serial_no)
sno.sales_invoice = invoice
sno.db_update()
if serial_no and frappe.db.exists('Serial No', serial_no):
sno = frappe.get_doc('Serial No', serial_no)
sno.sales_invoice = invoice
sno.db_update()
def validate_serial_numbers(self):
"""
validate serial number agains Delivery Note and Sales Invoice
"""
self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note()
self.validate_serial_against_sales_invoice()
def set_serial_no_against_delivery_note(self):
for item in self.items:
if item.serial_no and item.delivery_note and \
item.qty != len(get_serial_nos(item.serial_no)):
item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
def validate_serial_against_delivery_note(self):
"""
"""
validate if the serial numbers in Sales Invoice Items are same as in
Delivery Note Item
"""
@@ -825,14 +836,18 @@ class SalesInvoice(SellingController):
continue
serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
dn_serial_nos = set(serial_nos.split("\n"))
dn_serial_nos = set(get_serial_nos(serial_nos))
serial_nos = item.serial_no or ""
si_serial_nos = set(serial_nos.split("\n"))
si_serial_nos = set(get_serial_nos(serial_nos))
if si_serial_nos - dn_serial_nos:
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
if item.serial_no and cint(item.qty) != len(si_serial_nos):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format(
item.idx, item.qty, item.item_code, len(si_serial_nos))))
def validate_serial_against_sales_invoice(self):
""" check if serial number is already used in other sales invoice """
for item in self.items:
@@ -917,7 +932,6 @@ def make_delivery_note(source_name, target_doc=None):
return doclist
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc

View File

@@ -11,8 +11,7 @@ def get_data():
'Sales Invoice': 'return_against'
},
'internal_links': {
'Sales Order': ['items', 'sales_order'],
'Delivery Note': ['items', 'delivery_note']
'Sales Order': ['items', 'sales_order']
},
'transactions': [
{

View File

@@ -13,6 +13,7 @@ from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -1105,8 +1106,83 @@ class TestSalesInvoice(unittest.TestCase):
for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i])
def test_item_wise_tax_breakup(self):
def test_item_wise_tax_breakup_india(self):
frappe.flags.country = "India"
si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
"999800": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 1500.0
}
}
}
expected_itemised_taxable_amount = {
"999800": 15000.0
}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None
def test_item_wise_tax_breakup_outside_india(self):
frappe.flags.country = "United States"
si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
"_Test Item": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 1000.0
}
},
"_Test Item 2": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 500.0
}
}
}
expected_itemised_taxable_amount = {
"_Test Item": 10000.0,
"_Test Item 2": 5000.0
}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None
def create_si_to_test_tax_breakup(self):
si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
si.append("items", {
"item_code": "_Test Item",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC",
"qty": 100,
"rate": 50,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC"
})
si.append("items", {
"item_code": "_Test Item 2",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC",
"qty": 100,
"rate": 50,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC"
})
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
@@ -1115,11 +1191,7 @@ class TestSalesInvoice(unittest.TestCase):
"rate": 10
})
si.insert()
tax_breakup_html = '''\n<div class="tax-break-up" style="overflow-x: auto;">\n\t<table class="table table-bordered table-hover">\n\t\t<thead><tr><th class="text-left" style="min-width: 120px;">Item Name</th><th class="text-right" style="min-width: 80px;">Taxable Amount</th><th class="text-right" style="min-width: 80px;">_Test Account Service Tax - _TC</th></tr></thead>\n\t\t<tbody><tr><td>_Test Item</td><td class="text-right">\u20b9 5,000.00</td><td class="text-right">(10.0%) \u20b9 500.00</td></tr></tbody>\n\t</table>\n</div>'''
self.assertEqual(si.other_charges_calculation, tax_breakup_html)
return si
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
@@ -1140,6 +1212,7 @@ def create_sales_invoice(**args):
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1,
"rate": args.rate or 100,

View File

@@ -1424,7 +1424,7 @@
"collapsible": 1,
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
"columns": 0,
"depends_on": "eval: parent.update_stock",
"depends_on": "",
"fieldname": "warehouse_and_reference",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2166,7 +2166,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-07-06 17:54:03.347700",
"modified": "2017-07-17 17:54:48.246507",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -1078,7 +1078,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
} else if (item.barcode == me.serach_item.$input.val()) {
search_status = false;
return item.barcode == me.serach_item.$input.val();
} else if (reg.test(item.item_code.toLowerCase()) || reg.test(item.description.toLowerCase()) ||
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
return true
}
@@ -1351,7 +1351,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.child.item_name = this.items[0].item_name;
this.child.stock_uom = this.items[0].stock_uom;
this.child.brand = this.items[0].brand;
this.child.description = this.items[0].description;
this.child.description = this.items[0].description || this.items[0].item_name;
this.child.discount_percentage = 0.0;
this.child.qty = 1;
this.child.item_group = this.items[0].item_group;
@@ -1398,10 +1398,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
return erpnext.get_currency(this.frm.doc.company);
},
show_item_wise_taxes: function () {
return null;
},
show_items_in_item_cart: function () {
var me = this;
var $items = this.wrapper.find(".items").empty();

View File

@@ -8,9 +8,10 @@ import datetime
from frappe import _, msgprint, scrub
from frappe.defaults import get_user_permissions
from frappe.model.utils import get_fetch_values
from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff, \
add_years, get_timestamp, nowdate, flt
from frappe.contacts.doctype.address.address import get_address_display, get_default_address
from frappe.utils import (add_days, getdate, formatdate, get_first_day, date_diff,
add_years, get_timestamp, nowdate, flt)
from frappe.contacts.doctype.address.address import (get_address_display,
get_default_address, get_company_address)
from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact
from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
from erpnext.accounts.utils import get_fiscal_year
@@ -77,8 +78,9 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
if doctype and doctype in ['Sales Invoice']:
out.company_address = get_default_address('Company', company)
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
out.update(get_company_address(company))
if out.company_address:
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
def set_contact_details(out, party, party_type):
out.contact_person = get_default_contact(party_type, party.name)
@@ -282,12 +284,14 @@ def get_credit_days(party_type, party, company):
if not credit_days_based_on:
if party_type == "Customer":
credit_days_based_on, credit_days = \
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"]) \
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"])
else:
credit_days_based_on, credit_days = \
frappe.db.get_value("Supplier Type", supplier_type, ["credit_days_based_on", "credit_days"])\
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"] )
frappe.db.get_value("Supplier Type", supplier_type, ["credit_days_based_on", "credit_days"])
if not credit_days_based_on:
credit_days_based_on, credit_days = \
frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
return credit_days_based_on, credit_days

View File

@@ -209,7 +209,10 @@ class GrossProfitGenerator(object):
sle.voucher_detail_no == row.item_row:
previous_stock_value = len(my_sle) > i+1 and \
flt(my_sle[i+1].stock_value) or 0.0
return previous_stock_value - flt(sle.stock_value)
if previous_stock_value:
return previous_stock_value - flt(sle.stock_value)
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)

View File

@@ -1546,6 +1546,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "sec_tax_breakup",
"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": "Tax Breakup",
"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,
@@ -1553,7 +1583,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "other_charges_calculation",
"fieldtype": "HTML",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1568,7 +1598,7 @@
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -3305,7 +3335,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-13 14:29:21.066814",
"modified": "2017-07-19 14:03:51.838328",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -11,3 +11,6 @@ from erpnext.controllers.print_settings import print_settings_for_item_table
class PurchaseOrderItem(Document):
def __setup__(self):
print_settings_for_item_table(self)
def on_doctype_update():
frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])

View File

@@ -1120,6 +1120,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "tax_breakup",
"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": "Tax Breakup",
"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,
@@ -1127,7 +1157,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "other_charges_calculation",
"fieldtype": "HTML",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -1142,7 +1172,7 @@
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -2217,7 +2247,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-06-13 14:28:54.466450",
"modified": "2017-07-19 13:51:18.929697",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
@@ -13,6 +14,7 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -44,6 +46,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -73,34 +76,7 @@
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -131,6 +107,66 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lead_time_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": "Lead Time in 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
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -160,6 +196,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -192,6 +229,7 @@
"width": "300px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -219,6 +257,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -248,6 +287,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -278,6 +318,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -306,6 +347,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -338,6 +380,7 @@
"width": "60px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -368,6 +411,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -397,6 +441,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -426,6 +471,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -453,6 +499,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -486,6 +533,7 @@
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -515,6 +563,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -544,6 +593,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -571,6 +621,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
@@ -602,6 +653,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -633,6 +685,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -660,6 +713,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -694,6 +748,7 @@
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -725,6 +780,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -754,6 +810,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -782,6 +839,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -811,6 +869,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -841,6 +900,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -869,6 +929,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -899,6 +960,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -929,6 +991,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -957,6 +1020,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -988,6 +1052,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1017,6 +1082,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1047,6 +1113,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1080,6 +1147,7 @@
"width": "120px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1110,6 +1178,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1137,6 +1206,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1167,6 +1237,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1196,6 +1267,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1227,6 +1299,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1259,6 +1332,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1290,6 +1364,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1318,6 +1393,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -1347,6 +1423,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -1377,17 +1454,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-02-17 16:43:59.582188",
"modified": "2017-07-10 09:08:52.015387",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",

View File

@@ -160,6 +160,7 @@ class AccountsController(TransactionBase):
def set_missing_item_details(self, for_validate=False):
"""set missing item values"""
from erpnext.stock.get_item_details import get_item_details
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
if hasattr(self, "items"):
parent_dict = {}
@@ -196,7 +197,7 @@ class AccountsController(TransactionBase):
elif fieldname == "serial_no":
stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty")
if stock_qty != len(item.get('serial_no').split('\n')):
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"):

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import json
import frappe, erpnext
from frappe import _, scrub
from frappe.utils import cint, flt, cstr, fmt_money, round_based_on_smallest_currency_fraction
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax
@@ -509,103 +509,75 @@ class calculate_taxes_and_totals(object):
return rate_with_margin
def set_item_wise_tax_breakup(self):
item_tax = {}
tax_accounts = []
company_currency = erpnext.get_company_currency(self.doc.company)
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
item_tax, tax_accounts = self.get_item_tax(item_tax, tax_accounts, company_currency)
headings = get_table_column_headings(tax_accounts)
distinct_items = self.get_distinct_items()
rows = get_table_rows(distinct_items, item_tax, tax_accounts, company_currency)
if not rows:
self.doc.other_charges_calculation = ""
else:
self.doc.other_charges_calculation = '''
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead><tr>{headings}</tr></thead>
<tbody>{rows}</tbody>
</table>
</div>'''.format(**{
"headings": "".join(headings),
"rows": "".join(rows)
})
def get_item_tax(self, item_tax, tax_accounts, company_currency):
for tax in self.doc.taxes:
tax_amount_precision = tax.precision("tax_amount")
tax_rate_precision = tax.precision("rate");
item_tax_map = self._load_item_tax_rate(tax.item_wise_tax_detail)
for item_code, tax_data in item_tax_map.items():
if not item_tax.get(item_code):
item_tax[item_code] = {}
if isinstance(tax_data, list):
tax_rate = ""
if tax_data[0]:
if tax.charge_type == "Actual":
tax_rate = fmt_money(flt(tax_data[0], tax_amount_precision),
tax_amount_precision, company_currency)
else:
tax_rate = cstr(flt(tax_data[0], tax_rate_precision)) + "%"
tax_amount = fmt_money(flt(tax_data[1], tax_amount_precision),
tax_amount_precision, company_currency)
item_tax[item_code][tax.name] = [tax_rate, tax_amount]
else:
item_tax[item_code][tax.name] = [cstr(flt(tax_data, tax_rate_precision)) + "%", ""]
tax_accounts.append([tax.name, tax.account_head])
return item_tax, tax_accounts
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
return
frappe.flags.company = doc.company
def get_distinct_items(self):
distinct_item_names = []
distinct_items = []
for item in self.doc.items:
item_code = item.item_code or item.item_name
if item_code not in distinct_item_names:
distinct_item_names.append(item_code)
distinct_items.append(item)
return distinct_items
# get headers
tax_accounts = list(set([d.description for d in doc.taxes]))
headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
# get tax breakup data
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
frappe.flags.company = None
return frappe.render_template(
"templates/includes/itemised_tax_breakup.html", dict(
headers=headers,
itemised_tax=itemised_tax,
itemised_taxable_amount=itemised_taxable_amount,
tax_accounts=tax_accounts,
company_currency=erpnext.get_company_currency(doc.company)
)
)
def get_table_column_headings(tax_accounts):
headings_name = [_("Item Name"), _("Taxable Amount")] + [d[1] for d in tax_accounts]
headings = []
for head in headings_name:
if head == _("Item Name"):
headings.append('<th style="min-width: 120px;" class="text-left">' + (head or "") + "</th>")
else:
headings.append('<th style="min-width: 80px;" class="text-right">' + (head or "") + "</th>")
@erpnext.allow_regional
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
return [_("Item"), _("Taxable Amount")] + tax_accounts
@erpnext.allow_regional
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
return itemised_tax, itemised_taxable_amount
def get_itemised_tax(taxes):
itemised_tax = {}
for tax in taxes:
tax_amount_precision = tax.precision("tax_amount")
tax_rate_precision = tax.precision("rate")
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
for item_code, tax_data in item_tax_map.items():
itemised_tax.setdefault(item_code, frappe._dict())
return headings
def get_table_rows(distinct_items, item_tax, tax_accounts, company_currency):
rows = []
for item in distinct_items:
item_tax_record = item_tax.get(item.item_code or item.item_name)
if not item_tax_record:
continue
taxes = []
for head in tax_accounts:
if item_tax_record[head[0]]:
taxes.append("<td class='text-right'>(" + item_tax_record[head[0]][0] + ") "
+ item_tax_record[head[0]][1] + "</td>")
if isinstance(tax_data, list) and tax_data[0]:
precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision
itemised_tax[item_code][tax.description] = frappe._dict(dict(
tax_rate=flt(tax_data[0], precision),
tax_amount=flt(tax_data[1], tax_amount_precision)
))
else:
taxes.append("<td></td>")
rows.append("<tr><td>{item_name}</td><td class='text-right'>{taxable_amount}</td>{taxes}</tr>".format(**{
"item_name": item.item_name,
"taxable_amount": fmt_money(item.net_amount, item.precision("net_amount"), company_currency),
"taxes": "".join(taxes)
}))
return rows
itemised_tax[item_code][tax.description] = frappe._dict(dict(
tax_rate=flt(tax_data, tax_rate_precision),
tax_amount=0.0
))
return itemised_tax
def get_itemised_taxable_amount(items):
itemised_taxable_amount = frappe._dict()
for item in items:
item_code = item.item_code or item.item_name
itemised_taxable_amount.setdefault(item_code, 0)
itemised_taxable_amount[item_code] += item.net_amount
return itemised_taxable_amount

View File

@@ -27,7 +27,8 @@ def test_recurring_document(obj, test_records):
base_doc.set(date_field, today)
if base_doc.doctype == "Sales Order":
base_doc.set("delivery_date", add_days(today, 15))
for d in base_doc.get("items"):
d.set("delivery_date", add_days(today, 15))
# monthly
doc1 = frappe.copy_doc(base_doc)

View File

@@ -0,0 +1,43 @@
QUnit.module("sales");
QUnit.test("test: lead", function (assert) {
assert.expect(4);
let done = assert.async();
let lead_name = frappe.utils.get_random(10);
frappe.run_serially([
// test lead creation
() => frappe.set_route("List", "Lead"),
() => frappe.new_doc("Lead"),
() => frappe.timeout(1),
() => cur_frm.set_value("lead_name", lead_name),
() => cur_frm.save(),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.lead_name.includes(lead_name),
'name correctly set');
frappe.lead_name = cur_frm.doc.name;
},
// create address and contact
() => frappe.click_link('Address & Contact'),
() => frappe.click_button('New Address'),
() => frappe.timeout(1),
() => frappe.set_control('address_line1', 'Gateway'),
() => frappe.set_control('city', 'Mumbai'),
() => cur_frm.save(),
() => frappe.timeout(3),
() => assert.equal(frappe.get_route()[1], 'Lead',
'back to lead form'),
() => frappe.click_link('Address & Contact'),
() => assert.ok($('.address-box').text().includes('Mumbai'),
'city is seen in address box'),
// make opportunity
() => frappe.click_button('Make'),
() => frappe.click_link('Opportunity'),
() => frappe.timeout(2),
() => assert.equal(cur_frm.doc.lead, frappe.lead_name,
'lead name correctly mapped'),
() => done()
]);
});

View File

@@ -56,7 +56,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Series",
"length": 0,
@@ -88,7 +88,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Opportunity From",
"length": 0,
@@ -277,8 +277,8 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Opportunity Type",
"length": 0,
"no_copy": 0,
@@ -310,7 +310,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"length": 0,
@@ -1189,7 +1189,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-13 14:29:07.077697",
"modified": "2017-07-10 15:29:23.921967",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",

View File

@@ -272,4 +272,6 @@ def auto_close_opportunity():
for opportunity in opportunities:
doc = frappe.get_doc("Opportunity", opportunity.get("name"))
doc.status = "Closed"
doc.save(ignore_permissions=True)
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.save()

View File

@@ -0,0 +1,56 @@
QUnit.test("test: opportunity", function (assert) {
assert.expect(8);
let done = assert.async();
frappe.run_serially([
() => frappe.set_route('List', 'Opportunity'),
() => frappe.timeout(1),
() => frappe.click_button('New'),
() => frappe.timeout(1),
() => cur_frm.set_value('enquiry_from', 'Customer'),
() => cur_frm.set_value('customer', 'Test Customer 1'),
// check items
() => cur_frm.set_value('with_items', 1),
() => frappe.tests.set_grid_values(cur_frm, 'items', [
[
{item_code:'Test Product 1'},
{qty: 4}
]
]),
() => cur_frm.save(),
() => frappe.timeout(1),
() => {
assert.notOk(cur_frm.is_new(), 'saved');
frappe.opportunity_name = cur_frm.doc.name;
},
// close and re-open
() => frappe.click_button('Close'),
() => frappe.timeout(1),
() => assert.equal(cur_frm.doc.status, 'Closed',
'closed'),
() => frappe.click_button('Reopen'),
() => assert.equal(cur_frm.doc.status, 'Open',
'reopened'),
() => frappe.timeout(1),
// make quotation
() => frappe.click_button('Make'),
() => frappe.click_link('Quotation', 1),
() => frappe.timeout(2),
() => {
assert.equal(frappe.get_route()[1], 'Quotation',
'made quotation');
assert.equal(cur_frm.doc.customer, 'Test Customer 1',
'customer set in quotation');
assert.equal(cur_frm.doc.items[0].item_code, 'Test Product 1',
'item set in quotation');
assert.equal(cur_frm.doc.items[0].qty, 4,
'qty set in quotation');
assert.equal(cur_frm.doc.items[0].prevdoc_docname, frappe.opportunity_name,
'opportunity set in quotation');
},
() => done()
]);
});

View File

@@ -118,7 +118,8 @@ def make_sales_order():
from erpnext.selling.doctype.quotation.quotation import make_sales_order
so = frappe.get_doc(make_sales_order(q))
so.transaction_date = frappe.flags.current_date
so.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
for d in so.get("items"):
d.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
so.insert()
frappe.db.commit()
so.submit()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 443 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -640,8 +640,8 @@ attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.</p>
<pre><code> &lt;one line="" to="" give="" the="" program's="" name="" and="" a="" brief="" idea="" of="" what="" it="" does.=""&gt;
Copyright (C) &lt;year&gt; &lt;name of="" author=""&gt;
<pre><code> &lt;one line to give the program's name and a brief idea of what it does.&gt;
Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -15,5 +15,6 @@ third-party-backups
workflows
bar-code
company-setup
setting-company-sales-goal
calculate-incentive-for-sales-team
articles

View File

@@ -31,7 +31,7 @@ Most of the information in your Sales Order is the same as the Quotation.
There are a few amongst other things that a Sales Order will ask you to
update.
* Expected date of delivery.
* Enter delivery date agaist each item. If there are multiple items and if you enter delivery date in the first row, the date will be copied to other rows as well where it is blank.
* Customer Purchase Order number: If your customer has sent you a Purchase Order, you can update its number for future reference (in billing).
### Packing List
@@ -89,9 +89,9 @@ ERPNext will automatically create new Order and mail a notification to the Email
Once you “Submit” your Sales Order, you can now trigger different aspects of
your organization:
* To begin purchase click on Make Purchase Request
* To make a shipment entry click on Make Delivery Note
* To bill, make Make Sales Invoice
* To begin purchase click on Make -> Purchase Request
* To make a shipment entry click on Make -> Delivery Note. You can also make Delivery Note for selected items based on delivery date.
* To bill, make Make -> Sales Invoice
* To stop further process on this Sales Order, click on “Stop”
### Submission

View File

@@ -0,0 +1,15 @@
# Setting Company Sales Goal
Monthly sales targets can be set for a company via the Company master. By default, the Company master dashboard features past sales stats.
<img class="screenshot" alt="Sales Graph" src="{{docs_base_url}}/assets/img/sales_goal/sales_history_graph.png">
You can set the **Sales Target** field to track progress to track progress with respect to it.
<img class="screenshot" alt="Setting Sales Goal" src="{{docs_base_url}}/assets/img/sales_goal/setting_sales_goal.gif">
The target progress is also shown in notifications:
<img class="screenshot" alt="Sales Notification" src="{{docs_base_url}}/assets/img/sales_goal/sales_goal_notification.png">
{next}

View File

@@ -3,10 +3,10 @@ WORK IN PROGRESS
-->
# Manual de Usuario (Español)
### Contenido:
### Contenido:
{index}
**Trabajo en progreso.**
[The Spanish Translation of the ERPNext manual is in progress. Click here to see the english manual]({{ docs_base_url }}/user/manual/en)
[La traducción al Español del manual de ERPNext está en progreso. Click aquí para ver el manual en ingles]({{ docs_base_url }}/user/manual/en)

View File

@@ -1 +1,2 @@
introduction
accounts

View File

@@ -0,0 +1,20 @@
ERPNext es una herramienta moderna que no solo abarca el módulo de contabilidad,
sino que también, cubre todas las otras funciones de su negocio en una plataforma integrada.
Tiene muchos beneficios sobre los sistemas tradicionales de contabilidad y otros ERP en el mercado.
### Beneficios sobre los sistemas de contabilidad tradicionales:
* Es más que solo contabilidad! Gestionar inventario, facturación, cotizaciones, clientes potenciales, nómina y mucho más.
* Mantiene toda tu información segura y en un solo lugar. No siga buscando sus datos cuando más lo necesitas en diferente hojas de calculo y en diferentes ordenadores.
Gestiona a todos tus empleados en el mismo lugar. Todos los usuarios obtienen información actualizada.
* No más trabajo doble. No introduzcas la misma información desde su procesador de textos a su herramienta de contabilidad. Todo está integrado!
* Manten un historial. Obten el historial completo de un cliente o un acuerdo en un solo lugar
### Beneficios sobre ERPs más grandes
* $$$ - Ahorra dinero.
* **Más facíl de configurar:** Grandes ERP son extramadamente complicados para configurar y van a preguntar demasiadas preguntas amtes de que puedas hacer algo utíl.
* **Más facíl de usar:** Una moderna y limpia interfaz web va a mantener sus usuarios contentos y en un entorno mas familiar.
* **Código Abierto :** Este sistema es completamente gratis y puedes instalarlo/configurarlo donde desees.
{next}

View File

@@ -0,0 +1,31 @@
Hay muchas manera de comenzar a utilizar ERPNext.
### 1\. Ver el Demo
Si deseas entrar en contacto con la interfaz de usuario de ERPNext y **sentir** la aplicación, solo tienes que ver el demo en:
see the demo at:
* <https://demo.erpnext.com>
### 2\. Comienza con una cuenta gratis en ERPNext.com
ERPNext.com es manejado por la organización (Frappe) que publicó ERPNext.
Puedes iniciar con su propia cuenta en [registrandote en la página](https://erpnext.com).
También, puedes hostear tu aplicación en erpnext.com comprando un plan de alojamiento.
De esta forma, estas aportando a la organización que desarrolla y mejora ERPNext.
También obten soporte de uno-a-uno con los planes de alojamiento.
### 3\. Descarga una Maquina Virtual
Para evitar las molestias de instalar el sistema, ERPNext está disponible como una image virtual (un sistema operativo completo con ERPNext instalado).
Puedes usarla en **cualquier** plataforma incluyendo Microsoft Windows.
[Click aquí para ver las instrucciones de como usar la imagen](https://erpnext.com/download)
### 4\. Instalar ERPNext en su ordenador Unix/Linux/Mac
En caso de estar relacionado con la instalación de aplicaciones en plataformas *nix, leer las instrucciones de como instalarlo usando [Frappe Bench](https://github.com/frappe/bench).
{next}

View File

@@ -0,0 +1,32 @@
Antes de que empieces a manejar todas tus operaciones en ERPNext, primero
deberías estar familiarizado con el sistema y los términos que utiliza.
Por esa razón recomendamos que la implementación pase en dos fases.
* La **Fase de Prueba**, donde introduces información de prueba que representan sus transacciones del día a día y la **Fase de Producción**, donde comenzamos a introducir información real.
### Fase de Prueba
* Leer el manual
* Crea una cuenta gratis en [https://erpnext.com](https://erpnext.com) (La forma más facíl de experimental).
* Crea su primer Cliente, Suplidor y Producto. Agrega varios de estos para que se familiarice con ellos.
* Crea un Grupo de Clientes, Grupo de Productos, Almacenes, Grupo de Suplidores, para que puedas clasificar sus productos.
* Completar un ciclo estandar de ventas - Iniciativa > Oportunidad > Cotización > Orden de Venta > Nota de Entrega > Factura de Venta > Pago (Entrada de diario)
* Completa un ciclo estandar de compra - Solicitud de Material > Orden de Compra > Recibo de Compra > Pagos (Entrada de diario).
* Completar un ciclo de manofactura (si aplica) - BOM > Herramienta de Planificación de Producción > Orden de Producción > Problema de material
* Replicar un escenario de su día a día dentro del sistema.
* Crea un custom fields, formato de impresión, etc como sea requerido.
### Fase de Producción
Una vez ya estes falimiliarizado con ERPNext, inicia introduciendo la información real!
* Borra toda la información de prueba de la cuenta o inicia con una nueva instalación.
* Si solo quieres borrar las transacciones y no las demás informaciones sobre Productos, Clientes, Suplidores, BOM etc, puedes dar click en Eliminar Transacciones de su compañia y inicia desde cero. Para hacerlo, abre el registro de la compañia via Setup > Masters > Company y eliminar las transacciones de su compañia clickeando en el botón **Eliminar las transacciones de la compañia** al final del formulario de la compañia.
* También puedes configurar una nueva cuenta en [https://erpnext.com](https://erpnext.com), y usa los 30 días gratis. [Encuentra mas formas de usar ERPNext](/introduction/getting-started-with-erpnext)
* Configura todos los módulos con Grupos de Clientes, Grupos de Productos, Almacenes, BOMs etc.
* Importar Clientes, Suplidores, Productos, Contactos y Direcciones usando la Herramienta de Importación de Data.
* Importar el inventario de apertura usando la Herramienta de Reconciliación de Inventario.
* Crear la entrada de apertura de cuenta usando la Entrada de Diario y crea facturas de ventas pendientes y facturas de compra.
* Si necesitas ayuda, [puedes pagar por soporte](https://erpnext.com/pricing) o [preguntar en el foro de la comunidad](https://discuss.erpnext.com).
{next}

View File

@@ -0,0 +1,40 @@
## ¿Qué es un ERP y Por qué debería interesarme?
(Si ya sabes que necesitas un sistema todo-en-uno para su compañia, puedes pasar a la siguiente página)
Si eres dueño de una pequeña empresa que tiene varios empleados, debes entender que es difícil manejar la naturaleza dinámica de hacer negocios.
Pequeñas empresas no son tan diferentes que las grandes empresas. Las pequeñas empresas contienen la mayoria de las complejidades que posee una empresa grande junto a otras reestricciones.
Las pequeñas empresas tienen que comunicarse con clientes, hacer contabilidad, pagar impuestos, pagar nómina, gestionar tiempos,
proporsionar bienes y servicios de calidad, responder preguntar, y mantener a todos contentos como lo hacen las grandes empresas.
Grandes empresas tienen la venraja de usar sistemas avanzados para manejar sus procesos de una forma mas eficiente.
Pequeñas empresas, sin embargo, luchan para mantener las cosas organizadas. Normalmente usan un conjuntos de aplicaciones como hojas de calculos, sistemas de contabilidad,
un CRM etc para administrarse. El problema es que no todos estan en la misma página. Un ERP cambia todo eso.
## ¿Qué es ERPNext?
ERPNext es una solución de negocio de extremo a extremo que te ayuda a manejar toda la información de su negocio en una sola aplicación
y usado no solo para manejar operaciones, sino que tambien le permite tomar decisiones efectivas y bien documentadas justo en el momento que las necesites.
Forma una columna vertebral de su negocio para agregar fuerza, transparencia y control a su compañia.
Junto con otras cosas, ERPNext te ayudará con todo lo siguiente:
* Mantener registro de todas sus facturas y pagos.
* Saber que cantidad de cada producto hay disponible en almacen.
* Identificar y hacer seguimiento de los indicadores de rendimientos (KPI's)
* Identificar consultas abiertas de los clientes.
* Gestionar Nómina.
* Asignar tareas y hacer seguimiento de las mismas.
* Mantener una base de datos de todos sus clientes, suplidores y sus contactos.
* Preparar presupuestos.
* Hacer seguimiento a su presupuesto y sus gastos.
* Determinar el precio efectivo para ventas basado en la materia prima disponible, maquinaria y costo de esfuerzo.
* Obtener recordatorios sobre el calendario de mantenimientos.
* Publicar su página web.
Y Mucho mucho más.
### Temas
{index}

View File

@@ -0,0 +1,7 @@
do-i-need-an-erp
open-source
getting-started-with-erpnext
the-champion
implementation-strategy
key-workflows
concepts-and-terms

View File

@@ -0,0 +1,13 @@
# Flujo De Transacciones En ERPNext
Este diagrama cubre como ERPNext hace el seguimiento de la información de su compañia a través de funciones claves.
Este diagrama no cubre toda la funcionalidad o características de ERPNext.
![]({{docs_base_url}}/assets/old_images/erpnext/overview.png)
<img class="screenshot" alt="Workflow" src="{{docs_base_url}}/assets/img/setup/overview.png">
_Nota: No todos los pasos son obligatorios. ERPNext te permite pasar algunos pasos si deseas simplificar el proceso._
{next}

View File

@@ -0,0 +1,34 @@
El código fuente de ERPNext es de código abierto. Está abierto para que todos
podamos entenderlo, extenderlo o mejorarlo. Y es gratis!
Las ventajas de un Sistema de Código Abierto:
1. Puedes cambiar tu proveedor de servicios cuando quieras.
2. Puedas hostear la aplicación donde quieras, incluyendo en tu propio servidor para tener completa propiedad y privacidad de la información.
3. Puedes pedir ayuda a la comunidad en caso de necesitarla. No estas atado a un proveedor de servicios.
4. Te puedes beneficiar de un producto que es criticado y usado por una gran cantidad de personas,
quienes han reportado cientos de fallos y sugerencias para mejorarlo, y esto siempre va a continuar así.
---
### Código Fuente de ERPNext
El repositorio que contiene el código fuente de ERPnext está disponible en GitHub y puede ser encontrado aquí
- [https://github.com/frappe/erpnext](https://github.com/frappe/erpnext)
---
### Alternativas
Hay muchas soluciones ERP que puedes considerar. Los más populares son:
1. Odoo
2. OpenBravo
3. Apache OfBiz
4. xTuple
5. Compiere (y clones)
{next}

View File

@@ -0,0 +1,41 @@
<!-- no-heading -->
<h1 class="white">El campeón</h1>
<img alt="Champion" class="screenshot" src="{{docs_base_url}}/assets/img/setup/implementation-image.png">
Hemos visto docenas de implementaciones de sistemas ERP en los últimos años
y nos hemos dado cuenta que una implementación exitosa es más sobre cosas intangibles y actitudes.
**Los ERP no son requeridos.**
Como el ejercicio.
El cuerpo humano puede que parezca que no requiere ejercicio hoy ni quizas mañana, pero con el pasar del tiempo,
si desea mantener su cuerpo y su salud deberá comenzar a hacer ejercicio.
En esta misma forma, ERPs mejoran la salud de su compañia a largo plazo manteniendola ajustada y eficiente.
Mientas más demores en poner las cosas en orden, más tiempo pierdes, y estas más cerca de una desastre mayor.
Por tanto, cuando comienzas a implementar un ERP, manten la visión en beneficios a largo plazo.
Como el ejercicio, es doloroso al comienzo, pero va a hacer cosas maravillosas si te mantienes haciendolo.
* * *
## El Campeón
Un ERP significa un cambio en la organización y un cambio no sucede sin exfuerzo.
Cada cambio requiere un campeón y es la responsabilidad de el campeón el
organizar y motivar al equipo completo durante la implementación.
El campeón necesita ser activo en caso que algo salga mal.
En muchas organizaciones que hemos visto, frecuentemente el campeón es el dueño o un Administrador.
Ocasionalmente, el campeón es una persona externa quien es contratado con un propósito específico.
En cualquier caso, debes identificar su campeón primero.
Lo más seguro es que sea **usted!**
Comencemos!
{next}

View File

@@ -1,7 +1,7 @@
# User Interface and Navigation
<iframe width="660" height="371" src="https://www.youtube.com/embed/HdblX7kOWWs" frameborder="0" allowfullscreen></iframe>
<iframe width="660" height="371" src="https://www.youtube.com/embed/vKjHRzMEei0" frameborder="0" allowfullscreen></iframe>
**Duration: 2:17**
**Duration: 2:41**
This video walks you through using the ERPNext "Desk" interface. The ERPNext User Interface is very web friendly and if you have used a website or mobile app before, the navigation should be very intuitive.

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals
from frappe import _
from . import __version__ as app_version
app_name = "erpnext"
app_title = "ERPNext"
@@ -80,6 +79,7 @@ website_route_rules = [
"parents": [{"title": _("Supplier Quotation"), "name": "quotations"}]
}
},
{"from_route": "/quotation", "to_route": "Quotation"},
{"from_route": "/shipments", "to_route": "Delivery Note"},
{"from_route": "/shipments/<path:name>", "to_route": "order",
"defaults": {
@@ -110,6 +110,7 @@ standard_portal_menu_items = [
{"title": _("Projects"), "route": "/project", "reference_doctype": "Project"},
{"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"},
{"title": _("Supplier Quotation"), "route": "/quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"},
{"title": _("Quotations"), "route": "/quotation", "reference_doctype": "Quotation", "role":"Customer"},
{"title": _("Orders"), "route": "/orders", "reference_doctype": "Sales Order", "role":"Customer"},
{"title": _("Invoices"), "route": "/invoices", "reference_doctype": "Sales Invoice", "role":"Customer"},
{"title": _("Shipments"), "route": "/shipments", "reference_doctype": "Delivery Note", "role":"Customer"},
@@ -182,10 +183,13 @@ scheduler_events = {
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
"erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries",
"erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary",
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status"
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history"
]
}
email_brand_image = "assets/erpnext/images/erpnext-logo.jpg"
default_mail_footer = """<div style="text-align: center;">
<a href="https://erpnext.com?source=via_email_footer" target="_blank" style="color: #8d99a6;">
Sent via ERPNext
@@ -203,3 +207,11 @@ bot_parsers = [
get_site_info = 'erpnext.utilities.get_site_info'
payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account"
regional_overrides = {
'India': {
'erpnext.tests.test_regional.test_method': 'erpnext.regional.india.utils.test_method',
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data'
}
}

View File

@@ -8,7 +8,7 @@ from frappe.model.document import Document
from frappe import _
from email_reply_parser import EmailReplyParser
from erpnext.hr.doctype.employee.employee import is_holiday
from frappe.utils import formatdate
from frappe.utils import global_date_format
from markdown2 import markdown
class DailyWorkSummary(Document):
@@ -24,17 +24,18 @@ class DailyWorkSummary(Document):
def send_summary(self):
'''Send summary of all replies. Called at midnight'''
message = self.get_summary_message()
args = self.get_message_details()
frappe.sendmail(recipients = get_employee_emails(self.company, False),
message = message,
template='daily_work_summary',
args=args,
subject = _('Daily Work Summary for {0}').format(self.company),
reference_doctype=self.doctype, reference_name=self.name)
self.db_set('status', 'Sent')
def get_summary_message(self):
'''Return summary of replies as HTML'''
def get_message_details(self):
'''Return args for template'''
settings = frappe.get_doc('Daily Work Summary Settings')
replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'],
@@ -45,8 +46,12 @@ class DailyWorkSummary(Document):
did_not_reply = self.email_sent_to.split()
for d in replies:
d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
"employee_name") or d.sender
emp = frappe.db.get_values("Employee", {"user_id": d.sender},
["employee_name", "image"], as_dict=True)
d.sender_name = emp[0].employee_name if emp else d.sender
d.image = emp[0].image if emp and emp[0].image else None
if d.sender in did_not_reply:
did_not_reply.remove(d.sender)
if d.text_content:
@@ -56,30 +61,12 @@ class DailyWorkSummary(Document):
did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email)
for email in did_not_reply]
return frappe.render_template(self.get_summary_template(),
dict(replies=replies,
original_message=settings.message,
title=_('Daily Work Summary for {0}'.format(formatdate(self.creation))),
did_not_reply= ', '.join(did_not_reply) or '',
did_not_reply_title = _('No replies from')))
return dict(replies=replies,
original_message=settings.message,
title=_('Daily Work Summary for {0}'.format(global_date_format(self.creation))),
did_not_reply= ', '.join(did_not_reply) or '',
did_not_reply_title = _('No replies from'))
def get_summary_template(self):
return '''
<h3>{{ title }}</h3>
{% for reply in replies %}
<h4>{{ reply.sender_name }}</h4>
<p style="padding-bottom: 20px">
{{ reply.content }}
</p>
<hr>
{% endfor %}
{% if did_not_reply %}
<p>{{ did_not_reply_title }}: {{ did_not_reply }}</p>
{% endif %}
'''
def get_employee_emails(company, only_working=True):
'''Returns list of Employee user ids for the given company who are working today

View File

@@ -12,32 +12,31 @@ import frappe.utils
class TestDailyWorkSummary(unittest.TestCase):
def test_email_trigger(self):
settings, employees, emails = self.setup_and_prepare_test()
for d in employees:
self.setup_and_prepare_test()
for d in self.employees:
# check that email is sent to this employee
self.assertTrue(d.user_id in [d.recipient for d in emails
if settings.subject in d.message])
self.assertTrue(d.user_id in [d.recipient for d in self.emails
if self.settings.subject in d.message])
def test_email_trigger_failed(self):
hour = '00'
if frappe.utils.nowtime().split(':')[0]=='00':
hour = '01'
settings, employees, emails = self.setup_and_prepare_test(hour)
self.setup_and_prepare_test(hour)
for d in employees:
for d in self.employees:
# check that email is sent to this employee
self.assertFalse(d.user_id in [d.recipient for d in emails
if settings.subject in d.message])
self.assertFalse(d.user_id in [d.recipient for d in self.emails
if self.settings.subject in d.message])
def test_incoming(self):
settings, employees, emails = self.setup_and_prepare_test()
# get test mail with message-id as in-reply-to
self.setup_and_prepare_test()
with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f:
test_mails = [f.read().replace('{{ sender }}', employees[-1].user_id)\
.replace('{{ message_id }}', emails[-1].message_id)]
test_mails = [f.read().replace('{{ sender }}', self.employees[-1].user_id)\
.replace('{{ message_id }}', self.emails[-1].message_id)]
# pull the mail
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
@@ -47,35 +46,39 @@ class TestDailyWorkSummary(unittest.TestCase):
daily_work_summary = frappe.get_doc('Daily Work Summary',
frappe.get_all('Daily Work Summary')[0].name)
summary = daily_work_summary.get_summary_message()
args = daily_work_summary.get_message_details()
self.assertTrue('I built Daily Work Summary!' in summary)
self.assertTrue('I built Daily Work Summary!' in args.get('replies')[0].content)
def setup_and_prepare_test(self, hour=None):
if not hour:
hour = frappe.utils.nowtime().split(':')[0]
frappe.db.sql('delete from `tabDaily Work Summary`')
frappe.db.sql('delete from `tabEmail Queue`')
frappe.db.sql('delete from `tabEmail Queue Recipient`')
frappe.db.sql('delete from `tabCommunication`')
# setup email to trigger at this our
settings = frappe.get_doc('Daily Work Summary Settings')
settings.companies = []
settings.append('companies', dict(company='_Test Company',
send_emails_at=hour + ':00'))
settings.test_subject = 'this is a subject for testing summary emails'
settings.save()
self.setup_settings(hour)
from erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings \
import trigger_emails
trigger_emails()
# check if emails are created
employees = frappe.get_all('Employee', fields = ['user_id'],
filters=dict(company='_Test Company', status='Active'))
self.employees = frappe.get_all('Employee', fields = ['user_id'],
filters=dict(company='_Test Company', status='Active', user_id=('!=', 'test@example.com')))
emails = frappe.db.sql("""select r.recipient, q.message, q.message_id from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent""", as_dict=1)
self.emails = frappe.db.sql("""select r.recipient, q.message, q.message_id from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent""", as_dict=1)
frappe.db.commit()
def setup_settings(self, hour=None):
# setup email to trigger at this our
if not hour:
hour = frappe.utils.nowtime().split(':')[0]
self.settings = frappe.get_doc('Daily Work Summary Settings')
self.settings.companies = []
self.settings.append('companies', dict(company='_Test Company',
send_emails_at=hour + ':00'))
self.settings.test_subject = 'this is a subject for testing summary emails'
self.settings.save()
return settings, employees, emails

View File

@@ -27,6 +27,10 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
return;
}
if(!d.expense_type) {
return;
}
return frappe.call({
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account",
args: {
@@ -226,7 +230,7 @@ frappe.ui.form.on("Expense Claim",{
frm.fields_dict["payable_account"].get_query = function() {
return {
filters: {
"root_type": "Liability",
"report_type": "Balance Sheet",
"account_type": "Payable"
}
}

View File

@@ -905,7 +905,7 @@
"label": "Status",
"length": 0,
"no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nSubmitted\nCancelled",
"options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled",
"permlevel": 0,
"precision": "",
"print_hide": 1,
@@ -964,7 +964,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-06-13 14:29:16.914609",
"modified": "2017-07-17 15:47:23.255142",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",

View File

@@ -39,10 +39,13 @@ class ExpenseClaim(AccountsController):
"2": "Cancelled"
}[cstr(self.docstatus or 0)]
if self.total_sanctioned_amount == self.total_amount_reimbursed and self.docstatus == 1:
if self.total_sanctioned_amount > 0 and self.total_sanctioned_amount == self.total_amount_reimbursed \
and self.docstatus == 1 and self.approval_status == 'Approved':
self.status = "Paid"
elif self.docstatus == 1:
elif self.total_sanctioned_amount > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
self.status = "Unpaid"
elif self.docstatus == 1 and self.approval_status == 'Rejected':
self.status = 'Rejected'
def set_payable_account(self):
if not self.payable_account and not self.is_paid:
@@ -157,6 +160,9 @@ class ExpenseClaim(AccountsController):
self.total_claimed_amount = 0
self.total_sanctioned_amount = 0
for d in self.get('expenses'):
if self.approval_status == 'Rejected':
d.sanctioned_amount = 0.0
self.total_claimed_amount += flt(d.claim_amount)
self.total_sanctioned_amount += flt(d.sanctioned_amount)

View File

@@ -4,8 +4,10 @@ frappe.listview_settings['Expense Claim'] = {
get_indicator: function(doc) {
if(doc.status == "Paid") {
return [__("Paid"), "green", "status,=,'Paid'"];
} else {
}else if(doc.status == "Unpaid") {
return [__("Unpaid"), "orange"];
} else if(doc.status == "Rejected") {
return [__("Rejected"), "grey"];
}
}
};

View File

@@ -113,5 +113,23 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEquals(expected_values[gle.account][1], gle.debit)
self.assertEquals(expected_values[gle.account][2], gle.credit)
def test_rejected_expense_claim(self):
payable_account = get_payable_account("Wind Power LLC")
expense_claim = frappe.get_doc({
"doctype": "Expense Claim",
"employee": "_T-Employee-0001",
"payable_account": payable_account,
"approval_status": "Rejected",
"expenses":
[{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
})
expense_claim.submit()
self.assertEquals(expense_claim.status, 'Rejected')
self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
self.assertEquals(len(gl_entry), 0)
def get_payable_account(company):
return frappe.db.get_value('Company', company, 'default_payable_account')

View File

@@ -69,27 +69,17 @@ def get_events(start, end, filters=None):
:param end: End date-time.
:param filters: Filters (JSON).
"""
condition = ''
values = {
"start_date": getdate(start),
"end_date": getdate(end)
}
if filters:
if isinstance(filters, basestring):
filters = json.loads(filters)
filters = json.loads(filters)
else:
filters = []
if filters.get('holiday_list'):
condition = 'and hlist.name=%(holiday_list)s'
values['holiday_list'] = filters['holiday_list']
if start:
filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
if end:
filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
data = frappe.db.sql("""select hlist.name, h.holiday_date, h.description
from `tabHoliday List` hlist, tabHoliday h
where h.parent = hlist.name
and h.holiday_date is not null
and h.holiday_date >= %(start_date)s
and h.holiday_date <= %(end_date)s
{condition}""".format(condition=condition),
values, as_dict=True, update={"allDay": 1})
return data
return frappe.get_list('Holiday List',
fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description'],
filters = filters,
update={"allDay": 1})

View File

@@ -63,13 +63,13 @@ class LeaveApplication(Document):
def validate_dates(self):
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
frappe.throw(_("To date cannot be before from date"))
if self.half_day and self.half_day_date \
and (getdate(self.half_day_date) < getdate(self.from_date)
and (getdate(self.half_day_date) < getdate(self.from_date)
or getdate(self.half_day_date) > getdate(self.to_date)):
frappe.throw(_("Half Day Date should be between From Date and To Date"))
if not is_lwp(self.leave_type):
self.validate_dates_acorss_allocation()
self.validate_back_dated_application()
@@ -158,7 +158,7 @@ class LeaveApplication(Document):
self.name = "New Leave Application"
for d in frappe.db.sql("""
select
select
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
from `tabLeave Application`
where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
@@ -169,12 +169,12 @@ class LeaveApplication(Document):
"to_date": self.to_date,
"name": self.name
}, as_dict = 1):
if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
flt(self.total_leave_days)==0.5
or getdate(self.from_date) == getdate(d.to_date)
flt(self.total_leave_days)==0.5
or getdate(self.from_date) == getdate(d.to_date)
or getdate(self.to_date) == getdate(d.from_date)):
total_leaves_on_half_day = self.get_total_leaves_on_half_day()
if total_leaves_on_half_day >= 1:
self.throw_overlap_error(d)
@@ -199,7 +199,7 @@ class LeaveApplication(Document):
"half_day_date": self.half_day_date,
"name": self.name
})[0][0]
return leave_count_on_half_day_date * 0.5
def validate_max_days(self):
@@ -400,7 +400,7 @@ def is_lwp(leave_type):
return lwp and cint(lwp[0][0]) or 0
@frappe.whitelist()
def get_events(start, end):
def get_events(start, end, filters=None):
events = []
employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
@@ -411,14 +411,14 @@ def get_events(start, end):
employee=''
company=frappe.db.get_value("Global Defaults", None, "default_company")
from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("Leave Application")
from frappe.desk.reportview import get_filters_cond
conditions = get_filters_cond("Leave Application", filters, [])
# show department leaves for employee
if "Employee" in frappe.get_roles():
add_department_leaves(events, start, end, employee, company)
add_leaves(events, start, end, match_conditions)
add_leaves(events, start, end, conditions)
add_block_dates(events, start, end, employee, company)
add_holidays(events, start, end, employee, company)
@@ -435,7 +435,7 @@ def add_department_leaves(events, start, end, employee, company):
department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
and company=%s""", (department, company))
match_conditions = "employee in (\"%s\")" % '", "'.join(department_employees)
match_conditions = "and employee in (\"%s\")" % '", "'.join(department_employees)
add_leaves(events, start, end, match_conditions=match_conditions)
def add_leaves(events, start, end, match_conditions=None):
@@ -446,7 +446,7 @@ def add_leaves(events, start, end, match_conditions=None):
and docstatus < 2
and status!="Rejected" """
if match_conditions:
query += " and " + match_conditions
query += match_conditions
for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
e = {

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
var in_progress = false;
frappe.ui.form.on("Process Payroll", {
onload: function (frm) {
frm.doc.posting_date = frappe.datetime.nowdate();
@@ -47,11 +49,12 @@ frappe.ui.form.on("Process Payroll", {
},
start_date: function (frm) {
frm.trigger("set_start_end_dates");
},
end_date: function (frm) {
frm.trigger("set_start_end_dates");
if(!in_progress && frm.doc.start_date){
frm.trigger("set_end_date");
}else{
// reset flag
in_progress = false
}
},
salary_slip_based_on_timesheet: function (frm) {
@@ -68,10 +71,11 @@ frappe.ui.form.on("Process Payroll", {
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
args: {
payroll_frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date || frm.doc.posting_date
start_date: frm.doc.posting_date
},
callback: function (r) {
if (r.message) {
in_progress = true;
frm.set_value('start_date', r.message.start_date);
frm.set_value('end_date', r.message.end_date);
}
@@ -79,6 +83,21 @@ frappe.ui.form.on("Process Payroll", {
})
}
},
set_end_date: function(frm){
frappe.call({
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
args: {
frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date
},
callback: function (r) {
if (r.message) {
frm.set_value('end_date', r.message.end_date);
}
}
})
}
})
cur_frm.cscript.display_activity_log = function (msg) {

View File

@@ -3,7 +3,8 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money
from dateutil.relativedelta import relativedelta
from frappe.utils import cint, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT
from frappe import _
from erpnext.accounts.utils import get_fiscal_year
@@ -47,7 +48,6 @@ class ProcessPayroll(Document):
%s """% cond, {"sal_struct": sal_struct})
return emp_list
def get_filter_condition(self):
self.check_mandatory()
@@ -58,7 +58,6 @@ class ProcessPayroll(Document):
return cond
def get_joining_releiving_condition(self):
cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
@@ -66,7 +65,6 @@ class ProcessPayroll(Document):
""" % {"start_date": self.start_date, "end_date": self.end_date}
return cond
def check_mandatory(self):
for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname):
@@ -110,7 +108,6 @@ class ProcessPayroll(Document):
ss_list.append(ss_dict)
return self.create_log(ss_list)
def create_log(self, ss_list):
if not ss_list or len(ss_list) < 1:
log = "<p>" + _("No employee for the above selected criteria OR salary slip already created") + "</p>"
@@ -134,7 +131,6 @@ class ProcessPayroll(Document):
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
return ss_list
def submit_salary_slips(self):
"""
Submit all salary slips based on selected criteria
@@ -194,7 +190,6 @@ class ProcessPayroll(Document):
def format_as_links(self, salary_slip):
return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)]
def get_total_salary_and_loan_amounts(self):
"""
Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria
@@ -257,7 +252,6 @@ class ProcessPayroll(Document):
return payroll_payable_account
def make_accural_jv_entry(self):
self.check_permission('write')
earnings = self.get_salary_component_total(component_type = "earnings") or {}
@@ -358,7 +352,6 @@ class ProcessPayroll(Document):
self.update(get_start_end_dates(self.payroll_frequency,
self.start_date or self.posting_date, self.company))
@frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date'''
@@ -391,6 +384,29 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'start_date': start_date, 'end_date': end_date
})
def get_frequency_kwargs(frequency_name):
frequency_dict = {
'monthly': {'months': 1},
'fortnightly': {'days': 14},
'weekly': {'days': 7},
'daily': {'days': 1}
}
return frequency_dict.get(frequency_name)
@frappe.whitelist()
def get_end_date(start_date, frequency):
start_date = getdate(start_date)
frequency = frequency.lower() if frequency else 'monthly'
kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly')
# weekly, fortnightly and daily intervals have fixed days so no problems
end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1)
if frequency != 'bimonthly':
return dict(end_date=end_date.strftime(DATE_FORMAT))
else:
return dict(end_date='')
def get_month_details(year, month):
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
if ysd:

View File

@@ -3,10 +3,12 @@
from __future__ import unicode_literals
import unittest
import frappe
import erpnext
from frappe.utils import flt, add_months, cint, nowdate, getdate, add_days, random_string
from frappe.utils.make_random import get_random
import frappe
from frappe.utils import nowdate
from erpnext.hr.doctype.process_payroll.process_payroll import get_end_date
class TestProcessPayroll(unittest.TestCase):
def test_process_payroll(self):
@@ -30,6 +32,16 @@ class TestProcessPayroll(unittest.TestCase):
process_payroll.submit_salary_slips()
if process_payroll.get_sal_slip_list(ss_status = 1):
r = process_payroll.make_payment_entry()
def test_get_end_date(self):
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
self.assertEqual(get_end_date('2017-02-01', 'fortnightly'), {'end_date': '2017-02-14'})
self.assertEqual(get_end_date('2017-02-01', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2017-01-01', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2020-02-15', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2017-02-15', 'monthly'), {'end_date': '2017-03-14'})
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def get_salary_component_account(sal_comp):

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@@ -12,6 +13,7 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -22,6 +24,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Date",
@@ -40,6 +43,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -50,11 +54,13 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Principal Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -68,6 +74,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -78,11 +85,13 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Interest Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -96,6 +105,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -106,11 +116,13 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Payment",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -124,6 +136,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -134,11 +147,13 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Balance Loan Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -152,17 +167,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-01-09 12:00:10.818772",
"modified": "2017-07-26 14:47:29.862084",
"modified_by": "Administrator",
"module": "HR",
"name": "Repayment Schedule",
@@ -172,6 +187,7 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,

View File

@@ -29,6 +29,27 @@ frappe.ui.form.on("Salary Slip", {
})
},
start_date: function(frm){
if(frm.doc.start_date){
frm.trigger("set_end_date");
}
},
set_end_date: function(frm){
frappe.call({
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
args: {
frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date
},
callback: function (r) {
if (r.message) {
frm.set_value('end_date', r.message.end_date);
}
}
})
},
company: function(frm) {
var company = locals[':Company'][frm.doc.company];
if(!frm.doc.letter_head && company.default_letter_head) {
@@ -45,11 +66,17 @@ frappe.ui.form.on("Salary Slip", {
},
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
frm.trigger("toggle_fields");
frm.set_value('start_date', '');
},
payroll_frequency: function(frm) {
frm.trigger("toggle_fields")
frm.trigger("toggle_fields");
frm.set_value('start_date', '');
},
employee: function(frm){
frm.set_value('start_date', '');
},
toggle_fields: function(frm) {
@@ -74,18 +101,19 @@ frappe.ui.form.on('Salary Detail', {
// Get leave details
//---------------------------------------------------------------------
cur_frm.cscript.start_date = function(doc, dt, dn){
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
if(!doc.start_date){
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
}
}
cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date;
cur_frm.cscript.end_date = cur_frm.cscript.start_date;
cur_frm.cscript.employee = function(doc,dt,dn){
doc.salary_structure = ''

View File

@@ -51,6 +51,7 @@ class BOM(WebsiteGenerator):
from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
self.update_stock_qty()
self.validate_materials()
self.set_bom_material_details()
self.validate_operations()
@@ -365,9 +366,6 @@ class BOM(WebsiteGenerator):
base_total_rm_cost = 0
for d in self.get('items'):
if d.bom_no:
d.rate = self.get_bom_unitcost(d.bom_no)
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d))
d.base_amount = d.amount * flt(self.conversion_rate)

View File

@@ -77,7 +77,6 @@ frappe.ui.form.on("Production Order", {
if (!frm.doc.status)
frm.doc.status = 'Draft';
frm.add_fetch("sales_order", "delivery_date", "expected_delivery_date");
frm.add_fetch("sales_order", "project", "project");
if(frm.doc.__islocal) {
@@ -151,26 +150,28 @@ frappe.ui.form.on("Production Order", {
},
production_item: function(frm) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
args: {
item: frm.doc.production_item,
project: frm.doc.project
},
callback: function(r) {
if(r.message) {
erpnext.in_production_item_onchange = true;
$.each(["description", "stock_uom", "project", "bom_no"], function(i, field) {
frm.set_value(field, r.message[field]);
});
if (frm.doc.production_item) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
args: {
item: frm.doc.production_item,
project: frm.doc.project
},
callback: function(r) {
if(r.message) {
erpnext.in_production_item_onchange = true;
$.each(["description", "stock_uom", "project", "bom_no"], function(i, field) {
frm.set_value(field, r.message[field]);
});
if(r.message["set_scrap_wh_mandatory"]){
frm.toggle_reqd("scrap_warehouse", true);
if(r.message["set_scrap_wh_mandatory"]){
frm.toggle_reqd("scrap_warehouse", true);
}
erpnext.in_production_item_onchange = false;
}
erpnext.in_production_item_onchange = false;
}
}
});
});
}
},
project: function(frm) {
@@ -210,7 +211,9 @@ frappe.ui.form.on("Production Order", {
frappe.ui.form.on("Production Order Item", {
source_warehouse: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
if(row.source_warehouse) {
if(!row.item_code) {
frappe.throw(__("Please set the Item Code first"));
} else if(row.source_warehouse) {
frappe.call({
"method": "erpnext.stock.utils.get_latest_stock_qty",
args: {
@@ -263,7 +266,7 @@ erpnext.production_order = {
erpnext.production_order.stop_production_order(frm, "Resumed");
}, __("Status"));
}
if(!frm.doc.skip_transfer){
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') {
@@ -272,7 +275,7 @@ erpnext.production_order = {
erpnext.production_order.make_se(frm, 'Material Transfer for Manufacture');
});
start_btn.addClass('btn-primary');
}
}
}
if(!frm.doc.skip_transfer){
@@ -291,8 +294,9 @@ erpnext.production_order = {
} else {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true;
var finish_btn = frm.add_custom_button(__('Finish'),
cur_frm.cscript['Update Finished Goods']);
var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.production_order.make_se(frm, 'Manufacture');
});
finish_btn.addClass('btn-primary');
}
}

View File

@@ -1358,7 +1358,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-13 14:29:00.457874",
"modified": "2017-07-10 14:29:00.457874",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Order",

View File

@@ -50,8 +50,12 @@ class ProductionOrder(Document):
def validate_sales_order(self):
if self.sales_order:
so = frappe.db.sql("""select name, delivery_date, project from `tabSales Order`
where name=%s and docstatus = 1""", self.sales_order, as_dict=1)
so = frappe.db.sql("""
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
and so.docstatus = 1 and so_item.item_code=%s
""", (self.sales_order, self.production_item), as_dict=1)
if len(so):
if not self.expected_delivery_date:
@@ -510,6 +514,12 @@ def check_if_scrap_warehouse_mandatory(bom_no):
return res
@frappe.whitelist()
def set_production_order_ops(name):
po = frappe.get_doc('Production Order', name)
po.set_production_order_operations()
po.save()
@frappe.whitelist()
def make_stock_entry(production_order_id, purpose, qty=None):
production_order = frappe.get_doc("Production Order", production_order_id)

View File

@@ -353,7 +353,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-05-15 17:37:20.212361",
"modified": "2017-07-10 17:37:20.212361",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Order Item",

View File

@@ -8,3 +8,6 @@ from frappe.model.document import Document
class ProductionOrderItem(Document):
pass
def on_doctype_update():
frappe.db.add_index("Production Order Item", ["item_code", "source_warehouse"])

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Workstation", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially('Workstation', [
// insert a new Workstation
() => frappe.tests.make([
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:workstation_name",
@@ -12,11 +13,12 @@
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "description_and_warehouse",
"fieldname": "description_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -25,7 +27,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -41,6 +43,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -71,6 +74,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -102,65 +106,7 @@
"width": "300px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "holiday_list",
"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": "Holiday List",
"length": 0,
"no_copy": 0,
"options": "Holiday List",
"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,
@@ -190,6 +136,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -221,6 +168,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -252,6 +200,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -280,6 +229,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -311,6 +261,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -342,6 +293,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -373,6 +325,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -402,6 +355,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -430,20 +384,52 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "holiday_list",
"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": "Holiday List",
"length": 0,
"no_copy": 0,
"options": "Holiday List",
"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,
"icon": "icon-wrench",
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-17 17:26:57.238167",
"modified": "2017-07-18 22:28:50.163219",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Workstation",
@@ -470,11 +456,11 @@
"write": 1
}
],
"quick_entry": 0,
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 0,
"track_changes": 1,
"track_seen": 0
}

View File

@@ -0,0 +1,5 @@
/* eslint-disable */
frappe.listview_settings['Workstation'] = {
// add_fields: ["status"],
// filters:[["status","=", "Open"]]
};

View File

@@ -414,4 +414,13 @@ erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account
erpnext.patches.v8_1.gst_fixes #2017-07-06
erpnext.patches.v8_0.update_production_orders
erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no
erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit
erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit
erpnext.patches.v8_1.add_hsn_sac_codes
erpnext.patches.v8_1.update_gst_state #17-07-2017
erpnext.patches.v8_1.removed_report_support_hours
erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
erpnext.patches.v8_3.set_restrict_to_domain_for_module_def
erpnext.patches.v8_1.update_expense_claim_status
erpnext.patches.v8_3.update_company_total_sales
erpnext.patches.v8_1.set_delivery_date_in_so_item
erpnext.patches.v8_5.fix_tax_breakup_for_non_invoice_docs

View File

@@ -51,7 +51,7 @@ def check_is_warehouse_associated_with_company():
def make_warehouse_nestedset(company=None):
validate_parent_account_for_warehouse(company)
stock_account_group = get_stock_account_group(company.name)
enable_perpetual_inventory = cint(erpnext.is_perpetual_inventory_enabled(company)) or 0
enable_perpetual_inventory = cint(erpnext.is_perpetual_inventory_enabled(company.name)) or 0
if not stock_account_group and enable_perpetual_inventory:
return

View File

@@ -7,10 +7,11 @@ import frappe, erpnext
def execute():
frappe.reload_doctype("Account")
warehouses = frappe.db.sql_list("""select name, company from tabAccount
warehouses = frappe.db.sql("""select name, company from tabAccount
where account_type = 'Stock' and is_group = 0
and (warehouse is null or warehouse = '')""", as_dict)
and (warehouse is null or warehouse = '')""", as_dict=1)
warehouses = [d.name for d in warehouses if erpnext.is_perpetual_inventory_enabled(d.company)]
if len(warehouses) > 0:
warehouses = set_warehouse_for_stock_account(warehouses)
if not warehouses:

View File

@@ -11,6 +11,9 @@ def execute():
for dt in ("assessment", "course", "fees"):
frappe.reload_doc("schools", "doctype", dt)
for dt in ("domain", "has_domain", "domain_settings"):
frappe.reload_doc("core", "doctype", dt)
frappe.reload_doc('website', 'doctype', 'portal_menu_item')
frappe.get_doc('Portal Settings').sync_menu()

View File

@@ -0,0 +1,33 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
# new field address_html is created in place of address field for the company's address in PR #8754 (without patch)
# so here is the patch for moving the address details in the address doc
company_list = []
if 'address' in frappe.db.get_table_columns('Company'):
company_list = frappe.db.sql('''select name, address from `tabCompany`
where address is not null and address != ""''', as_dict=1)
for company in company_list:
add_list = company.address.split(" ")
if ',' in company.address:
add_list = company.address.rpartition(',')
elif ' ' in company.address:
add_list = company.address.rpartition(' ')
else:
add_list = [company.address, None, company.address]
doc = frappe.get_doc({
"doctype":"Address",
"address_line1": add_list[0],
"city": add_list[2],
"links": [{
"link_doctype": "Company",
"link_name": company.name
}]
})
doc.save()

View File

@@ -9,8 +9,8 @@ from frappe.model.mapper import get_mapped_doc
def execute():
# for converting student batch into student group
for doctype in ["Student Group", "Student Group Student", "Student Group Instructor", "Student Attendance"]:
frappe.reload_doc("schools", "doctype", doctype)
for doctype in ["Student Group", "Student Group Student", "Student Group Instructor", "Student Attendance", "Student"]:
frappe.reload_doc("schools", "doctype", frappe.scrub(doctype))
if frappe.db.table_exists("Student Batch"):
student_batches = frappe.db.sql('''select name as student_group_name, student_batch_name as batch,

View File

@@ -0,0 +1,10 @@
import frappe
from erpnext.regional.india.setup import setup
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
# call setup for india
setup(patch=True)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def execute():
for dt in ("Sales Order Item", "Purchase Order Item",
"Material Request Item", "Production Order Item", "Packed Item"):
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")

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
def execute():
frappe.db.sql(""" update `tabAuto Email Report` set report = %s
where name = %s""", ('Support Hour Distribution', 'Support Hours'))
frappe.db.sql(""" update `tabCustom Role` set report = %s
where report = %s""", ('Support Hour Distribution', 'Support Hours'))
frappe.delete_doc('Report', 'Support Hours')

View File

@@ -0,0 +1,13 @@
import frappe
def execute():
frappe.reload_doctype("Sales Order")
frappe.reload_doctype("Sales Order Item")
frappe.db.sql("""update `tabSales Order` set final_delivery_date = delivery_date where docstatus=1""")
frappe.db.sql("""
update `tabSales Order` so, `tabSales Order Item` so_item
set so_item.delivery_date = so.delivery_date
where so.name = so_item.parent
""")

View File

@@ -5,6 +5,7 @@ def execute():
frappe.reload_doc('regional', 'doctype', 'gst_settings')
frappe.reload_doc('regional', 'doctype', 'gst_hsn_code')
frappe.reload_doc('stock', 'doctype', 'item')
frappe.reload_doc("stock", "doctype", "customs_tariff_number")
for report_name in ('GST Sales Register', 'GST Purchase Register',
'GST Itemised Sales Register', 'GST Itemised Purchase Register'):

View File

@@ -0,0 +1,23 @@
# 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('Expense Claim')
for data in frappe.db.sql(""" select name from `tabExpense Claim`
where (docstatus=1 and total_sanctioned_amount=0 and status = 'Paid') or
(docstatus = 1 and approval_status = 'Rejected' and total_sanctioned_amount > 0)""", as_dict=1):
doc = frappe.get_doc('Expense Claim', data.name)
if doc.approval_status == 'Rejected':
for d in doc.expenses:
d.db_set("sanctioned_amount", 0, update_modified = False)
doc.db_set("total_sanctioned_amount", 0, update_modified = False)
frappe.db.sql(""" delete from `tabGL Entry` where voucher_type = 'Expense Claim'
and voucher_no = %s""", (doc.name))
doc.set_status()
doc.db_set("status", doc.status, update_modified = False)

View File

@@ -0,0 +1,14 @@
import frappe
from erpnext.regional.india import states
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
if not frappe.db.get_value("Custom Field", filters={'fieldname':'gst_state'}):
return
frappe.db.sql("update `tabCustom Field` set options=%s where fieldname='gst_state'", '\n'.join(states))
frappe.db.sql("update `tabAddress` set gst_state='Chhattisgarh' where gst_state='Chattisgarh'")
frappe.db.sql("update `tabAddress` set gst_state_number='05' where gst_state='Uttarakhand'")

View File

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 erpnext.setup.setup_wizard.domainify import update_module_def_restrict_to_domain
def execute():
""" set the restrict to domain in module def """
frappe.reload_doc("core", "doctype", "module_def")
if frappe.db.get_single_value('System Settings', 'setup_complete'):
update_module_def_restrict_to_domain()

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
from erpnext.setup.doctype.company.company import update_company_current_month_sales, update_company_monthly_sales
def execute():
'''Update company monthly sales history based on sales invoices'''
frappe.reload_doctype("Company")
companies = [d['name'] for d in frappe.get_list("Company")]
for company in companies:
update_company_current_month_sales(company)
update_company_monthly_sales(company)

View File

View File

@@ -0,0 +1,47 @@
import frappe
from erpnext.regional.india.setup import make_custom_fields
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_html
def execute():
companies = [d.name for d in frappe.get_all('Company', filters = {'country': 'India'})]
if not companies:
return
make_custom_fields()
# update invoice copy value
values = ["Original for Recipient", "Duplicate for Transporter",
"Duplicate for Supplier", "Triplicate for Supplier"]
for d in values:
frappe.db.sql("update `tabSales Invoice` set invoice_copy=%s where invoice_copy=%s", (d, d))
# update tax breakup in transactions made after 1st July 2017
doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice",
"Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"]
for doctype in doctypes:
frappe.reload_doctype(doctype)
date_field = "posting_date"
if doctype in ["Quotation", "Sales Order", "Supplier Quotation", "Purchase Order"]:
date_field = "transaction_date"
records = [d.name for d in frappe.get_all(doctype, filters={
"docstatus": ["!=", 2],
date_field: [">=", "2017-07-01"],
"company": ["in", companies],
"total_taxes_and_charges": [">", 0],
"other_charges_calculation": ""
})]
if records:
frappe.db.sql("""
update `tab%s Item` dt_item
set gst_hsn_code = (select gst_hsn_code from tabItem where name=dt_item.item_code)
where parent in (%s)
and (gst_hsn_code is null or gst_hsn_code = '')
""" % (doctype, ', '.join(['%s']*len(records))), tuple(records))
for record in records:
doc = frappe.get_doc(doctype, record)
html = get_itemised_tax_breakup_html(doc)
doc.db_set("other_charges_calculation", html, update_modified=False)

View File

@@ -3,6 +3,22 @@
frappe.ui.form.on("Project", {
onload: function(frm) {
frm.set_indicator_formatter('title',
function(doc) {
let indicator = 'orange';
if (doc.status == 'Overdue') {
indicator = 'red';
}
else if (doc.status == 'Cancelled') {
indicator = 'dark grey';
}
else if (doc.status == 'Closed') {
indicator = 'green';
}
return indicator;
}
);
var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function(field) {
if(frm.is_new()) return;
@@ -35,6 +51,7 @@ frappe.ui.form.on("Project", {
}
});
},
refresh: function(frm) {
if(frm.doc.__islocal) {
frm.web_link && frm.web_link.remove();

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