Compare commits

..

197 Commits

Author SHA1 Message Date
Pratik Vyas
399a3097e8 Merge branch 'develop' 2014-12-09 16:40:28 +05:30
Pratik Vyas
c8f5c3cdbe bumped to version 4.13.0 2014-12-09 17:10:28 +06:00
Nabin Hait
e83d506319 Merge pull request #2474 from nabinhait/fix1
Added leave approver name field in leave application
2014-12-09 16:37:40 +05:30
Nabin Hait
690bcd7b66 Added leave approver name field in leave application 2014-12-09 16:36:33 +05:30
Nabin Hait
f18d285eab Merge pull request #2473 from nabinhait/fix1
minor fix
2014-12-09 16:31:03 +05:30
Nabin Hait
7887ccb441 Credit days fix 2014-12-09 14:34:14 +05:30
Nabin Hait
d57b57a21d Minor fix in accounts receivable report 2014-12-09 11:56:13 +05:30
Nabin Hait
5cc0531d27 Merge pull request #2451 from revant/develop
"From time" cannot be later than "To time" and hours cannot be negative
2014-12-02 14:27:00 +05:30
Revant Nandgaonkar
0e5cdc8495 Merge branch 'develop' of https://github.com/revant/erpnext into develop 2014-12-02 14:21:15 +05:30
Revant Nandgaonkar
b80d892eab removed unecessary backslash 2014-12-02 14:19:29 +05:30
Revant Nandgaonkar
185af03fb2 Update time_log.py 2014-12-02 14:18:10 +05:30
Nabin Hait
abcbbc63d8 Merge pull request #2435 from nabinhait/fix1
Fixes in authorization rule based on average discount
2014-12-02 10:36:25 +05:30
Nabin Hait
6871c74ce1 Merge pull request #2444 from ankitjavalkarwork/contactmob
Display mobile no. of contact
2014-12-02 10:35:46 +05:30
Revant Nandgaonkar
d0a44ca85c Changed contact_date to Datetime so appointment or call with time can be scheduled 2014-12-01 23:31:53 +05:30
Revant Nandgaonkar
83db3e3ddb "From time" cannot be later than "To time" and hours cannot be negative 2014-12-01 16:53:29 +05:30
Nabin Hait
cc11045fd3 minor fix 2014-11-28 16:59:32 +05:30
Nabin Hait
1b5afe737f Fixes in authorization rule based on average discount 2014-11-28 16:59:32 +05:30
Rushabh Mehta
3d65d9602e [error-reports] 2014-11-28 14:58:43 +05:30
ankitjavalkarwork
9440d080d4 Print Hide Recurring Order/Invoice based fields 2014-11-27 17:28:53 +05:30
ankitjavalkarwork
9269c86339 Display mobile no of contact 2014-11-27 17:13:36 +05:30
Pratik Vyas
7c82d616c9 Merge branch 'develop' 2014-11-25 11:16:33 +05:30
Pratik Vyas
f227379d2f bumped to version 4.12.0 2014-11-25 11:46:33 +06:00
Nabin Hait
eba88919c1 Merge pull request #2431 from mayur-patel/patch-2
Update leave_application.py : Modified get_holidays function
2014-11-25 10:55:11 +05:30
Rushabh Mehta
3408432b50 Merge pull request #2428 from rmehta/translations-update
[translations] updated via frappe.io/translator
2014-11-25 10:54:36 +05:30
Rushabh Mehta
a0949158b7 [translations] updated via frappe.io/translator 2014-11-25 10:53:12 +05:30
Nabin Hait
ea91d2aaf1 Merge pull request #2429 from ankitjavalkarwork/expclaimname
Add configurable naming series to Expense claim
2014-11-25 10:51:03 +05:30
Rushabh Mehta
de992abf83 Merge pull request #2413 from Delte/patch-1
Update tr.csv
2014-11-25 10:49:51 +05:30
Mayur Patel
5d8635a8dc Update leave_application.py
See issue #2422 for more detail.
2014-11-24 16:57:53 +00:00
ankitjavalkarwork
7e911bae95 Add configurable naming series to Expense claim 2014-11-24 18:26:17 +05:30
Nabin Hait
0676cf6d3f Merge pull request #2430 from nabinhait/fix1
Multiple minor fixes
2014-11-24 18:21:37 +05:30
Nabin Hait
b74ae7aa31 Minor fix for order to invoice mapping 2014-11-24 18:17:01 +05:30
Nabin Hait
0ad7db3bbd Minor fix in salary slip net total calculation 2014-11-24 15:25:13 +05:30
Nabin Hait
db74e316d2 Validate serial no after auto-creation based on naming series 2014-11-24 15:25:13 +05:30
Nabin Hait
1897360e4b Set default language as English in setup wizard 2014-11-24 15:25:13 +05:30
Nabin Hait
e7fb957415 Merge pull request #2427 from rmehta/develop
[fix] client side queries
2014-11-24 15:23:27 +05:30
Rushabh Mehta
2f4567fa3c [minor] [ux] address refresh for lead 2014-11-24 15:18:40 +05:30
Rushabh Mehta
0a7abc188e [minor] [ux] contact template 2014-11-24 15:06:40 +05:30
Rushabh Mehta
5eeef7f065 [refactor] address and contact list in customer, supplier, lead, sales person 2014-11-24 14:16:51 +05:30
Rushabh Mehta
7d36875d6f [refactor] address and contact list in customer, supplier, lead, sales person 2014-11-24 14:16:47 +05:30
Delte
2fa718705a Update tr.csv
Typo & translation fix
2014-11-15 22:39:25 +02:00
Pratik Vyas
b1fdbf2335 Merge branch 'develop' 2014-11-14 15:27:34 +05:30
Pratik Vyas
2277922313 bumped to version 4.11.2 2014-11-14 15:57:34 +06:00
Nabin Hait
3e1029309c Merge pull request #2411 from nabinhait/fix1
minor fix
2014-11-14 15:19:51 +05:30
Nabin Hait
aa5deaa070 minor fix 2014-11-14 15:19:07 +05:30
Nabin Hait
6b5d51ca22 Merge pull request #2402 from jorxzpagta/patch-1
Update supplier.js [Displaying Messages on the Communation History]
2014-11-14 15:05:11 +05:30
Nabin Hait
2c114b5bb5 Merge pull request #2407 from ankitjavalkarwork/packingsliphead
Add letterhead field and mapper in Packing Slip
2014-11-14 14:50:28 +05:30
Nabin Hait
bd38a79e5e Merge pull request #2410 from nabinhait/fix1
Minor fixes
2014-11-14 14:48:02 +05:30
Nabin Hait
ccd9fd3e94 Rounding in totals calculation 2014-11-14 14:27:24 +05:30
Nabin Hait
4215b3afc3 temporary fix in payment tool 2014-11-14 10:51:31 +05:30
ankitjavalkarwork
f60f111afe Add letterhead field and mapper in Packing Slip 2014-11-13 17:28:42 +05:30
jorxzpagta
6fe0a3cee3 Update supplier.js
Display Messages on the Communication History
2014-11-12 17:02:06 +08:00
Nabin Hait
f004077734 Map only pending qty from Material Request to Purchase Order/Stock Entry 2014-11-11 10:51:41 +05:30
Nabin Hait
6c5cfd2148 Merge pull request #2396 from nabinhait/fix1
Reference / Cheque date is after due date
2014-11-10 18:07:52 +05:30
Nabin Hait
6c1011df92 Reference / Cheque date is after due date 2014-11-10 14:57:53 +05:30
Pratik Vyas
1582a0ce78 Merge branch 'develop' 2014-11-10 12:38:45 +05:30
Pratik Vyas
b5b821363d bumped to version 4.11.1 2014-11-10 13:08:45 +06:00
Nabin Hait
399afc87ef Merge pull request #2379 from nathando/develop
Remove get_server_field from leave_application
2014-11-10 12:05:40 +05:30
Nabin Hait
8c842af172 Merge pull request #2393 from nabinhait/fix1
Credit days and payment tool fixes
2014-11-10 11:44:37 +05:30
Nabin Hait
bf836277f9 Payment tool fix: get outstanding sales orders 2014-11-10 11:28:25 +05:30
Nabin Hait
10d1806d81 Credit days validation fixes 2014-11-10 11:11:58 +05:30
Pratik Vyas
bc41ce95fc Merge branch 'develop' 2014-11-06 14:36:51 +05:30
Pratik Vyas
4948d336c4 bumped to version 4.11.0 2014-11-06 15:06:51 +06:00
Nabin Hait
be2527d93d Merge pull request #2386 from nabinhait/fix1
Translation fixed for Croatia
2014-11-06 12:52:59 +05:30
Nabin Hait
7f5bb1c8aa Translation fixed for Croatia 2014-11-06 12:51:27 +05:30
Pratik Vyas
a8d40e4409 Merge branch 'develop' 2014-11-05 14:15:47 +05:30
Pratik Vyas
e9b4686fec bumped to version 4.10.0 2014-11-05 14:45:47 +06:00
Nabin Hait
5086ef2499 Update update_requested_and_ordered_qty.py 2014-11-05 12:33:29 +05:30
Nathan Do
8a55d9a795 Remove get_server_field from leave_application
- Make get_holidays and get_total_leave_days global to module
- Replace get_server_field with using whitelisted function
get_total_leave_days
2014-11-05 12:32:57 +08:00
Nabin Hait
1caca80203 Merge pull request #2378 from nabinhait/fix1
Requested and ordered qty calculation
2014-11-04 18:06:00 +05:30
Nabin Hait
9114c26857 Patch to recalculate requested qty and ordered qty for all items 2014-11-04 15:34:03 +05:30
Nabin Hait
941a965af4 Ordered qty calculation logic 2014-11-04 15:34:03 +05:30
Nabin Hait
4acd431b92 Requested qty calculation logic 2014-11-04 15:34:03 +05:30
Nabin Hait
fad0d566f9 Payment receipt voucher print format fixed 2014-11-04 15:34:03 +05:30
Pratik Vyas
e2d4079363 Merge branch 'develop' 2014-11-03 16:04:30 +05:30
Pratik Vyas
b9ce1f590b bumped to version 4.9.3 2014-11-03 16:34:29 +06:00
Nabin Hait
36463ed790 Merge pull request #2375 from nabinhait/fix1
Check credit limit only if customer debited
2014-11-03 15:55:03 +05:30
Nabin Hait
3c8838816d Check credit limit only if customer debited 2014-11-03 15:49:29 +05:30
Nabin Hait
11e50a8eee Merge pull request #2366 from ankitjavalkarwork/fixprinthideamt
Hide totals in DN, Auto set Bank/Cash acc. in JV
2014-11-03 15:38:45 +05:30
Nabin Hait
9de4c60bd6 Merge pull request #2374 from nabinhait/fix1
minor fix
2014-11-03 15:37:51 +05:30
Nabin Hait
5288bdeabb Minor fix 2014-11-03 15:08:21 +05:30
ankitjavalkarwork
f5804438bb Auto set default bank/cash account on select of Bank/Cash Voucher in JV 2014-11-03 12:23:55 +05:30
ankitjavalkarwork
6e06357dc5 Hide totals on checking print_without_amount field in DN 2014-11-03 11:57:44 +05:30
Nabin Hait
28913b97b0 [fix] Null issue fixed in stock analytics report 2014-10-31 14:46:25 +05:30
Pratik Vyas
c59cd46391 Merge branch 'develop' 2014-10-30 14:22:17 +05:30
Pratik Vyas
dda239fd49 bumped to version 4.9.2 2014-10-30 14:52:17 +06:00
Nabin Hait
b86a6ce26a Merge pull request #2358 from nabinhait/fix1
[fix] Floating point issue fixed in payment reconciliation
2014-10-29 14:26:41 +05:30
Nabin Hait
ee212e7bb5 [fix] Floating point issue fixed in payment reconciliation 2014-10-29 14:26:05 +05:30
Nabin Hait
4952c7b3b5 Merge pull request #2343 from ankitjavalkarwork/fixcustissue
Fix Customer Issue error on click event of Serial No. field
2014-10-27 11:40:18 +05:30
ankitjavalkarwork
4e61536f50 Fix Customer Issue error on click event of Serial No. field 2014-10-27 11:08:05 +05:30
Pratik Vyas
f2886f152b Merge pull request #2348 from pdvyas/fix-pdf-test
Fix PDF test
2014-10-27 10:47:00 +05:30
Pratik Vyas
b5c56f6cea [minor] add port to test site 2014-10-27 09:51:08 +05:30
Pratik Vyas
80e95388f5 Merge branch 'develop' 2014-10-27 09:26:36 +05:30
Pratik Vyas
daed0b655a bumped to version 4.9.1 2014-10-27 09:56:36 +06:00
Nabin Hait
fbb994c731 Merge pull request #2344 from nabinhait/fix1
cost center based on project
2014-10-23 13:30:51 +05:30
Nabin Hait
10b155a486 cost center based on project 2014-10-23 13:18:59 +05:30
Nabin Hait
ce6eda709b Merge pull request #2342 from ankitjavalkarwork/fixtimelogui
Change position of 'To Time' field for better UX
2014-10-22 16:57:52 +05:30
ankitjavalkarwork
14e1d20df3 Change position of 'To Time' field for better UX 2014-10-22 16:55:29 +05:30
Pratik Vyas
da8a02d56e Merge branch 'develop' 2014-10-22 14:19:17 +05:30
Pratik Vyas
16343683d9 bumped to version 4.9.0 2014-10-22 14:49:17 +06:00
Nabin Hait
2bdc017aff Merge pull request #2339 from nabinhait/fix1
Minor fixes
2014-10-22 13:11:21 +05:30
Nabin Hait
16e05c321c Removed stock ageing grid report 2014-10-22 13:10:43 +05:30
Nabin Hait
23d7919865 Stock ageing report fix 2014-10-22 13:06:26 +05:30
Nabin Hait
38265efc39 Minor fix 2014-10-21 20:23:39 +05:30
Rushabh Mehta
2d414706dc [hotfix] translation boo boo 2014-10-21 11:49:40 +05:30
Rushabh Mehta
d85e4b0d46 Merge pull request #2331 from rmehta/translation
[translations] update
2014-10-20 17:12:22 +05:30
Rushabh Mehta
45d03af8f6 [translations] update 2014-10-20 17:11:06 +05:30
Nabin Hait
73a3a2a131 Merge pull request #2327 from nabinhait/stock_reco
Fixes in credit note and quotation
2014-10-20 11:51:01 +05:30
Nabin Hait
73804580d4 validate quotation to lead or customer 2014-10-20 11:18:11 +05:30
Nabin Hait
259f9779db Credit Note: Set total amount and print format fix 2014-10-20 11:18:11 +05:30
Pratik Vyas
14e0e58a7d Merge branch 'develop' 2014-10-17 15:04:05 +05:30
Pratik Vyas
5eb373f7b5 bumped to version 4.8.0 2014-10-17 15:34:05 +06:00
Nabin Hait
4595c30a7b Merge pull request #2322 from nabinhait/stock_reco
Partial payment reconciliation
2014-10-17 15:01:13 +05:30
Nabin Hait
fc2dd44694 Partial payment reconciliation. Fixes #1982 2014-10-17 14:57:20 +05:30
Pratik Vyas
053c54017e [minor] add admin password to travis test site 2014-10-17 14:16:10 +05:30
Pratik Vyas
af473d78f2 Merge branch 'develop' 2014-10-15 15:34:39 +05:30
Pratik Vyas
26d096aa87 bumped to version 4.7.2 2014-10-15 16:04:39 +06:00
Nabin Hait
476c613ba6 Merge pull request #2317 from nabinhait/stock_reco
repost stock utility
2014-10-15 15:33:50 +05:30
Nabin Hait
2348a5f592 repost stock utility 2014-10-15 15:31:33 +05:30
Pratik Vyas
9d8d045c66 Merge branch 'develop' 2014-10-15 14:44:35 +05:30
Pratik Vyas
63914dd55b bumped to version 4.7.1 2014-10-15 15:14:35 +06:00
Nabin Hait
93a8042f08 Merge pull request #2316 from nabinhait/stock_reco
Minor fix
2014-10-15 14:44:06 +05:30
Nabin Hait
b2f32dac1b Minor fix 2014-10-15 14:42:18 +05:30
Pratik Vyas
577a3acaac Merge branch 'develop' 2014-10-15 14:35:47 +05:30
Pratik Vyas
5e46ce8a50 bumped to version 4.7.0 2014-10-15 15:05:47 +06:00
Nabin Hait
3b7342b7b5 Merge pull request #2315 from nabinhait/stock_reco
Strip company in setup wizard
2014-10-15 13:59:07 +05:30
Nabin Hait
531077e504 Strip company in setup wizard 2014-10-15 13:55:37 +05:30
Nabin Hait
147918ed66 Merge pull request #2313 from nabinhait/stock_reco
Fix gl entries for stock transactions
2014-10-15 12:44:59 +05:30
Nabin Hait
9bbfca9226 Fix gl entries for stock transactions 2014-10-15 12:24:38 +05:30
Nabin Hait
f1a07ff105 Allow zero rate while reposting 2014-10-15 12:23:35 +05:30
Nabin Hait
fb6e434315 Guess valuation rate in case of negative stock 2014-10-15 11:34:40 +05:30
Rushabh Mehta
3811d96feb Merge pull request #2308 from devdesco-ceo/patch-1
Update README.md
2014-10-15 10:02:06 +05:30
Viktor Zhuromskyy
1017615d02 Update README.md 2014-10-14 10:12:10 -05:00
Pratik Vyas
056d627f46 Merge branch 'develop' 2014-10-14 17:34:01 +05:30
Pratik Vyas
663bfeacf4 bumped to version 4.6.2 2014-10-14 18:04:01 +06:00
Nabin Hait
e918ebd721 Merge pull request #2307 from nabinhait/stock_reco
Minor fixes
2014-10-14 17:16:32 +05:30
Nabin Hait
0dc18f0102 Minor fixes 2014-10-14 17:15:02 +05:30
Pratik Vyas
8ddc882a66 Merge branch 'develop' 2014-10-14 16:16:29 +05:30
Pratik Vyas
49365d0982 bumped to version 4.6.1 2014-10-14 16:46:29 +06:00
Nabin Hait
50c29c7d0f Merge pull request #2306 from nabinhait/stock_reco
Negative stock in perpetual inventory
2014-10-14 16:10:46 +05:30
Nabin Hait
bf492122f8 minor fix 2014-10-14 16:09:14 +05:30
Nabin Hait
38d0ed9f3a Negative stock allowed for perpetual inventory. Blocked in specific case 2014-10-14 16:08:19 +05:30
Pratik Vyas
248a65b37d Merge branch 'develop' 2014-10-14 12:37:07 +05:30
Pratik Vyas
78f86e9385 bumped to version 4.6.0 2014-10-14 13:07:07 +06:00
Nabin Hait
d9f3e0c275 Merge pull request #2297 from neilLasrado/bom-issue
Fix - BOM calculated wrong cost on update cost
2014-10-14 12:28:16 +05:30
Neil Trini Lasrado
c40451ee2e Patch Fixed 2014-10-14 12:14:39 +05:30
Neil Trini Lasrado
6644406185 Fix - BOM calculated wrong cost on update cost 2014-10-14 12:14:38 +05:30
Nabin Hait
b3a962e121 Merge pull request #2304 from nabinhait/stock_reco
Repost gl entries where mismatch with stock balance
2014-10-14 11:58:41 +05:30
Nabin Hait
8a28ccfa2f Repost gl entries where mismatch with stock balance 2014-10-14 11:41:56 +05:30
Nabin Hait
0a75fa09ef Merge pull request #2300 from nabinhait/stock_reco
Minor fixes
2014-10-13 11:52:00 +05:30
Nabin Hait
17a16eeaf4 Tets case fixed for payment tool 2014-10-13 11:43:14 +05:30
Nabin Hait
3d3f0bcf54 Minor fix in setup wizard 2014-10-13 11:32:41 +05:30
Nabin Hait
bc8b20ae3c Allocate entire advance amount if advance against SO/PO 2014-10-13 10:48:26 +05:30
Nabin Hait
daf344e5fd Precision fixed in batch-wise balance report 2014-10-13 10:48:26 +05:30
Pratik Vyas
7916792f99 Merge pull request #2298 from pdvyas/fix-mariadb
add my_config patch to travis
2014-10-12 19:39:52 +05:30
Pratik Vyas
27e37e68b2 add my_config patch to travis 2014-10-12 19:35:46 +05:30
Nabin Hait
a538f8a24a Merge pull request #2296 from nabinhait/stock_reco
Stock balance report and valuation fixes
2014-10-10 21:23:30 +05:30
Nabin Hait
70ec88b733 fixed test cases 2014-10-10 21:22:46 +05:30
Nabin Hait
4f0e5db216 Stock balance grid report deprecated and moved to server side 2014-10-10 20:54:57 +05:30
Nabin Hait
79ed124939 Update journal_voucher.py 2014-10-10 18:19:03 +05:30
Nabin Hait
c0a3cd603b Merge pull request #2290 from ankitjavalkarwork/accreport
Bug Fixes
2014-10-10 18:11:36 +05:30
Nabin Hait
7c6f990cf9 Minor fix for moving average 2014-10-10 18:03:27 +05:30
Nabin Hait
7820b171d3 Stock balance grid report deprecated and moved to server side 2014-10-10 18:03:07 +05:30
ankitjavalkarwork
0cf4cc283c Add Shipping Addr to Sales Invoice 2014-10-10 16:55:03 +05:30
ankitjavalkarwork
9a4b173b88 Add validation for stopped orders, advance payment in journal voucher 2014-10-10 16:28:41 +05:30
ankitjavalkarwork
6d83454237 Disallow Stopped Orders in Against Voucher table 2014-10-10 13:30:10 +05:30
ankitjavalkarwork
ff231b5e62 Allow advance JV payments in Accounts Receivable/Payable 2014-10-10 13:13:39 +05:30
Nabin Hait
4d74216147 Maintain negative stock balance if balance qty is negative 2014-10-09 19:25:19 +05:30
Nabin Hait
b7e5ad0a31 Merge pull request #2276 from ankitjavalkarwork/sopofield
Rearrange To/From, Recurring Type field for better UX
2014-10-09 11:58:13 +05:30
Pratik Vyas
e435592d64 [minor] Fix default website style patch (reload doc) 2014-10-09 11:22:12 +05:30
Nabin Hait
7ddde8dc3a Merge pull request #2278 from nabinhait/stock_reco
stock reco and repost vouchers
2014-10-08 18:38:54 +05:30
Nabin Hait
fce2881de6 minor fix 2014-10-08 18:38:27 +05:30
Nabin Hait
e96e83d557 stock reco and repost vouchers 2014-10-08 18:06:14 +05:30
ankitjavalkarwork
1b2944e871 Rearrange To/From, Recurring Type field for better UX 2014-10-08 14:24:58 +05:30
Rushabh Mehta
074e73a0dd [translations] updated via translator 2014-10-08 14:16:33 +05:30
Nabin Hait
8923801881 Update stock_entry.py 2014-10-08 13:20:31 +05:30
Nabin Hait
cfafe93391 Merge pull request #2274 from nabinhait/stock_reco
Stock reco
2014-10-08 11:03:03 +05:30
Nabin Hait
6c48ef781b Utility: Repost stock ledger entries and gl entries for all stock transactions 2014-10-08 11:02:18 +05:30
Nabin Hait
adeb976a1b Block negative stock in perpetual inventory 2014-10-08 11:02:18 +05:30
Nabin Hait
bb19b91ef9 stock reco fixes 2014-10-08 11:02:18 +05:30
Nabin Hait
bfa7f171bd Stock reconciliation sl entries 2014-10-08 11:02:18 +05:30
Nabin Hait
b96c014daf Stock Reconciliation logic simplified 2014-10-08 11:02:18 +05:30
Pratik Vyas
e0c83e22d9 Merge branch 'develop' 2014-10-07 17:05:32 +05:30
pdvyas
099ad0f5e1 bumped to version 4.5.2 2014-10-07 17:35:32 +06:00
Nabin Hait
cf9746dd84 Merge pull request #2270 from nabinhait/fix2
Voucher dynamic link in general ledger report
2014-10-07 11:33:40 +05:30
Nabin Hait
b70712dbba Voucher dynamic link in general ledger report 2014-10-07 11:32:54 +05:30
Rushabh Mehta
4c057fe693 Merge pull request #2269 from rmehta/fixes
[fix] packing slip
2014-10-07 11:06:08 +05:30
Nabin Hait
ee8ff51d60 Merge pull request #2261 from ankitjavalkarwork/sopofield
[#2253] Rearrange To/From fields and depend on is_recurring
2014-10-07 10:52:10 +05:30
Rushabh Mehta
9974b16c32 [fix] packing slip 2014-10-07 10:52:05 +05:30
ankitjavalkarwork
9e5f319d80 [#2253] Rearrange To/From fields and depend on is_recurring in SI,SO,PI,PO 2014-10-06 12:58:38 +05:30
Nabin Hait
4f614b4030 Merge pull request #2195 from neilLasrado/manufacture-and-repack
[fix] Issue #2183 - Manufacturing and repack seprated in stock entry
2014-09-30 15:11:05 +05:30
Nabin Hait
3d458e973e Merge pull request #2236 from neilLasrado/upstream/develop
Has Batch No field should be freezed #2023
2014-09-30 14:56:15 +05:30
Neil Trini Lasrado
3a19a71262 patch fixed 2014-09-30 14:48:38 +05:30
Neil Trini Lasrado
29d1a1c593 manufacturing and repack sepreted, test cases fixed, patch fixed 2014-09-30 14:07:56 +05:30
Neil Trini Lasrado
3b90de558f Has Batch No field should be freezed #2023 2014-09-30 12:57:32 +05:30
Nabin Hait
af7e31acb3 Merge pull request #2235 from adityaduggal/develop
Added the dynamic link field in the accounts receivable report.
2014-09-30 10:39:40 +05:30
Aditya Duggal
a2c9d35efb Added the dynamic link field. 2014-09-29 17:44:28 +05:30
Aditya Duggal
4c058f4056 Added the dynamic link and removed the link column 2014-09-29 17:41:19 +05:30
160 changed files with 8467 additions and 8797 deletions

View File

@@ -14,6 +14,8 @@ install:
- sudo apt-get update - sudo apt-get update
- sudo apt-get purge -y mysql-common - sudo apt-get purge -y mysql-common
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev - sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev
- ./ci/fix-mariadb.sh
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb - wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb
- sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb - sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb
- CFLAGS=-O0 pip install git+https://github.com/frappe/frappe.git@develop - CFLAGS=-O0 pip install git+https://github.com/frappe/frappe.git@develop

View File

@@ -4,7 +4,7 @@
Includes Accounting, Inventory, CRM, Sales, Purchase, Projects, HRMS. Built on Python / MariaDB. Includes Accounting, Inventory, CRM, Sales, Purchase, Projects, HRMS. Built on Python / MariaDB.
ERPNext is built on [frappe](https://github.com/frappe/frappe) ERPNext is built on [frappe](https://github.com/frappe/frappe) Python Framework.
- [User Guide](http://erpnext.org/user-guide.html) - [User Guide](http://erpnext.org/user-guide.html)
- [Getting Help](http://erpnext.org/getting-help.html) - [Getting Help](http://erpnext.org/getting-help.html)
@@ -21,7 +21,7 @@ Use the bench, https://github.com/frappe/bench
1. go to "/login" 1. go to "/login"
1. Administrator user name: "Administrator" 1. Administrator user name: "Administrator"
1. Administrator passowrd "admin" 1. Administrator password: "admin"
### Download and Install ### Download and Install

11
ci/fix-mariadb.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# stolen from http://cgit.drupalcode.org/octopus/commit/?id=db4f837
includedir=`mysql_config --variable=pkgincludedir`
thiscwd=`pwd`
_THIS_DB_VERSION=`mysql -V 2>&1 | tr -d "\n" | cut -d" " -f6 | awk '{ print $1}' | cut -d"-" -f1 | awk '{ print $1}' | sed "s/[\,']//g"`
if [ "$_THIS_DB_VERSION" = "5.5.40" ] && [ ! -e "$includedir-$_THIS_DB_VERSION-fixed.log" ] ; then
cd $includedir
sudo patch -p1 < $thiscwd/ci/my_config.h.patch &> /dev/null
sudo touch $includedir-$_THIS_DB_VERSION-fixed.log
fi

22
ci/my_config.h.patch Normal file
View File

@@ -0,0 +1,22 @@
diff -burp a/my_config.h b/my_config.h
--- a/my_config.h 2014-10-09 19:32:46.000000000 -0400
+++ b/my_config.h 2014-10-09 19:35:12.000000000 -0400
@@ -641,17 +641,4 @@
#define SIZEOF_TIME_T 8
/* #undef TIME_T_UNSIGNED */
-/*
- stat structure (from <sys/stat.h>) is conditionally defined
- to have different layout and size depending on the defined macros.
- The correct macro is defined in my_config.h, which means it MUST be
- included first (or at least before <features.h> - so, practically,
- before including any system headers).
-
- __GLIBC__ is defined in <features.h>
-*/
-#ifdef __GLIBC__
-#error <my_config.h> MUST be included first!
-#endif
-
#endif

View File

@@ -1 +1 @@
__version__ = '4.5.1' __version__ = '4.13.0'

View File

@@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt, cstr, cint, getdate, add_days, formatdate from frappe.utils import flt, cstr, cint, getdate
from frappe import msgprint, throw, _ from frappe import msgprint, throw, _
from frappe.model.document import Document from frappe.model.document import Document
@@ -176,15 +176,7 @@ class Account(Document):
frappe.throw(_("Due Date cannot be before Posting Date")) frappe.throw(_("Due Date cannot be before Posting Date"))
elif credit_days is not None and diff > credit_days: elif credit_days is not None and diff > credit_days:
is_credit_controller = frappe.db.get_value("Accounts Settings", None, msgprint(_("Note: Due Date exceeds the allowed credit days by {0} day(s)").format(diff - credit_days))
"credit_controller") in frappe.user.get_roles()
if is_credit_controller:
msgprint(_("Note: Due Date exceeds the allowed credit days by {0} day(s)").format(
diff - credit_days))
else:
max_due_date = formatdate(add_days(posting_date, credit_days))
frappe.throw(_("Due Date cannot be after {0}").format(max_due_date))
def validate_trash(self): def validate_trash(self):
"""checks gl entries and if child exists""" """checks gl entries and if child exists"""

View File

@@ -124,6 +124,10 @@ def update_outstanding_amt(account, against_voucher_type, against_voucher, on_ca
from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s
and account = %s and ifnull(against_voucher, '') = ''""", and account = %s and ifnull(against_voucher, '') = ''""",
(against_voucher, account))[0][0]) (against_voucher, account))[0][0])
if not against_voucher_amount:
frappe.throw(_("Against Journal Voucher {0} is already adjusted against some other voucher")
.format(against_voucher))
bal = against_voucher_amount + bal bal = against_voucher_amount + bal
if against_voucher_amount < 0: if against_voucher_amount < 0:
bal = -bal bal = -bal

View File

@@ -213,10 +213,11 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
return; return;
var update_jv_details = function(doc, r) { var update_jv_details = function(doc, r) {
$.each(r.message, function(i, d) {
var jvdetail = frappe.model.add_child(doc, "Journal Voucher Detail", "entries"); var jvdetail = frappe.model.add_child(doc, "Journal Voucher Detail", "entries");
jvdetail.account = d.account; $.each(r, function(i, d) {
jvdetail.balance = d.balance; var row = frappe.model.add_child(doc, "Journal Voucher Detail", "entries");
row.account = d.account;
row.balance = d.balance;
}); });
refresh_field("entries"); refresh_field("entries");
} }
@@ -231,7 +232,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
}, },
callback: function(r) { callback: function(r) {
if(r.message) { if(r.message) {
update_jv_details(doc, r); update_jv_details(doc, [r.message]);
} }
} }
}) })
@@ -245,7 +246,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) {
callback: function(r) { callback: function(r) {
frappe.model.clear_table(doc, "entries"); frappe.model.clear_table(doc, "entries");
if(r.message) { if(r.message) {
update_jv_details(doc, r); update_jv_details(doc, r.message);
} }
cur_frm.set_value("is_opening", "Yes") cur_frm.set_value("is_opening", "Yes")
} }

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, cstr, flt, fmt_money, formatdate, getdate from frappe.utils import cstr, flt, fmt_money, formatdate, getdate
from frappe import msgprint, _, scrub from frappe import msgprint, _, scrub
from erpnext.setup.utils import get_company_currency from erpnext.setup.utils import get_company_currency
@@ -13,10 +13,6 @@ from erpnext.controllers.accounts_controller import AccountsController
class JournalVoucher(AccountsController): class JournalVoucher(AccountsController):
def __init__(self, arg1, arg2=None): def __init__(self, arg1, arg2=None):
super(JournalVoucher, self).__init__(arg1, arg2) super(JournalVoucher, self).__init__(arg1, arg2)
self.master_type = {}
self.credit_days_for = {}
self.credit_days_global = -1
self.is_approving_authority = -1
def validate(self): def validate(self):
if not self.is_opening: if not self.is_opening:
@@ -40,7 +36,7 @@ class JournalVoucher(AccountsController):
def on_submit(self): def on_submit(self):
if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']: if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']:
self.check_credit_days() self.check_reference_date()
self.make_gl_entries() self.make_gl_entries()
self.check_credit_limit() self.check_credit_limit()
self.update_advance_paid() self.update_advance_paid()
@@ -76,26 +72,36 @@ class JournalVoucher(AccountsController):
def validate_entries_for_advance(self): def validate_entries_for_advance(self):
for d in self.get('entries'): for d in self.get('entries'):
if not d.is_advance and not d.against_voucher and \ if not (d.against_voucher and d.against_invoice and d.against_jv):
not d.against_invoice and not d.against_jv:
master_type = frappe.db.get_value("Account", d.account, "master_type") master_type = frappe.db.get_value("Account", d.account, "master_type")
if (master_type == 'Customer' and flt(d.credit) > 0) or \ if (master_type == 'Customer' and flt(d.credit) > 0) or \
(master_type == 'Supplier' and flt(d.debit) > 0): (master_type == 'Supplier' and flt(d.debit) > 0):
msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this \ if not d.is_advance:
is an advance entry.").format(d.idx, d.account)) msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account))
elif (d.against_sales_order or d.against_purchase_order) and d.is_advance != "Yes":
frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx))
def validate_against_jv(self): def validate_against_jv(self):
for d in self.get('entries'): for d in self.get('entries'):
if d.against_jv: if d.against_jv:
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(_("For {0}, only credit entries can be linked against another debit entry")
.format(d.account))
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(_("For {0}, only debit entries can be linked against another credit entry")
.format(d.account))
if d.against_jv == self.name: if d.against_jv == self.name:
frappe.throw(_("You can not enter current voucher in 'Against Journal Voucher' column")) frappe.throw(_("You can not enter current voucher in 'Against Journal Voucher' column"))
against_entries = frappe.db.sql("""select * from `tabJournal Voucher Detail` against_entries = frappe.db.sql("""select * from `tabJournal Voucher Detail`
where account = %s and docstatus = 1 and parent = %s where account = %s and docstatus = 1 and parent = %s
and ifnull(against_jv, '') = ''""", (d.account, d.against_jv), as_dict=True) and ifnull(against_jv, '') = '' and ifnull(against_invoice, '') = ''
and ifnull(against_voucher, '') = ''""", (d.account, d.against_jv), as_dict=True)
if not against_entries: if not against_entries:
frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched") frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched against other voucher")
.format(d.against_jv, d.account)) .format(d.against_jv, d.account))
else: else:
dr_or_cr = "debit" if d.credit > 0 else "credit" dr_or_cr = "debit" if d.credit > 0 else "credit"
@@ -177,7 +183,7 @@ class JournalVoucher(AccountsController):
def validate_against_order_fields(self, doctype, payment_against_voucher): def validate_against_order_fields(self, doctype, payment_against_voucher):
for voucher_no, payment_list in payment_against_voucher.items(): for voucher_no, payment_list in payment_against_voucher.items():
voucher_properties = frappe.db.get_value(doctype, voucher_no, voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "per_billed", "advance_paid", "grand_total"]) ["docstatus", "per_billed", "status", "advance_paid", "grand_total"])
if voucher_properties[0] != 1: if voucher_properties[0] != 1:
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no)) frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no))
@@ -185,7 +191,10 @@ class JournalVoucher(AccountsController):
if flt(voucher_properties[1]) >= 100: if flt(voucher_properties[1]) >= 100:
frappe.throw(_("{0} {1} is fully billed").format(doctype, voucher_no)) frappe.throw(_("{0} {1} is fully billed").format(doctype, voucher_no))
if flt(voucher_properties[3]) < flt(voucher_properties[2]) + flt(sum(payment_list)): if cstr(voucher_properties[2]) == "Stopped":
frappe.throw(_("{0} {1} is stopped").format(doctype, voucher_no))
if flt(voucher_properties[4]) < flt(voucher_properties[3]) + flt(sum(payment_list)):
frappe.throw(_("Advance paid against {0} {1} cannot be greater \ frappe.throw(_("Advance paid against {0} {1} cannot be greater \
than Grand Total {2}").format(doctype, voucher_no, voucher_properties[3])) than Grand Total {2}").format(doctype, voucher_no, voucher_properties[3]))
@@ -276,71 +285,38 @@ class JournalVoucher(AccountsController):
def set_print_format_fields(self): def set_print_format_fields(self):
for d in self.get('entries'): for d in self.get('entries'):
result = frappe.db.get_value("Account", d.account, acc = frappe.db.get_value("Account", d.account, ["account_type", "master_type"], as_dict=1)
["account_type", "master_type"])
if not result: if not acc: continue
continue
account_type, master_type = result if acc.master_type in ['Supplier', 'Customer']:
if master_type in ['Supplier', 'Customer']:
if not self.pay_to_recd_from: if not self.pay_to_recd_from:
self.pay_to_recd_from = frappe.db.get_value(master_type, self.pay_to_recd_from = frappe.db.get_value(acc.master_type, ' - '.join(d.account.split(' - ')[:-1]),
' - '.join(d.account.split(' - ')[:-1]), acc.master_type == 'Customer' and 'customer_name' or 'supplier_name')
master_type == 'Customer' and 'customer_name' or 'supplier_name') if self.voucher_type in ["Credit Note", "Debit Note"]:
self.set_total_amount(d.debit or d.credit)
if account_type in ['Bank', 'Cash']: if acc.account_type in ['Bank', 'Cash']:
self.set_total_amount(d.debit or d.credit)
def set_total_amount(self, amt):
company_currency = get_company_currency(self.company) company_currency = get_company_currency(self.company)
amt = flt(d.debit) and d.debit or d.credit
self.total_amount = fmt_money(amt, currency=company_currency) self.total_amount = fmt_money(amt, currency=company_currency)
from frappe.utils import money_in_words from frappe.utils import money_in_words
self.total_amount_in_words = money_in_words(amt, company_currency) self.total_amount_in_words = money_in_words(amt, company_currency)
def check_credit_days(self): def check_reference_date(self):
date_diff = 0
if self.cheque_date: if self.cheque_date:
date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days for d in self.get("entries"):
due_date = None
if d.against_invoice and flt(d.credit) > 0:
due_date = frappe.db.get_value("Sales Invoice", d.against_invoice, "due_date")
elif d.against_voucher and flt(d.debit) > 0:
due_date = frappe.db.get_value("Purchase Invoice", d.against_voucher, "due_date")
if date_diff <= 0: return if due_date and getdate(self.cheque_date) > getdate(due_date):
msgprint(_("Note: Reference Date {0} is after invoice due date {1}")
# Get List of Customer Account .format(formatdate(self.cheque_date), formatdate(due_date)))
acc_list = filter(lambda d: frappe.db.get_value("Account", d.account,
"master_type")=='Customer', self.get('entries'))
for d in acc_list:
credit_days = self.get_credit_days_for(d.account)
# Check credit days
if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days:
msgprint(_("Maximum allowed credit is {0} days after posting date").format(credit_days),
raise_exception=1)
def get_credit_days_for(self, ac):
if not self.credit_days_for.has_key(ac):
self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days"))
if not self.credit_days_for[ac]:
if self.credit_days_global==-1:
self.credit_days_global = cint(frappe.db.get_value("Company",
self.company, "credit_days"))
return self.credit_days_global
else:
return self.credit_days_for[ac]
def get_authorized_user(self):
if self.is_approving_authority==-1:
self.is_approving_authority = 0
# Fetch credit controller role
approving_authority = frappe.db.get_value("Accounts Settings", None,
"credit_controller")
# Check logged-in user is authorized
if approving_authority in frappe.user.get_roles():
self.is_approving_authority = 1
return self.is_approving_authority
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
@@ -373,7 +349,7 @@ class JournalVoucher(AccountsController):
for d in self.get("entries"): for d in self.get("entries"):
master_type, master_name = frappe.db.get_value("Account", d.account, master_type, master_name = frappe.db.get_value("Account", d.account,
["master_type", "master_name"]) ["master_type", "master_name"])
if master_type == "Customer" and master_name: if master_type == "Customer" and master_name and flt(d.debit) > 0:
super(JournalVoucher, self).check_credit_limit(d.account) super(JournalVoucher, self).check_credit_limit(d.account)
def get_balance(self): def get_balance(self):
@@ -528,9 +504,10 @@ def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filter
(filters["account"], "%%%s%%" % txt, start, page_len)) (filters["account"], "%%%s%%" % txt, start, page_len))
def get_against_jv(doctype, txt, searchfield, start, page_len, filters): def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark return frappe.db.sql("""select distinct jv.name, jv.posting_date, jv.user_remark
from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jvd
where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1 where jvd.parent = jv.name and jvd.account = %s and jv.docstatus = 1
and (ifnull(jvd.against_invoice, '') = '' and ifnull(jvd.against_voucher, '') = '' and ifnull(jvd.against_jv, '') = '' )
and jv.%s like %s order by jv.name desc limit %s, %s""" % and jv.%s like %s order by jv.name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"), ("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len)) (filters["account"], "%%%s%%" % txt, start, page_len))

View File

@@ -35,10 +35,6 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
}; };
} }
}); });
var help_content = '<i class="icon-hand-right"></i> ' + __("Note") + ':<br>'+
'<ul>' + __("If you are unable to match the exact amount, then amend your Journal Voucher and split rows such that payment amount match the invoice amount.") + '</ul>';
this.frm.set_value("reconcile_help", help_content);
}, },
get_unreconciled_entries: function() { get_unreconciled_entries: function() {

View File

@@ -118,19 +118,12 @@
"options": "Payment Reconciliation Invoice", "options": "Payment Reconciliation Invoice",
"permlevel": 0, "permlevel": 0,
"read_only": 1 "read_only": 1
},
{
"fieldname": "reconcile_help",
"fieldtype": "Small Text",
"label": "",
"permlevel": 0,
"read_only": 1
} }
], ],
"hide_toolbar": 1, "hide_toolbar": 1,
"icon": "icon-resize-horizontal", "icon": "icon-resize-horizontal",
"issingle": 1, "issingle": 1,
"modified": "2014-07-31 05:43:03.410832", "modified": "2014-10-16 17:51:44.367107",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation", "name": "Payment Reconciliation",

View File

@@ -96,13 +96,14 @@ class PaymentReconciliation(Document):
payment_amount = payment_amount[0][0] if payment_amount else 0 payment_amount = payment_amount[0][0] if payment_amount else 0
if d.invoice_amount > payment_amount: if d.invoice_amount - payment_amount > 0.005:
non_reconciled_invoices.append({ non_reconciled_invoices.append({
'voucher_no': d.voucher_no, 'voucher_no': d.voucher_no,
'voucher_type': d.voucher_type, 'voucher_type': d.voucher_type,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
'invoice_amount': flt(d.invoice_amount), 'invoice_amount': flt(d.invoice_amount),
'outstanding_amount': d.invoice_amount - payment_amount}) 'outstanding_amount': flt(d.invoice_amount - payment_amount, 2)
})
self.add_invoice_entries(non_reconciled_invoices) self.add_invoice_entries(non_reconciled_invoices)
@@ -124,7 +125,7 @@ class PaymentReconciliation(Document):
dr_or_cr = "credit" if self.party_type == "Customer" else "debit" dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
lst = [] lst = []
for e in self.get('payment_reconciliation_payments'): for e in self.get('payment_reconciliation_payments'):
if e.invoice_type and e.invoice_number: if e.invoice_type and e.invoice_number and e.allocated_amount:
lst.append({ lst.append({
'voucher_no' : e.journal_voucher, 'voucher_no' : e.journal_voucher,
'voucher_detail_no' : e.voucher_detail_number, 'voucher_detail_no' : e.voucher_detail_number,
@@ -134,7 +135,7 @@ class PaymentReconciliation(Document):
'is_advance' : e.is_advance, 'is_advance' : e.is_advance,
'dr_or_cr' : dr_or_cr, 'dr_or_cr' : dr_or_cr,
'unadjusted_amt' : flt(e.amount), 'unadjusted_amt' : flt(e.amount),
'allocated_amt' : flt(e.amount) 'allocated_amt' : flt(e.allocated_amount)
}) })
if lst: if lst:
@@ -162,18 +163,23 @@ class PaymentReconciliation(Document):
invoices_to_reconcile = [] invoices_to_reconcile = []
for p in self.get("payment_reconciliation_payments"): for p in self.get("payment_reconciliation_payments"):
if p.invoice_type and p.invoice_number: if p.invoice_type and p.invoice_number and p.allocated_amount:
invoices_to_reconcile.append(p.invoice_number) invoices_to_reconcile.append(p.invoice_number)
if p.invoice_number not in unreconciled_invoices.get(p.invoice_type, {}): if p.invoice_number not in unreconciled_invoices.get(p.invoice_type, {}):
frappe.throw(_("{0}: {1} not found in Invoice Details table") frappe.throw(_("{0}: {1} not found in Invoice Details table")
.format(p.invoice_type, p.invoice_number)) .format(p.invoice_type, p.invoice_number))
if p.amount > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number): if flt(p.allocated_amount) > flt(p.amount):
frappe.throw(_("Row {0}: Payment amount must be less than or equals to invoice outstanding amount. Please refer Note below.").format(p.idx)) frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to JV amount {2}")
.format(p.idx, p.allocated_amount, p.amount))
if flt(p.allocated_amount) > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number):
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
.format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)))
if not invoices_to_reconcile: if not invoices_to_reconcile:
frappe.throw(_("Please select Invoice Type and Invoice Number in atleast one row")) frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
def check_condition(self, dr_or_cr): def check_condition(self, dr_or_cr):
cond = self.from_date and " and posting_date >= '" + self.from_date + "'" or "" cond = self.from_date and " and posting_date >= '" + self.from_date + "'" or ""

View File

@@ -53,11 +53,20 @@
"label": "Column Break", "label": "Column Break",
"permlevel": 0 "permlevel": 0
}, },
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
"permlevel": 0,
"precision": "",
"reqd": 1
},
{ {
"default": "Sales Invoice", "default": "Sales Invoice",
"fieldname": "invoice_type", "fieldname": "invoice_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 0,
"label": "Invoice Type", "label": "Invoice Type",
"options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher", "options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher",
"permlevel": 0, "permlevel": 0,
@@ -95,7 +104,7 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2014-07-21 16:53:56.206169", "modified": "2014-10-16 17:40:54.040194",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Payment", "name": "Payment Reconciliation Payment",

View File

@@ -42,9 +42,6 @@ frappe.ui.form.on("Payment Tool", "received_or_paid", function(frm) {
erpnext.payment_tool.check_mandatory_to_set_button(frm); erpnext.payment_tool.check_mandatory_to_set_button(frm);
}); });
// Fetch bank/cash account based on payment mode
cur_frm.add_fetch("payment_mode", "default_account", "payment_account");
// Set party account name // Set party account name
frappe.ui.form.on("Payment Tool", "customer", function(frm) { frappe.ui.form.on("Payment Tool", "customer", function(frm) {
erpnext.payment_tool.set_party_account(frm); erpnext.payment_tool.set_party_account(frm);

View File

@@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _, scrub from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.document import Document from frappe.model.document import Document
import json import json
@@ -91,8 +91,9 @@ def get_orders_to_be_billed(party_type, party_name):
where where
%s = %s %s = %s
and docstatus = 1 and docstatus = 1
and ifnull(status, "") != "Stopped"
and ifnull(grand_total, 0) > ifnull(advance_paid, 0) and ifnull(grand_total, 0) > ifnull(advance_paid, 0)
and ifnull(per_billed, 0) < 100.0 and abs(100 - ifnull(per_billed, 0)) > 0.01
""" % (voucher_type, 'customer' if party_type == "Customer" else 'supplier', '%s'), """ % (voucher_type, 'customer' if party_type == "Customer" else 'supplier', '%s'),
party_name, as_dict = True) party_name, as_dict = True)

View File

@@ -33,7 +33,8 @@ class TestPaymentTool(unittest.TestCase):
jv_against_so1 = self.create_against_jv(jv_test_records[0], { jv_against_so1 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC", "account": "_Test Customer 3 - _TC",
"against_sales_order": so1.name "against_sales_order": so1.name,
"is_advance": "Yes"
}) })
@@ -45,7 +46,8 @@ class TestPaymentTool(unittest.TestCase):
jv_against_so2 = self.create_against_jv(jv_test_records[0], { jv_against_so2 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC", "account": "_Test Customer 3 - _TC",
"against_sales_order": so2.name, "against_sales_order": so2.name,
"credit": 1000 "credit": 1000,
"is_advance": "Yes"
}) })
po = self.create_voucher(po_test_records[1], { po = self.create_voucher(po_test_records[1], {
"supplier": "_Test Supplier 1" "supplier": "_Test Supplier 1"

View File

@@ -142,24 +142,6 @@
"reqd": 0, "reqd": 0,
"search_index": 1 "search_index": 1
}, },
{
"allow_on_submit": 1,
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0
},
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
@@ -810,6 +792,28 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -821,17 +825,6 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -850,6 +843,17 @@
"print_hide": 1, "print_hide": 1,
"width": "50%" "width": "50%"
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices. It is generated on submit.", "description": "The unique id for tracking all recurring invoices. It is generated on submit.",
@@ -876,7 +880,7 @@
"icon": "icon-file-text", "icon": "icon-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-09-18 03:12:51.994059", "modified": "2014-11-27 17:28:20.133701",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -233,6 +233,11 @@ erpnext.POS = Class.extend({
}, },
make_item_list: function() { make_item_list: function() {
var me = this; var me = this;
if(!this.price_list) {
msgprint(__("Price List not found or disabled"));
return;
}
me.item_timeout = null; me.item_timeout = null;
frappe.call({ frappe.call({
method: 'erpnext.accounts.doctype.sales_invoice.pos.get_items', method: 'erpnext.accounts.doctype.sales_invoice.pos.get_items',

View File

@@ -169,28 +169,25 @@
"search_index": 0 "search_index": 0
}, },
{ {
"allow_on_submit": 1, "fieldname": "shipping_address_name",
"depends_on": "", "fieldtype": "Link",
"description": "Start date of current invoice's period", "hidden": 1,
"fieldname": "from_date", "in_filter": 1,
"fieldtype": "Date", "label": "Shipping Address Name",
"label": "From Date", "options": "Address",
"no_copy": 1,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "precision": "",
"read_only": 0 "print_hide": 1
}, },
{ {
"allow_on_submit": 1, "fieldname": "shipping_address",
"depends_on": "", "fieldtype": "Small Text",
"description": "End date of current invoice's period", "hidden": 1,
"fieldname": "to_date", "label": "Shipping Address",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "precision": "",
"read_only": 0 "print_hide": 1,
"read_only": 1
}, },
{ {
"fieldname": "currency_section", "fieldname": "currency_section",
@@ -1108,6 +1105,30 @@
"print_hide": 1, "print_hide": 1,
"read_only": 0 "read_only": 0
}, },
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"read_only": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"read_only": 0
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -1120,17 +1141,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 0 "read_only": 0
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.\n",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -1152,6 +1162,17 @@
"read_only": 0, "read_only": 0,
"width": "50%" "width": "50%"
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.\n",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices.\u00a0It is generated on submit.", "description": "The unique id for tracking all recurring invoices.\u00a0It is generated on submit.",
@@ -1192,7 +1213,7 @@
"icon": "icon-file-text", "icon": "icon-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-09-18 03:17:54.976732", "modified": "2014-10-10 16:54:22.284284",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -97,8 +97,7 @@ def validate_account_for_auto_accounting_for_stock(gl_map):
for entry in gl_map: for entry in gl_map:
if entry.account in aii_accounts: if entry.account in aii_accounts:
frappe.throw(_("Account: {0} can only be updated via \ frappe.throw(_("Account: {0} can only be updated via Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,

View File

@@ -4,9 +4,9 @@
"doc_type": "Journal Voucher", "doc_type": "Journal Voucher",
"docstatus": 0, "docstatus": 0,
"doctype": "Print Format", "doctype": "Print Format",
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + doc.total_amount + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n", "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + frappe.utils.cstr(doc.total_amount) + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n",
"idx": 2, "idx": 2,
"modified": "2014-08-29 13:20:15.789533", "modified": "2014-10-17 17:20:02.740340",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Credit Note", "name": "Credit Note",

View File

@@ -3,9 +3,9 @@
"doc_type": "Journal Voucher", "doc_type": "Journal Voucher",
"docstatus": 0, "docstatus": 0,
"doctype": "Print Format", "doctype": "Print Format",
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Payment Receipt Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Received On\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Received From\"), doc.pay_to_recd_from),\n (_(\"Amount\"), \"<strong>\" + doc.total_amount + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n", "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Payment Receipt Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Received On\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Received From\"), doc.pay_to_recd_from),\n (_(\"Amount\"), \"<strong>\" + doc.total_amount or 0 + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n",
"idx": 1, "idx": 1,
"modified": "2014-08-29 15:55:34.248384", "modified": "2014-11-04 11:25:57.560873",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Receipt Voucher", "name": "Payment Receipt Voucher",

View File

@@ -30,7 +30,8 @@ def execute(filters=None):
data = [] data = []
for gle in entries: for gle in entries:
if cstr(gle.against_voucher) == gle.voucher_no or not gle.against_voucher \ if cstr(gle.against_voucher) == gle.voucher_no or not gle.against_voucher \
or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date: or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date \
or (gle.against_voucher_type == "Purchase Order"):
voucher_details = voucher_detail_map.get(gle.voucher_type, {}).get(gle.voucher_no, {}) voucher_details = voucher_detail_map.get(gle.voucher_type, {}).get(gle.voucher_no, {})
invoiced_amount = gle.credit > 0 and gle.credit or 0 invoiced_amount = gle.credit > 0 and gle.credit or 0

View File

@@ -21,7 +21,7 @@ class AccountsReceivableReport(object):
def get_columns(self, customer_naming_by): def get_columns(self, customer_naming_by):
columns = [ columns = [
_("Posting Date") + ":Date:80", _("Account") + ":Link/Account:150", _("Posting Date") + ":Date:80", _("Account") + ":Link/Account:150",
_("Voucher Type") + "::110", _("Voucher No") + "::120", "::30", _("Voucher Type") + "::110", _("Voucher No") + ":Dynamic Link/Voucher Type:120",
_("Due Date") + ":Date:80", _("Due Date") + ":Date:80",
_("Invoiced Amount") + ":Currency:100", _("Payment Received") + ":Currency:100", _("Invoiced Amount") + ":Currency:100", _("Payment Received") + ":Currency:100",
_("Outstanding Amount") + ":Currency:100", _("Age") + ":Int:50", "0-30:Currency:100", _("Outstanding Amount") + ":Currency:100", _("Age") + ":Int:50", "0-30:Currency:100",
@@ -63,11 +63,6 @@ class AccountsReceivableReport(object):
row += [self.get_territory(gle.account), gle.remarks] row += [self.get_territory(gle.account), gle.remarks]
data.append(row) data.append(row)
for i in range(0, len(data)):
data[i].insert(4, """<a href="%s"><i class="icon icon-share" style="cursor: pointer;"></i></a>""" \
% ("/".join(["#Form", data[i][2], data[i][3]]),))
return data return data
def get_entries_after(self, report_date): def get_entries_after(self, report_date):
@@ -85,6 +80,9 @@ class AccountsReceivableReport(object):
# advance # advance
(not gle.against_voucher) or (not gle.against_voucher) or
# against sales order
(gle.against_voucher_type == "Sales Order") or
# sales invoice # sales invoice
(gle.against_voucher==gle.voucher_no and gle.debit > 0) or (gle.against_voucher==gle.voucher_no and gle.debit > 0) or
@@ -151,7 +149,7 @@ class AccountsReceivableReport(object):
if not account_map: if not account_map:
frappe.throw(_("No Customer Accounts found.")) frappe.throw(_("No Customer Accounts found."))
else: else:
accounts_list = ['"{0}"'.format(ac) for ac in account_map] accounts_list = ['"{0}"'.format(ac.replace('"', '\"')) for ac in account_map]
conditions.append("account in ({0})".format(", ".join(accounts_list))) conditions.append("account in ({0})".format(", ".join(accounts_list)))
return " and ".join(conditions), values return " and ".join(conditions), values

View File

@@ -35,7 +35,7 @@ def validate_filters(filters, account_details):
def get_columns(): def get_columns():
return [_("Posting Date") + ":Date:100", _("Account") + ":Link/Account:200", _("Debit") + ":Float:100", return [_("Posting Date") + ":Date:100", _("Account") + ":Link/Account:200", _("Debit") + ":Float:100",
_("Credit") + ":Float:100", _("Voucher Type") + "::120", _("Voucher No") + "::160", _("Link") + "::20", _("Credit") + ":Float:100", _("Voucher Type") + "::120", _("Voucher No") + ":Dynamic Link/Voucher Type:160",
_("Against Account") + "::120", _("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"] _("Against Account") + "::120", _("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"]
def get_result(filters, account_details): def get_result(filters, account_details):
@@ -162,15 +162,6 @@ def get_result_as_list(data):
for d in data: for d in data:
result.append([d.get("posting_date"), d.get("account"), d.get("debit"), result.append([d.get("posting_date"), d.get("account"), d.get("debit"),
d.get("credit"), d.get("voucher_type"), d.get("voucher_no"), d.get("credit"), d.get("voucher_type"), d.get("voucher_no"),
get_voucher_link(d.get("voucher_type"), d.get("voucher_no")),
d.get("against"), d.get("cost_center"), d.get("remarks")]) d.get("against"), d.get("cost_center"), d.get("remarks")])
return result return result
def get_voucher_link(voucher_type, voucher_no):
icon = ""
if voucher_type and voucher_no:
icon = """<a href="%s"><i class="icon icon-share" style="cursor: pointer;">
</i></a>""" % ("/".join(["#Form", voucher_type, voucher_no]))
return icon

View File

@@ -210,14 +210,15 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
calculate_totals: function() { calculate_totals: function() {
var tax_count = this.frm.tax_doclist.length; var tax_count = this.frm.tax_doclist.length;
this.frm.doc.grand_total = flt(tax_count ? this.frm.doc.grand_total = flt(tax_count ?
this.frm.tax_doclist[tax_count - 1].total : this.frm.doc.net_total, this.frm.tax_doclist[tax_count - 1].total : this.frm.doc.net_total);
precision("grand_total")); this.frm.doc.grand_total_import = flt(this.frm.doc.grand_total / this.frm.doc.conversion_rate);
this.frm.doc.grand_total_import = flt(this.frm.doc.grand_total /
this.frm.doc.conversion_rate, precision("grand_total_import"));
this.frm.doc.total_tax = flt(this.frm.doc.grand_total - this.frm.doc.net_total, this.frm.doc.total_tax = flt(this.frm.doc.grand_total - this.frm.doc.net_total,
precision("total_tax")); precision("total_tax"));
this.frm.doc.grand_total = flt(this.frm.doc.grand_total, precision("grand_total"));
this.frm.doc.grand_total_import = flt(this.frm.doc.grand_total_import, precision("grand_total_import"));
// rounded totals // rounded totals
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) { if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
this.frm.doc.rounded_total = Math.round(this.frm.doc.grand_total); this.frm.doc.rounded_total = Math.round(this.frm.doc.grand_total);

View File

@@ -3,15 +3,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt
from frappe.utils import cstr, flt
from frappe import _ from frappe import _
from erpnext.stock.doctype.item.item import get_last_purchase_details from erpnext.stock.doctype.item.item import get_last_purchase_details
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
class PurchaseCommon(BuyingController): class PurchaseCommon(BuyingController):
def update_last_purchase_rate(self, obj, is_submit): def update_last_purchase_rate(self, obj, is_submit):
"""updates last_purchase_rate in item table for each item""" """updates last_purchase_rate in item table for each item"""
@@ -123,27 +121,6 @@ class PurchaseCommon(BuyingController):
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name):
# Get total Quantities of current doctype (eg. PR) except for qty of this transaction
#------------------------------
# please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same .
# i.e. in PO uom is NOS then in PR uom should be NOS
# but if in Material Request uom KG it can change in PO
get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty'
qty = frappe.db.sql("""select sum(%s) from `tab%s` where %s = %s and
docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'),
(ref_tab_dn, curr_parent_name))
qty = qty and flt(qty[0][0]) or 0
# get total qty of ref doctype
#--------------------
max_qty = frappe.db.sql("""select qty from `tab%s` where name = %s
and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn)
max_qty = max_qty and flt(max_qty[0][0]) or 0
return cstr(qty)+'~~~'+cstr(max_qty)
def check_for_stopped_status(self, doctype, docname): def check_for_stopped_status(self, doctype, docname):
stopped = frappe.db.sql("""select name from `tab%s` where name = %s and stopped = frappe.db.sql("""select name from `tab%s` where name = %s and
status = 'Stopped'""" % (doctype, '%s'), docname) status = 'Stopped'""" % (doctype, '%s'), docname)

View File

@@ -101,24 +101,6 @@
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1
}, },
{
"allow_on_submit": 1,
"description": "Start date of current order's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"description": "End date of current order's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0
},
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
@@ -703,7 +685,30 @@
"label": "Recurring Type", "label": "Recurring Type",
"no_copy": 1, "no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly", "options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0 "permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current order's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current order's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@@ -716,17 +721,6 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -745,6 +739,17 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
"fieldname": "recurring_id", "fieldname": "recurring_id",
@@ -770,7 +775,7 @@
"icon": "icon-file-text", "icon": "icon-file-text",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-09-18 03:16:06.299317", "modified": "2014-11-27 17:27:38.839440",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@@ -96,50 +96,45 @@ class PurchaseOrder(BuyingController):
check_list.append(d.prevdoc_docname) check_list.append(d.prevdoc_docname)
pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname) pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname)
def update_requested_qty(self):
material_request_map = {}
for d in self.get("po_details"):
if d.prevdoc_doctype and d.prevdoc_doctype == "Material Request" and d.prevdoc_detail_docname:
material_request_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname)
def update_bin(self, is_submit, is_stopped = 0): for mr, mr_item_rows in material_request_map.items():
from erpnext.stock.utils import update_bin if mr and mr_item_rows:
pc_obj = frappe.get_doc('Purchase Common') mr_obj = frappe.get_doc("Material Request", mr)
for d in self.get('po_details'):
#1. Check if is_stock_item == 'Yes'
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes":
# this happens when item is changed from non-stock to stock item
if not d.warehouse:
continue
ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor) if mr_obj.status in ["Stopped", "Cancelled"]:
if is_stopped: frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError)
po_qty = flt(d.qty) > flt(d.received_qty) and \
flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0
# No updates in Material Request on Stop / Unstop mr_obj.update_requested_qty(mr_item_rows)
if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped:
# get qty and pending_qty of prevdoc
curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname',
d.prevdoc_detail_docname, 'Material Request Item',
'Material Request - Purchase Order', self.name)
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
flt(curr_ref_qty.split('~~~')[0]), 0
if flt(qty) + flt(po_qty) > flt(max_qty): def update_ordered_qty(self, po_item_rows=None):
curr_qty = flt(max_qty) - flt(qty) """update requested qty (before ordered_qty is updated)"""
# special case as there is no restriction from erpnext.stock.utils import get_bin
# for Material Request - Purchase Order
curr_qty = curr_qty > 0 and curr_qty or 0
else:
curr_qty = flt(po_qty)
ind_qty = -flt(curr_qty) def _update_ordered_qty(item_code, warehouse):
ordered_qty = frappe.db.sql("""
select sum((po_item.qty - ifnull(po_item.received_qty, 0))*po_item.conversion_factor)
from `tabPurchase Order Item` po_item, `tabPurchase Order` po
where po_item.item_code=%s and po_item.warehouse=%s
and po_item.qty > ifnull(po_item.received_qty, 0) and po_item.parent=po.name
and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse))
# Update ordered_qty and indented_qty in bin bin_doc = get_bin(item_code, warehouse)
args = { bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0
"item_code": d.item_code, bin_doc.save()
"warehouse": d.warehouse,
"ordered_qty": (is_submit and 1 or -1) * flt(po_qty), item_wh_list = []
"indented_qty": (is_submit and 1 or -1) * flt(ind_qty), for d in self.get("po_details"):
"posting_date": self.transaction_date if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
} and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
update_bin(args) item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
_update_ordered_qty(item_code, warehouse)
def check_modified_date(self): def check_modified_date(self):
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
@@ -152,13 +147,11 @@ class PurchaseOrder(BuyingController):
def update_status(self, status): def update_status(self, status):
self.check_modified_date() self.check_modified_date()
# step 1:=> Set Status
frappe.db.set(self,'status',cstr(status)) frappe.db.set(self,'status',cstr(status))
# step 2:=> Update Bin self.update_requested_qty()
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) self.update_ordered_qty()
# step 3:=> Acknowledge user
msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status)) msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status))
def on_submit(self): def on_submit(self):
@@ -167,7 +160,8 @@ class PurchaseOrder(BuyingController):
purchase_controller = frappe.get_doc("Purchase Common") purchase_controller = frappe.get_doc("Purchase Common")
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_bin(is_submit = 1, is_stopped = 0) self.update_requested_qty()
self.update_ordered_qty()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.grand_total) self.company, self.grand_total)
@@ -192,8 +186,13 @@ class PurchaseOrder(BuyingController):
throw(_("Purchase Invoice {0} is already submitted").format(", ".join(submitted))) throw(_("Purchase Invoice {0} is already submitted").format(", ".join(submitted)))
frappe.db.set(self,'status','Cancelled') frappe.db.set(self,'status','Cancelled')
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_bin( is_submit = 0, is_stopped = 0)
# Must be called after updating ordered qty in Material Request
self.update_requested_qty()
self.update_ordered_qty()
pc_obj.update_last_purchase_rate(self, is_submit = 0) pc_obj.update_last_purchase_rate(self, is_submit = 0)
def on_update(self): def on_update(self):
@@ -248,7 +247,7 @@ def make_purchase_invoice(source_name, target_doc=None):
def update_item(obj, target, source_parent): def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt) target.amount = flt(obj.amount) - flt(obj.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate) target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = target.amount / flt(obj.rate) if flt(obj.rate) else flt(obj.qty) target.qty = target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty)
doc = get_mapped_doc("Purchase Order", source_name, { doc = get_mapped_doc("Purchase Order", source_name, {
"Purchase Order": { "Purchase Order": {

View File

@@ -29,8 +29,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.get_doc(pr).insert() frappe.get_doc(pr).insert()
def test_ordered_qty(self): def test_ordered_qty(self):
frappe.db.sql("delete from tabBin") existing_ordered_qty = self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC")
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = frappe.copy_doc(test_records[0]).insert() po = frappe.copy_doc(test_records[0]).insert()
@@ -43,8 +42,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.get("po_details")[0].item_code = "_Test Item" po.get("po_details")[0].item_code = "_Test Item"
po.submit() po.submit()
self.assertEquals(frappe.db.get_value("Bin", {"item_code": "_Test Item", self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 10)
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10)
pr = make_purchase_receipt(po.name) pr = make_purchase_receipt(po.name)
@@ -56,8 +54,9 @@ class TestPurchaseOrder(unittest.TestCase):
pr.insert() pr.insert()
pr.submit() pr.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", po.load_from_db()
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0) self.assertEquals(po.get("po_details")[0].received_qty, 4)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6)
frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) frappe.db.set_value('Item', '_Test Item', 'tolerance', 50)
@@ -68,8 +67,16 @@ class TestPurchaseOrder(unittest.TestCase):
pr1.insert() pr1.insert()
pr1.submit() pr1.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", po.load_from_db()
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0) self.assertEquals(po.get("po_details")[0].received_qty, 12)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty)
pr1.load_from_db()
pr1.cancel()
po.load_from_db()
self.assertEquals(po.get("po_details")[0].received_qty, 4)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
@@ -111,7 +118,10 @@ class TestPurchaseOrder(unittest.TestCase):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document from erpnext.controllers.tests.test_recurring_document import test_recurring_document
test_recurring_document(self, test_records) test_recurring_document(self, test_records)
def _get_ordered_qty(self, item_code, warehouse):
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "ordered_qty"))
test_dependencies = ["BOM"]
test_dependencies = ["BOM", "Item Price"]
test_records = frappe.get_test_records('Purchase Order') test_records = frappe.get_test_records('Purchase Order')

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
{% include 'setup/doctype/contact_control/contact_control.js' %};
cur_frm.cscript.refresh = function(doc, dt, dn) { cur_frm.cscript.refresh = function(doc, dt, dn) {
cur_frm.cscript.make_dashboard(doc); cur_frm.cscript.make_dashboard(doc);
@@ -17,15 +15,13 @@ cur_frm.cscript.refresh = function(doc, dt, dn) {
} }
else{ else{
unhide_field(['address_html','contact_html']); unhide_field(['address_html','contact_html']);
// make lists
cur_frm.cscript.make_address(doc,dt,dn); erpnext.utils.render_address_and_contact(cur_frm)
cur_frm.cscript.make_contact(doc,dt,dn);
cur_frm.communication_view = new frappe.views.CommunicationList({ cur_frm.communication_view = new frappe.views.CommunicationList({
list: frappe.get_list("Communication", {"supplier": doc.name}),
parent: cur_frm.fields_dict.communication_html.wrapper, parent: cur_frm.fields_dict.communication_html.wrapper,
doc: doc doc: doc
}) });
} }
} }
@@ -61,45 +57,6 @@ cur_frm.cscript.make_dashboard = function(doc) {
}) })
} }
cur_frm.cscript.make_address = function() {
if(!cur_frm.address_list) {
cur_frm.address_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['address_html'].wrapper,
page_length: 5,
new_doctype: "Address",
get_query: function() {
return "select name, address_type, address_line1, address_line2, city, state, country, pincode, fax, email_id, phone, is_primary_address, is_shipping_address from tabAddress where supplier='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_address desc"
},
as_dict: 1,
no_results_message: __('No addresses created'),
render_row: cur_frm.cscript.render_address_row,
});
// note: render_address_row is defined in contact_control.js
}
cur_frm.address_list.run();
}
cur_frm.cscript.make_contact = function() {
if(!cur_frm.contact_list) {
cur_frm.contact_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['contact_html'].wrapper,
page_length: 5,
new_doctype: "Contact",
get_query: function() {
return "select name, first_name, last_name, email_id, phone, mobile_no, department, designation, is_primary_contact from tabContact where supplier='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_contact desc"
},
as_dict: 1,
no_results_message: __('No contacts created'),
render_row: cur_frm.cscript.render_contact_row,
});
// note: render_contact_row is defined in contact_control.js
}
cur_frm.contact_list.run();
}
cur_frm.fields_dict['default_price_list'].get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['default_price_list'].get_query = function(doc, cdt, cdn) {
return{ return{
filters:{'buying': 1} filters:{'buying': 1}

View File

@@ -9,10 +9,14 @@ from frappe.utils import cint
from frappe import msgprint, _ from frappe import msgprint, _
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from erpnext.accounts.party import create_party_account from erpnext.accounts.party import create_party_account
from erpnext.utilities.address_and_contact import load_address_and_contact
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
class Supplier(TransactionBase): class Supplier(TransactionBase):
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self, "supplier")
def autoname(self): def autoname(self):
supp_master_name = frappe.defaults.get_global_default('supp_master_name') supp_master_name = frappe.defaults.get_global_default('supp_master_name')

View File

@@ -142,10 +142,10 @@ def get_data():
"doctype": "Item", "doctype": "Item",
}, },
{ {
"type": "page", "type": "report",
"name": "stock-balance", "is_query_report": True,
"label": _("Stock Balance"), "name": "Stock Balance",
"icon": "icon-table", "doctype": "Warehouse"
}, },
{ {
"type": "report", "type": "report",
@@ -170,13 +170,7 @@ def get_data():
"name": "stock-analytics", "name": "stock-analytics",
"label": _("Stock Analytics"), "label": _("Stock Analytics"),
"icon": "icon-bar-chart" "icon": "icon-bar-chart"
}, }
{
"type": "report",
"is_query_report": True,
"name": "Warehouse-Wise Stock Balance",
"doctype": "Warehouse"
},
] ]
}, },
{ {

View File

@@ -387,7 +387,7 @@ class AccountsController(TransactionBase):
res = frappe.db.sql(""" res = frappe.db.sql("""
select select
t1.name as jv_no, t1.remark, t2.%s as amount, t2.name as jv_detail_no t1.name as jv_no, t1.remark, t2.%s as amount, t2.name as jv_detail_no, `against_%s` as against_order
from from
`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2 `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
where where
@@ -400,7 +400,7 @@ class AccountsController(TransactionBase):
and ifnull(t2.against_purchase_order, '') = '' and ifnull(t2.against_purchase_order, '') = ''
) %s) ) %s)
order by t1.posting_date""" % order by t1.posting_date""" %
(dr_or_cr, '%s', cond), (dr_or_cr, against_order_field, '%s', cond),
tuple([account_head] + so_list), as_dict= True) tuple([account_head] + so_list), as_dict= True)
self.set(parentfield, []) self.set(parentfield, [])
@@ -411,7 +411,7 @@ class AccountsController(TransactionBase):
"jv_detail_no": d.jv_detail_no, "jv_detail_no": d.jv_detail_no,
"remarks": d.remark, "remarks": d.remark,
"advance_amount": flt(d.amount), "advance_amount": flt(d.amount),
"allocate_amount": 0 "allocated_amount": flt(d.amount) if d.against_order else 0
}) })
def validate_advance_jv(self, advance_table_fieldname, against_order_field): def validate_advance_jv(self, advance_table_fieldname, against_order_field):
@@ -437,7 +437,7 @@ class AccountsController(TransactionBase):
for order, jv_list in order_jv_map.items(): for order, jv_list in order_jv_map.items():
for jv in jv_list: for jv in jv_list:
if not advance_jv_against_si or jv not in advance_jv_against_si: if not advance_jv_against_si or jv not in advance_jv_against_si:
frappe.throw(_("Journal Voucher {0} is linked against Order {1}, hence it must be fetched as advance in Invoice as well.") frappe.msgprint(_("Journal Voucher {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.")
.format(jv, order)) .format(jv, order))

View File

@@ -110,13 +110,13 @@ class BuyingController(StockController):
self.round_floats_in(self, ["net_total", "net_total_import"]) self.round_floats_in(self, ["net_total", "net_total_import"])
def calculate_totals(self): def calculate_totals(self):
self.grand_total = flt(self.tax_doclist[-1].total if self.tax_doclist self.grand_total = flt(self.tax_doclist[-1].total if self.tax_doclist else self.net_total)
else self.net_total, self.precision("grand_total")) self.grand_total_import = flt(self.grand_total / self.conversion_rate)
self.grand_total_import = flt(self.grand_total / self.conversion_rate,
self.precision("grand_total_import"))
self.total_tax = flt(self.grand_total - self.net_total, self.total_tax = flt(self.grand_total - self.net_total, self.precision("total_tax"))
self.precision("total_tax"))
self.grand_total = flt(self.grand_total, self.precision("grand_total"))
self.grand_total_import = flt(self.grand_total_import, self.precision("grand_total_import"))
if self.meta.get_field("rounded_total"): if self.meta.get_field("rounded_total"):
self.rounded_total = rounded(self.grand_total) self.rounded_total = rounded(self.grand_total)
@@ -256,8 +256,6 @@ class BuyingController(StockController):
rm.required_qty = required_qty rm.required_qty = required_qty
rm.conversion_factor = item.conversion_factor rm.conversion_factor = item.conversion_factor
rm.rate = bom_item.rate
rm.amount = required_qty * flt(bom_item.rate)
rm.idx = rm_supplied_idx rm.idx = rm_supplied_idx
if self.doctype == "Purchase Receipt": if self.doctype == "Purchase Receipt":
@@ -268,7 +266,25 @@ class BuyingController(StockController):
rm_supplied_idx += 1 rm_supplied_idx += 1
raw_materials_cost += required_qty * flt(bom_item.rate) # get raw materials rate
if self.doctype == "Purchase Receipt":
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": bom_item.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * required_qty,
"serial_no": rm.serial_no
})
if not rm.rate:
from erpnext.stock.stock_ledger import get_valuation_rate
rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
else:
rm.rate = bom_item.rate
rm.amount = required_qty * flt(rm.rate)
raw_materials_cost += flt(rm.amount)
if self.doctype == "Purchase Receipt": if self.doctype == "Purchase Receipt":
item.rm_supp_cost = raw_materials_cost item.rm_supp_cost = raw_materials_cost

View File

@@ -216,17 +216,17 @@ class SellingController(StockController):
self.round_floats_in(self, ["net_total", "net_total_export"]) self.round_floats_in(self, ["net_total", "net_total_export"])
def calculate_totals(self): def calculate_totals(self):
self.grand_total = flt(self.tax_doclist and \ self.grand_total = flt(self.tax_doclist[-1].total if self.tax_doclist else self.net_total)
self.tax_doclist[-1].total or self.net_total, self.precision("grand_total"))
self.grand_total_export = flt(self.grand_total / self.conversion_rate,
self.precision("grand_total_export"))
self.other_charges_total = flt(self.grand_total - self.net_total, self.grand_total_export = flt(self.grand_total / self.conversion_rate)
self.precision("other_charges_total"))
self.other_charges_total_export = flt(self.grand_total_export - self.other_charges_total = flt(self.grand_total - self.net_total, self.precision("other_charges_total"))
self.net_total_export + flt(self.discount_amount),
self.precision("other_charges_total_export")) self.other_charges_total_export = flt(self.grand_total_export - self.net_total_export +
flt(self.discount_amount), self.precision("other_charges_total_export"))
self.grand_total = flt(self.grand_total, self.precision("grand_total"))
self.grand_total_export = flt(self.grand_total_export, self.precision("grand_total_export"))
self.rounded_total = rounded(self.grand_total) self.rounded_total = rounded(self.grand_total)
self.rounded_total_export = rounded(self.grand_total_export) self.rounded_total_export = rounded(self.grand_total_export)

View File

@@ -8,7 +8,7 @@ from frappe import msgprint, _
import frappe.defaults import frappe.defaults
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
class StockController(AccountsController): class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True): def make_gl_entries(self, repost_future_gle=True):
@@ -24,11 +24,12 @@ class StockController(AccountsController):
if repost_future_gle: if repost_future_gle:
items, warehouses = self.get_items_and_warehouses() items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items, warehouse_account) update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):
from erpnext.accounts.general_ledger import process_gl_map
if not warehouse_account: if not warehouse_account:
warehouse_account = get_warehouse_account() warehouse_account = get_warehouse_account()
@@ -118,7 +119,8 @@ class StockController(AccountsController):
def get_stock_ledger_details(self): def get_stock_ledger_details(self):
stock_ledger = {} stock_ledger = {}
for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no for sle in frappe.db.sql("""select warehouse, stock_value_difference,
voucher_detail_no, item_code, posting_date, actual_qty
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
(self.doctype, self.name), as_dict=True): (self.doctype, self.name), as_dict=True):
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle) stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
@@ -173,7 +175,7 @@ class StockController(AccountsController):
_(self.doctype), self.name, item.get("item_code"))) _(self.doctype), self.name, item.get("item_code")))
def get_sl_entries(self, d, args): def get_sl_entries(self, d, args):
sl_dict = { sl_dict = frappe._dict({
"item_code": d.get("item_code", None), "item_code": d.get("item_code", None),
"warehouse": d.get("warehouse", None), "warehouse": d.get("warehouse", None),
"posting_date": self.posting_date, "posting_date": self.posting_date,
@@ -190,7 +192,7 @@ class StockController(AccountsController):
"serial_no": d.get("serial_no"), "serial_no": d.get("serial_no"),
"project": d.get("project_name"), "project": d.get("project_name"),
"is_cancelled": self.docstatus==2 and "Yes" or "No" "is_cancelled": self.docstatus==2 and "Yes" or "No"
} })
sl_dict.update(args) sl_dict.update(args)
return sl_dict return sl_dict
@@ -214,7 +216,8 @@ class StockController(AccountsController):
return serialized_items return serialized_items
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None): def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None):
def _delete_gl_entries(voucher_type, voucher_no): def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry` frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))

View File

@@ -4,7 +4,7 @@ app_publisher = "Web Notes Technologies Pvt. Ltd. and Contributors"
app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations" app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations"
app_icon = "icon-th" app_icon = "icon-th"
app_color = "#e74c3c" app_color = "#e74c3c"
app_version = "4.5.1" app_version = "4.13.0"
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"
@@ -47,8 +47,8 @@ doc_events = {
"on_update": "erpnext.home.make_comment_feed" "on_update": "erpnext.home.make_comment_feed"
}, },
"Stock Entry": { "Stock Entry": {
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_qty", "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_qty" "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty"
}, },
"User": { "User": {
"validate": "erpnext.hr.doctype.employee.employee.validate_employee_role", "validate": "erpnext.hr.doctype.employee.employee.validate_employee_role",

View File

@@ -1,9 +1,21 @@
{ {
"autoname": "EXP.######", "autoname": "naming_series:",
"creation": "2013-01-10 16:34:14", "creation": "2013-01-10 16:34:14",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
{
"default": "EXP",
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"no_copy": 1,
"options": "EXP",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"reqd": 1
},
{ {
"default": "Draft", "default": "Draft",
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
@@ -190,7 +202,7 @@
"icon": "icon-money", "icon": "icon-money",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-08-27 07:08:48.454580", "modified": "2014-11-24 18:25:53.038826",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim", "name": "Expense Claim",

View File

@@ -104,9 +104,32 @@ cur_frm.cscript.calculate_total_days = function(doc, dt, dn) {
if(cint(doc.half_day) == 1) set_multiple(dt,dn,{total_leave_days:0.5}); if(cint(doc.half_day) == 1) set_multiple(dt,dn,{total_leave_days:0.5});
else{ else{
// server call is done to include holidays in leave days calculations // server call is done to include holidays in leave days calculations
return get_server_fields('get_total_leave_days', '', '', doc, dt, dn, 1); return frappe.call({
method: 'erpnext.hr.doctype.leave_application.leave_application.get_total_leave_days',
args: {leave_app: doc},
callback: function(response) {
if (response && response.message) {
cur_frm.set_value('total_leave_days', response.message.total_leave_days);
}
}
});
} }
} }
} }
cur_frm.fields_dict.employee.get_query = erpnext.queries.employee; cur_frm.fields_dict.employee.get_query = erpnext.queries.employee;
frappe.ui.form.on("Leave Application", "leave_approver", function(frm) {
frappe.call({
"method": "frappe.client.get",
args: {
doctype: "User",
name: frm.doc.leave_approver
},
callback: function (data) {
frappe.model.set_value(frm.doctype, frm.docname, "leave_approver_name",
data.message.first_name
+ (data.message.last_name ? (" " + data.message.last_name) : ""))
}
})
})

View File

@@ -24,6 +24,13 @@
"options": "User", "options": "User",
"permlevel": 0 "permlevel": 0
}, },
{
"fieldname": "leave_approver_name",
"fieldtype": "Read Only",
"label": "Leave Approver Name",
"permlevel": 0,
"precision": ""
},
{ {
"fieldname": "leave_type", "fieldname": "leave_type",
"fieldtype": "Link", "fieldtype": "Link",
@@ -184,7 +191,7 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"max_attachments": 3, "max_attachments": 3,
"modified": "2014-09-09 05:35:31.531651", "modified": "2014-12-09 16:33:29.626849",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Application", "name": "Leave Application",

View File

@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe, json
from frappe import _ from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_url_to_form, \ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_url_to_form, \
@@ -77,26 +77,10 @@ class LeaveApplication(Document):
LeaveDayBlockedError) LeaveDayBlockedError)
def get_holidays(self): def get_holidays(self):
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2, `tabEmployee` e1 return get_holidays(self)
where e1.name = %s and h1.parent = h2.name and e1.holiday_list = h2.name
and h1.holiday_date between %s and %s""", (self.employee, self.from_date, self.to_date))
if not tot_hol:
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s
and ifnull(h2.is_default,0) = 1 and h2.fiscal_year = %s""",
(self.from_date, self.to_date, self.fiscal_year))
return tot_hol and flt(tot_hol[0][0]) or 0
def get_total_leave_days(self): def get_total_leave_days(self):
"""Calculates total leave days based on input and holidays""" return get_total_leave_days(self)
ret = {'total_leave_days' : 0.5}
if not self.half_day:
tot_days = date_diff(self.to_date, self.from_date) + 1
holidays = self.get_holidays()
ret = {
'total_leave_days' : flt(tot_days)-flt(holidays)
}
return ret
def validate_to_date(self): def validate_to_date(self):
if self.from_date and self.to_date and \ if self.from_date and self.to_date and \
@@ -216,6 +200,35 @@ class LeaveApplication(Document):
post(**{"txt": args.message, "contact": args.message_to, "subject": args.subject, post(**{"txt": args.message, "contact": args.message_to, "subject": args.subject,
"notify": cint(self.follow_via_email)}) "notify": cint(self.follow_via_email)})
def get_holidays(leave_app):
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2, `tabEmployee` e1
where e1.name = %s and h1.parent = h2.name and e1.holiday_list = h2.name
and h1.holiday_date between %s and %s""", (leave_app.employee, leave_app.from_date,
leave_app.to_date))
# below line is needed. If an employee hasn't been assigned with any holiday list then above will return 0 rows.
if not tot_hol:
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s
and ifnull(h2.is_default,0) = 1 and h2.fiscal_year = %s""",
(leave_app.from_date, leave_app.to_date, leave_app.fiscal_year))
return tot_hol and tot_hol[0][0] or 0
@frappe.whitelist()
def get_total_leave_days(leave_app):
# Parse Leave Application if neccessary
if isinstance(leave_app, str) or isinstance(leave_app, unicode):
leave_app = frappe.get_doc(json.loads(leave_app))
"""Calculates total leave days based on input and holidays"""
ret = {'total_leave_days' : 0.5}
if not leave_app.half_day:
tot_days = date_diff(leave_app.to_date, leave_app.from_date) + 1
holidays = leave_app.get_holidays()
ret = {
'total_leave_days' : flt(tot_days)-flt(holidays)
}
return ret
@frappe.whitelist() @frappe.whitelist()
def get_leave_balance(employee, leave_type, fiscal_year): def get_leave_balance(employee, leave_type, fiscal_year):
leave_all = frappe.db.sql("""select total_leaves_allocated leave_all = frappe.db.sql("""select total_leaves_allocated

View File

@@ -156,7 +156,7 @@ class SalarySlip(TransactionBase):
/ cint(self.total_days_in_month), 2) / cint(self.total_days_in_month), 2)
elif not self.payment_days: elif not self.payment_days:
d.e_modified_amount = 0 d.e_modified_amount = 0
else: elif not d.e_modified_amount:
d.e_modified_amount = d.e_amount d.e_modified_amount = d.e_amount
self.gross_pay += flt(d.e_modified_amount) self.gross_pay += flt(d.e_modified_amount)
@@ -168,7 +168,7 @@ class SalarySlip(TransactionBase):
/ cint(self.total_days_in_month), 2) / cint(self.total_days_in_month), 2)
elif not self.payment_days: elif not self.payment_days:
d.d_modified_amount = 0 d.d_modified_amount = 0
else: elif not d.d_modified_amount:
d.d_modified_amount = d.d_amount d.d_modified_amount = d.d_amount
self.total_deduction += flt(d.d_modified_amount) self.total_deduction += flt(d.d_modified_amount)

View File

@@ -115,6 +115,9 @@ class BOM(Document):
return rate return rate
def update_cost(self): def update_cost(self):
if self.docstatus == 2:
return
for d in self.get("bom_materials"): for d in self.get("bom_materials"):
d.rate = self.get_bom_material_detail({ d.rate = self.get_bom_material_detail({
'item_code': d.item_code, 'item_code': d.item_code,
@@ -122,8 +125,9 @@ class BOM(Document):
'qty': d.qty 'qty': d.qty
})["rate"] })["rate"]
if self.docstatus in (0, 1): if self.docstatus == 1:
self.ignore_validate_update_after_submit = True self.ignore_validate_update_after_submit = True
self.calculate_cost()
self.save() self.save()
def get_bom_unitcost(self, bom_no): def get_bom_unitcost(self, bom_no):
@@ -269,29 +273,27 @@ class BOM(Document):
"""Calculate bom totals""" """Calculate bom totals"""
self.calculate_op_cost() self.calculate_op_cost()
self.calculate_rm_cost() self.calculate_rm_cost()
self.calculate_fixed_cost()
self.total_variable_cost = self.raw_material_cost + self.operating_cost self.total_variable_cost = self.raw_material_cost + self.operating_cost
self.total_cost = self.total_variable_cost + self.total_fixed_cost
def calculate_op_cost(self): def calculate_op_cost(self):
"""Update workstation rate and calculates totals""" """Update workstation rate and calculates totals"""
total_op_cost = 0 total_op_cost, fixed_cost = 0, 0
for d in self.get('bom_operations'): for d in self.get('bom_operations'):
if d.workstation and not d.hour_rate: if d.workstation:
d.hour_rate = frappe.db.get_value("Workstation", d.workstation, "hour_rate") w = frappe.db.get_value("Workstation", d.workstation, ["hour_rate", "fixed_cycle_cost"])
if not d.hour_rate:
d.hour_rate = flt(w[0])
fixed_cost += flt(w[1])
if d.hour_rate and d.time_in_mins: if d.hour_rate and d.time_in_mins:
d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
total_op_cost += flt(d.operating_cost) total_op_cost += flt(d.operating_cost)
self.operating_cost = total_op_cost self.operating_cost = total_op_cost
def calculate_fixed_cost(self):
"""Update workstation rate and calculates totals"""
fixed_cost = 0
for d in self.get('bom_operations'):
if d.workstation:
fixed_cost += flt(frappe.db.get_value("Workstation", d.workstation, "fixed_cycle_cost"))
self.total_fixed_cost = fixed_cost self.total_fixed_cost = fixed_cost
def calculate_rm_cost(self): def calculate_rm_cost(self):
"""Fetch RM rate as per today's valuation rate and calculate totals""" """Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0 total_rm_cost = 0

View File

@@ -96,7 +96,7 @@ cur_frm.cscript['Transfer Raw Materials'] = function() {
} }
cur_frm.cscript['Update Finished Goods'] = function() { cur_frm.cscript['Update Finished Goods'] = function() {
cur_frm.cscript.make_se('Manufacture/Repack'); cur_frm.cscript.make_se('Manufacture');
} }
cur_frm.fields_dict['production_item'].get_query = function(doc) { cur_frm.fields_dict['production_item'].get_query = function(doc) {

View File

@@ -110,7 +110,7 @@
}, },
{ {
"depends_on": "eval:doc.docstatus==1", "depends_on": "eval:doc.docstatus==1",
"description": "Automatically updated via Stock Entry of type Manufacture/Repack", "description": "Automatically updated via Stock Entry of type Manufacture or Repack",
"fieldname": "produced_qty", "fieldname": "produced_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Manufactured Qty", "label": "Manufactured Qty",

View File

@@ -103,7 +103,7 @@ class ProductionOrder(Document):
status = "Submitted" status = "Submitted"
if stock_entries: if stock_entries:
status = "In Process" status = "In Process"
produced_qty = stock_entries.get("Manufacture/Repack") produced_qty = stock_entries.get("Manufacture")
if flt(produced_qty) == flt(self.qty): if flt(produced_qty) == flt(self.qty):
status = "Completed" status = "Completed"
@@ -113,7 +113,7 @@ class ProductionOrder(Document):
def update_produced_qty(self): def update_produced_qty(self):
produced_qty = frappe.db.sql("""select sum(fg_completed_qty) produced_qty = frappe.db.sql("""select sum(fg_completed_qty)
from `tabStock Entry` where production_order=%s and docstatus=1 from `tabStock Entry` where production_order=%s and docstatus=1
and purpose='Manufacture/Repack'""", self.name) and purpose='Manufacture'""", self.name)
produced_qty = flt(produced_qty[0][0]) if produced_qty else 0 produced_qty = flt(produced_qty[0][0]) if produced_qty else 0
if produced_qty > self.qty: if produced_qty > self.qty:

View File

@@ -31,7 +31,7 @@ class TestProductionOrder(unittest.TestCase):
s.submit() s.submit()
# from wip to fg # from wip to fg
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture/Repack", 4)) s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 4))
s.insert() s.insert()
s.submit() s.submit()
@@ -49,7 +49,7 @@ class TestProductionOrder(unittest.TestCase):
test_stock_entry.make_stock_entry("_Test Item", None, "_Test Warehouse - _TC", 100, 100) test_stock_entry.make_stock_entry("_Test Item", None, "_Test Warehouse - _TC", 100, 100)
test_stock_entry.make_stock_entry("_Test Item Home Desktop 100", None, "_Test Warehouse - _TC", 100, 100) test_stock_entry.make_stock_entry("_Test Item Home Desktop 100", None, "_Test Warehouse - _TC", 100, 100)
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture/Repack", 7)) s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7))
s.insert() s.insert()
self.assertRaises(StockOverProductionError, s.submit) self.assertRaises(StockOverProductionError, s.submit)

View File

@@ -6,12 +6,12 @@
"doctype": "Report", "doctype": "Report",
"idx": 1, "idx": 1,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2014-06-03 07:18:17.082436", "modified": "2014-09-17 12:41:55.740299",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Issued Items Against Production Order", "name": "Issued Items Against Production Order",
"owner": "Administrator", "owner": "Administrator",
"query": "select\n ste.production_order as \"Production Order:Link/Production Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\tpro.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\"\nfrom\n\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabProduction Order` pro\nwhere\n\tifnull(ste.production_order, '') != '' and ste.name = ste_item.parent \n\tand ste.production_order = pro.name and ste.docstatus = 1 \n\tand ste.purpose = 'Manufacture/Repack'\norder by ste.posting_date, ste.production_order, ste_item.item_code", "query": "select\n ste.production_order as \"Production Order:Link/Production Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\tpro.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\"\nfrom\n\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabProduction Order` pro\nwhere\n\tifnull(ste.production_order, '') != '' and ste.name = ste_item.parent \n\tand ste.production_order = pro.name and ste.docstatus = 1 \n\tand ste.purpose = 'Manufacture' or 'Repack'\norder by ste.posting_date, ste.production_order, ste_item.item_code",
"ref_doctype": "Production Order", "ref_doctype": "Production Order",
"report_name": "Issued Items Against Production Order", "report_name": "Issued Items Against Production Order",
"report_type": "Query Report" "report_type": "Query Report"

View File

@@ -81,3 +81,11 @@ erpnext.patches.v4_2.default_website_style
erpnext.patches.v4_2.set_company_country erpnext.patches.v4_2.set_company_country
erpnext.patches.v4_2.update_sales_order_invoice_field_name erpnext.patches.v4_2.update_sales_order_invoice_field_name
erpnext.patches.v4_2.cost_of_production_cycle erpnext.patches.v4_2.cost_of_production_cycle
erpnext.patches.v4_2.seprate_manufacture_and_repack
execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance")
execute:frappe.delete_doc("DocType", "Purchase Request")
execute:frappe.delete_doc("DocType", "Purchase Request Item")
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
erpnext.patches.v4_2.update_requested_and_ordered_qty
execute:frappe.delete_doc("DocType", "Contact Control")

View File

@@ -2,6 +2,7 @@ import frappe
from frappe.templates.pages.style_settings import default_properties from frappe.templates.pages.style_settings import default_properties
def execute(): def execute():
frappe.reload_doc('website', 'doctype', 'style_settings')
style_settings = frappe.get_doc("Style Settings", "Style Settings") style_settings = frappe.get_doc("Style Settings", "Style Settings")
if not style_settings.apply_style: if not style_settings.apply_style:
style_settings.update(default_properties) style_settings.update(default_properties)

View File

@@ -3,24 +3,50 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt
def execute(): def execute():
warehouses_with_account = frappe.db.sql_list("""select master_name from tabAccount from erpnext.utilities.repost_stock import repost
where ifnull(account_type, '') = 'Warehouse'""") repost(allow_zero_rate=True, only_actual=True)
stock_vouchers_without_gle = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
where ifnull(account_type, '') = 'Warehouse'""")
if warehouse_account:
warehouses = [d[1] for d in warehouse_account]
accounts = [d[0] for d in warehouse_account]
stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where sle.warehouse in (%s) where sle.warehouse in (%s)
and not exists(select name from `tabGL Entry`
where voucher_type=sle.voucher_type and voucher_no=sle.voucher_no)
order by sle.posting_date""" % order by sle.posting_date""" %
', '.join(['%s']*len(warehouses_with_account)), tuple(warehouses_with_account)) ', '.join(['%s']*len(warehouses)), tuple(warehouses))
rejected = []
for voucher_type, voucher_no in stock_vouchers:
stock_bal = frappe.db.sql("""select sum(stock_value_difference) from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no =%s and warehouse in (%s)""" %
('%s', '%s', ', '.join(['%s']*len(warehouses))), tuple([voucher_type, voucher_no] + warehouses))
account_bal = frappe.db.sql("""select ifnull(sum(ifnull(debit, 0) - ifnull(credit, 0)), 0)
from `tabGL Entry`
where voucher_type=%s and voucher_no =%s and account in (%s)
group by voucher_type, voucher_no""" %
('%s', '%s', ', '.join(['%s']*len(accounts))), tuple([voucher_type, voucher_no] + accounts))
if stock_bal and account_bal and abs(flt(stock_bal[0][0]) - flt(account_bal[0][0])) > 0.1:
try:
print voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0]
for voucher_type, voucher_no in stock_vouchers_without_gle:
print voucher_type, voucher_no
frappe.db.sql("""delete from `tabGL Entry` frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
voucher = frappe.get_doc(voucher_type, voucher_no) voucher = frappe.get_doc(voucher_type, voucher_no)
voucher.make_gl_entries() voucher.make_gl_entries(repost_future_gle=False)
frappe.db.commit() frappe.db.commit()
except Exception, e:
print frappe.get_traceback()
rejected.append([voucher_type, voucher_no])
frappe.db.rollback()
print "Failed to repost: "
print rejected

View File

@@ -0,0 +1,16 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
for d in frappe.db.sql("select name from `tabBOM` where docstatus < 2"):
try:
document = frappe.get_doc('BOM', d[0])
if document.docstatus == 1:
document.ignore_validate_update_after_submit = True
document.calculate_cost()
document.save()
except:
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql("""update `tabStock Entry` set purpose='Manufacture' where purpose='Manufacture/Repack' and ifnull(production_order,"")!="" """)
frappe.db.sql("""update `tabStock Entry` set purpose='Repack' where purpose='Manufacture/Repack' and ifnull(production_order,"")="" """)

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty, get_ordered_qty
count=0
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from
(select item_code, warehouse from tabBin
union
select item_code, warehouse from `tabStock Ledger Entry`) a"""):
try:
count += 1
update_bin_qty(item_code, warehouse, {
"indented_qty": get_indented_qty(item_code, warehouse),
"ordered_qty": get_ordered_qty(item_code, warehouse)
})
if count % 200 == 0:
frappe.db.commit()
except:
frappe.db.rollback()

View File

@@ -17,5 +17,14 @@ class TestTimeLog(unittest.TestCase):
frappe.db.sql("delete from `tabTime Log`") frappe.db.sql("delete from `tabTime Log`")
def test_negative_hours(self):
frappe.db.sql("delete from `tabTime Log`")
test_time_log = frappe.new_doc("Time Log")
test_time_log.activity_type = "Communication"
test_time_log.from_time = "2013-01-01 11:00:00.000000"
test_time_log.to_time = "2013-01-01 10:00:00.000000"
self.assertRaises(frappe.ValidationError, test_time_log.save)
frappe.db.sql("delete from `tabTime Log`")
test_records = frappe.get_test_records('Time Log') test_records = frappe.get_test_records('Time Log')
test_ignore = ["Time Log Batch", "Sales Invoice"] test_ignore = ["Time Log Batch", "Sales Invoice"]

View File

@@ -25,14 +25,6 @@
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "hours",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Hours",
"permlevel": 0,
"read_only": 0
},
{ {
"fieldname": "to_time", "fieldname": "to_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
@@ -42,6 +34,14 @@
"read_only": 0, "read_only": 0,
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "hours",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Hours",
"permlevel": 0,
"read_only": 0
},
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
@@ -151,7 +151,7 @@
"icon": "icon-time", "icon": "icon-time",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-08-04 05:23:15.740050", "modified": "2014-10-22 16:53:26.993828",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Time Log", "name": "Time Log",

View File

@@ -23,6 +23,8 @@ class TimeLog(Document):
def calculate_total_hours(self): def calculate_total_hours(self):
from frappe.utils import time_diff_in_hours from frappe.utils import time_diff_in_hours
self.hours = time_diff_in_hours(self.to_time, self.from_time) self.hours = time_diff_in_hours(self.to_time, self.from_time)
if self.hours < 0:
frappe.throw(_("'From Time' cannot be later than 'To Time'"))
def set_status(self): def set_status(self):
self.status = { self.status = {

View File

@@ -10,6 +10,8 @@
"public/js/feature_setup.js", "public/js/feature_setup.js",
"public/js/utils.js", "public/js/utils.js",
"public/js/queries.js", "public/js/queries.js",
"public/js/utils/party.js" "public/js/utils/party.js",
"public/js/templates/address_list.html",
"public/js/templates/contact_list.html"
] ]
} }

View File

@@ -131,6 +131,7 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) { if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) {
var item = me.item_by_name[sl.item_code]; var item = me.item_by_name[sl.item_code];
if(item.closing_qty_value==undefined) item.closing_qty_value = 0;
if(me.value_or_qty!="Quantity") { if(me.value_or_qty!="Quantity") {
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code); var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
@@ -138,10 +139,21 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
item.valuation_method : sys_defaults.valuation_method; item.valuation_method : sys_defaults.valuation_method;
var is_fifo = valuation_method == "FIFO"; var is_fifo = valuation_method == "FIFO";
if(sl.voucher_type=="Stock Reconciliation") {
var diff = (sl.qty_after_transaction * sl.valuation_rate) - item.closing_qty_value;
wh.fifo_stack.push([sl.qty_after_transaction, sl.valuation_rate, sl.posting_date]);
wh.balance_qty = sl.qty_after_transaction;
wh.balance_value = sl.valuation_rate * sl.qty_after_transaction;
} else {
var diff = me.get_value_diff(wh, sl, is_fifo); var diff = me.get_value_diff(wh, sl, is_fifo);
}
} else {
if(sl.voucher_type=="Stock Reconciliation") {
var diff = sl.qty_after_transaction - item.closing_qty_value;
} else { } else {
var diff = sl.qty; var diff = sl.qty;
} }
}
if(posting_datetime < from_date) { if(posting_datetime < from_date) {
item.opening += diff; item.opening += diff;
@@ -150,6 +162,8 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
} else { } else {
break; break;
} }
item.closing_qty_value += diff;
} }
} }
}, },

View File

@@ -58,7 +58,6 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
// update balance (only needed in case of valuation) // update balance (only needed in case of valuation)
wh.balance_qty += sl.qty; wh.balance_qty += sl.qty;
wh.balance_value += value_diff; wh.balance_value += value_diff;
return value_diff; return value_diff;
}, },
get_fifo_value_diff: function(wh, sl) { get_fifo_value_diff: function(wh, sl) {
@@ -87,7 +86,6 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
qty = qty - batch[0]; qty = qty - batch[0];
} }
} }
// reset the updated stack // reset the updated stack
wh.fifo_stack = fifo_stack.reverse(); wh.fifo_stack = fifo_stack.reverse();
return -fifo_value_diff; return -fifo_value_diff;

View File

@@ -0,0 +1,20 @@
<p><button class="btn btn-sm btn-default btn-address">
<i class="icon-plus"></i> New Address</button></p>
{% for(var i=0, l=addr_list.length; i<l; i++) { %}
<hr>
<a href="#Form/Address/{%= addr_list[i].name %}" class="btn btn-sm btn-default pull-right">
{%= __("Edit") %}</a>
<p><b>{%= i+1 %}. {%= addr_list[i].address_type %}</b></p>
<div style="padding-left: 15px;">
<div>
{% if(addr_list[i].is_primary_address) { %}<span class="label label-info">
{%= __("Primary") %}</span>{% } %}
{% if(addr_list[i].is_shipping_address) { %}<span class="label label-default">
{%= __("Shipping") %}</span>{% } %}
</div>
<p style="margin-top: 5px;">{%= addr_list[i].display %}</p>
</div>
{% } %}
{% if(!addr_list.length) { %}
<p class="text-muted">{%= __("No address added yet.") %}</p>
{% } %}

View File

@@ -0,0 +1,28 @@
<p><button class="btn btn-sm btn-default btn-contact">
<i class="icon-plus"></i> New Contact</button></p>
{% for(var i=0, l=contact_list.length; i<l; i++) { %}
<hr>
<a href="#Form/Contact/{%= contact_list[i].name %}" class="btn btn-sm btn-default pull-right">
{%= __("Edit") %}</a>
<p><b>{%= i+1 %}. {%= contact_list[i].first_name %} {%= contact_list[i].last_name %}</b></p>
<div style="padding-left: 15px;">
<div>
{% if(contact_list[i].is_primary_contact) { %}<span class="label label-info">
{%= __("Primary") %}</span>{% } %}
</div>
<p style="padding-top: 5px;">
{% if(contact_list[i].phone) { %}
{%= __("Phone") %}: {%= contact_list[i].phone %}<br>
{% } %}
{% if(contact_list[i].mobile_no) { %}
{%= __("Mobile No.") %}: {%= contact_list[i].mobile_no %}<br>
{% } %}
{% if(contact_list[i].email_id) { %}
{%= __("Email ID") %}: {%= contact_list[i].email_id %}
{% } %}
</p>
</div>
{% } %}
{% if(!contact_list.length) { %}
<p class="text-muted">{%= __("No contacts added yet.") %}</p>
{% } %}

View File

@@ -81,5 +81,28 @@ $.extend(erpnext, {
d.show(); d.show();
}); });
} },
}); });
erpnext.utils = {
render_address_and_contact: function(frm) {
// render address
$(frm.fields_dict['address_html'].wrapper)
.html(frappe.render(frappe.templates.address_list,
cur_frm.doc.__onload))
.find(".btn-address").on("click", function() {
new_doc("Address");
});
// render contact
if(frm.fields_dict['contact_html']) {
$(frm.fields_dict['contact_html'].wrapper)
.html(frappe.render(frappe.templates.contact_list,
cur_frm.doc.__onload))
.find(".btn-contact").on("click", function() {
new_doc("Contact");
}
);
}
}
}

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
{% include 'setup/doctype/contact_control/contact_control.js' %};
cur_frm.cscript.onload = function(doc, dt, dn) { cur_frm.cscript.onload = function(doc, dt, dn) {
cur_frm.cscript.load_defaults(doc, dt, dn); cur_frm.cscript.load_defaults(doc, dt, dn);
} }
@@ -32,8 +30,8 @@ cur_frm.cscript.refresh = function(doc, dt, dn) {
}else{ }else{
unhide_field(['address_html','contact_html']); unhide_field(['address_html','contact_html']);
// make lists // make lists
cur_frm.cscript.make_address(doc, dt, dn);
cur_frm.cscript.make_contact(doc, dt, dn); erpnext.utils.render_address_and_contact(cur_frm)
cur_frm.communication_view = new frappe.views.CommunicationList({ cur_frm.communication_view = new frappe.views.CommunicationList({
parent: cur_frm.fields_dict.communication_html.wrapper, parent: cur_frm.fields_dict.communication_html.wrapper,
@@ -79,44 +77,6 @@ cur_frm.cscript.setup_dashboard = function(doc) {
}); });
} }
cur_frm.cscript.make_address = function() {
if(!cur_frm.address_list) {
cur_frm.address_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['address_html'].wrapper,
page_length: 5,
new_doctype: "Address",
get_query: function() {
return "select name, address_type, address_line1, address_line2, city, state, country, pincode, fax, email_id, phone, is_primary_address, is_shipping_address from tabAddress where customer='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_address desc"
},
as_dict: 1,
no_results_message: __('No addresses created'),
render_row: cur_frm.cscript.render_address_row,
});
// note: render_address_row is defined in contact_control.js
}
cur_frm.address_list.run();
}
cur_frm.cscript.make_contact = function() {
if(!cur_frm.contact_list) {
cur_frm.contact_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['contact_html'].wrapper,
page_length: 5,
new_doctype: "Contact",
get_query: function() {
return "select name, first_name, last_name, email_id, phone, mobile_no, department, designation, is_primary_contact from tabContact where customer='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_contact desc"
},
as_dict: 1,
no_results_message: __('No contacts created'),
render_row: cur_frm.cscript.render_contact_row,
});
// note: render_contact_row is defined in contact_control.js
}
cur_frm.contact_list.run();
}
cur_frm.fields_dict['customer_group'].get_query = function(doc, dt, dn) { cur_frm.fields_dict['customer_group'].get_query = function(doc, dt, dn) {
return{ return{
filters:{'is_group': 'No'} filters:{'is_group': 'No'}

View File

@@ -7,11 +7,14 @@ from frappe.model.naming import make_autoname
from frappe import msgprint, _ from frappe import msgprint, _
import frappe.defaults import frappe.defaults
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from erpnext.utilities.address_and_contact import load_address_and_contact
from erpnext.accounts.party import create_party_account from erpnext.accounts.party import create_party_account
class Customer(TransactionBase): class Customer(TransactionBase):
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self, "customer")
def autoname(self): def autoname(self):
cust_master_name = frappe.defaults.get_global_default('cust_master_name') cust_master_name = frappe.defaults.get_global_default('cust_master_name')

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
{% include 'setup/doctype/contact_control/contact_control.js' %};
frappe.provide("erpnext"); frappe.provide("erpnext");
erpnext.LeadController = frappe.ui.form.Controller.extend({ erpnext.LeadController = frappe.ui.form.Controller.extend({
setup: function() { setup: function() {
@@ -42,33 +40,10 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
}); });
if(!this.frm.doc.__islocal) { if(!this.frm.doc.__islocal) {
this.make_address_list(); erpnext.utils.render_address_and_contact(cur_frm);
} }
}, },
make_address_list: function() {
var me = this;
if(!this.frm.address_list) {
this.frm.address_list = new frappe.ui.Listing({
parent: this.frm.fields_dict['address_html'].wrapper,
page_length: 5,
new_doctype: "Address",
get_query: function() {
return 'select name, address_type, address_line1, address_line2, \
city, state, country, pincode, fax, email_id, phone, \
is_primary_address, is_shipping_address from tabAddress \
where lead="'+me.frm.doc.name+'" and docstatus != 2 \
order by is_primary_address, is_shipping_address desc'
},
as_dict: 1,
no_results_message: __('No addresses created'),
render_row: this.render_address_row,
});
// note: render_address_row is defined in contact_control.js
}
this.frm.address_list.run();
},
create_customer: function() { create_customer: function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.lead.lead.make_customer", method: "erpnext.selling.doctype.lead.lead.make_customer",

View File

@@ -160,7 +160,7 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"description": "Add to calendar on this date", "description": "Add to calendar on this date",
"fieldname": "contact_date", "fieldname": "contact_date",
"fieldtype": "Date", "fieldtype": "Datetime",
"in_filter": 1, "in_filter": 1,
"label": "Next Contact Date", "label": "Next Contact Date",
"no_copy": 1, "no_copy": 1,
@@ -368,7 +368,7 @@
], ],
"icon": "icon-user", "icon": "icon-user",
"idx": 1, "idx": 1,
"modified": "2014-08-12 05:22:18.801092", "modified": "2014-12-01 08:22:23.286314",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Lead", "name": "Lead",

View File

@@ -9,11 +9,13 @@ from frappe import session
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from erpnext.utilities.address_and_contact import load_address_and_contact
class Lead(SellingController): class Lead(SellingController):
def onload(self): def onload(self):
customer = frappe.db.get_value("Customer", {"lead_name": self.name}) customer = frappe.db.get_value("Customer", {"lead_name": self.name})
self.get("__onload").is_customer = customer self.get("__onload").is_customer = customer
load_address_and_contact(self, "lead")
def validate(self): def validate(self):
self._prev = frappe._dict({ self._prev = frappe._dict({
@@ -43,6 +45,7 @@ class Lead(SellingController):
def add_calendar_event(self, opts=None, force=False): def add_calendar_event(self, opts=None, force=False):
super(Lead, self).add_calendar_event({ super(Lead, self).add_calendar_event({
"owner": self.lead_owner, "owner": self.lead_owner,
"starts_on": self.contact_date,
"subject": ('Contact ' + cstr(self.lead_name)), "subject": ('Contact ' + cstr(self.lead_name)),
"description": ('Contact ' + cstr(self.lead_name)) + \ "description": ('Contact ' + cstr(self.lead_name)) + \
(self.contact_by and ('. By : ' + cstr(self.contact_by)) or '') (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')

View File

@@ -372,7 +372,7 @@
{ {
"description": "Your sales person will get a reminder on this date to contact the customer", "description": "Your sales person will get a reminder on this date to contact the customer",
"fieldname": "contact_date", "fieldname": "contact_date",
"fieldtype": "Date", "fieldtype": "Datetime",
"label": "Next Contact Date", "label": "Next Contact Date",
"oldfieldname": "contact_date", "oldfieldname": "contact_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
@@ -416,7 +416,7 @@
"icon": "icon-info-sign", "icon": "icon-info-sign",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-08-12 05:21:51.282397", "modified": "2014-12-01 08:46:35.331148",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Opportunity", "name": "Opportunity",

View File

@@ -57,6 +57,7 @@ class Opportunity(TransactionBase):
opts = frappe._dict() opts = frappe._dict()
opts.description = "" opts.description = ""
opts.contact_date = self.contact_date
if self.customer: if self.customer:
if self.contact_person: if self.contact_person:

View File

@@ -23,7 +23,7 @@ class Quotation(SellingController):
self.validate_order_type() self.validate_order_type()
self.validate_for_items() self.validate_for_items()
self.validate_uom_is_integer("stock_uom", "qty") self.validate_uom_is_integer("stock_uom", "qty")
self.quotation_to = "Customer" if self.customer else "Lead" self.validate_quotation_to()
def has_sales_order(self): def has_sales_order(self):
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1}) return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
@@ -54,6 +54,13 @@ class Quotation(SellingController):
if is_sales_item == 'No': if is_sales_item == 'No':
frappe.throw(_("Item {0} must be Sales Item").format(d.item_code)) frappe.throw(_("Item {0} must be Sales Item").format(d.item_code))
def validate_quotation_to(self):
if self.customer:
self.quotation_to = "Customer"
self.lead = None
elif self.lead:
self.quotation_to = "Lead"
def update_opportunity(self): def update_opportunity(self):
for opportunity in list(set([d.prevdoc_docname for d in self.get("quotation_details")])): for opportunity in list(set([d.prevdoc_docname for d in self.get("quotation_details")])):
if opportunity: if opportunity:
@@ -139,8 +146,8 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
return doclist return doclist
def _make_customer(source_name, ignore_permissions=False): def _make_customer(source_name, ignore_permissions=False):
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"]) quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type", "customer"])
if quotation and quotation[0]: if quotation and quotation[0] and not quotation[2]:
lead_name = quotation[0] lead_name = quotation[0]
customer_name = frappe.db.get_value("Customer", {"lead_name": lead_name}, customer_name = frappe.db.get_value("Customer", {"lead_name": lead_name},
["name", "customer_name"], as_dict=True) ["name", "customer_name"], as_dict=True)

View File

@@ -169,24 +169,6 @@
"search_index": 1, "search_index": 1,
"width": "160px" "width": "160px"
}, },
{
"allow_on_submit": 1,
"description": "Start date of current order's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"description": "End date of current order's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0
},
{ {
"description": "Customer's Purchase Order Number", "description": "Customer's Purchase Order Number",
"fieldname": "po_no", "fieldname": "po_no",
@@ -951,6 +933,26 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current order's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current order's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -962,17 +964,6 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
@@ -991,6 +982,17 @@
"permlevel": 0, "permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{ {
"depends_on": "eval:doc.is_recurring==1", "depends_on": "eval:doc.is_recurring==1",
"fieldname": "recurring_id", "fieldname": "recurring_id",
@@ -1018,7 +1020,7 @@
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "issingle": 0,
"modified": "2014-09-18 03:17:33.241162", "modified": "2014-10-08 14:22:44.717108",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

@@ -157,7 +157,7 @@ class SalesOrder(SellingController):
self.check_credit(self.grand_total) self.check_credit(self.grand_total)
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.grand_total, self) frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total, self)
self.update_prevdoc_status('submit') self.update_prevdoc_status('submit')
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
@@ -329,7 +329,7 @@ def make_sales_invoice(source_name, target_doc=None):
def update_item(source, target, source_parent): def update_item(source, target, source_parent):
target.amount = flt(source.amount) - flt(source.billed_amt) target.amount = flt(source.amount) - flt(source.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate) target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = source.rate and target.amount / flt(source.rate) or source.qty target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
@@ -357,17 +357,6 @@ def make_sales_invoice(source_name, target_doc=None):
} }
}, target_doc, postprocess) }, target_doc, postprocess)
def set_advance_vouchers(source, target):
advance_voucher_list = []
advance_voucher = frappe.db.sql("""
select
t1.name as voucher_no, t1.posting_date, t1.remark, t2.account,
t2.name as voucher_detail_no, {amount_query} as payment_amount, t2.is_advance
from
`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
""")
return doclist return doclist
@frappe.whitelist() @frappe.whitelist()

View File

@@ -341,11 +341,8 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
var me = this; var me = this;
var tax_count = this.frm.tax_doclist.length; var tax_count = this.frm.tax_doclist.length;
this.frm.doc.grand_total = flt( this.frm.doc.grand_total = flt(tax_count ? this.frm.tax_doclist[tax_count - 1].total : this.frm.doc.net_total);
tax_count ? this.frm.tax_doclist[tax_count - 1].total : this.frm.doc.net_total, this.frm.doc.grand_total_export = flt(this.frm.doc.grand_total / this.frm.doc.conversion_rate);
precision("grand_total"));
this.frm.doc.grand_total_export = flt(this.frm.doc.grand_total / this.frm.doc.conversion_rate,
precision("grand_total_export"));
this.frm.doc.other_charges_total = flt(this.frm.doc.grand_total - this.frm.doc.net_total, this.frm.doc.other_charges_total = flt(this.frm.doc.grand_total - this.frm.doc.net_total,
precision("other_charges_total")); precision("other_charges_total"));
@@ -353,6 +350,9 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
this.frm.doc.net_total_export + flt(this.frm.doc.discount_amount), this.frm.doc.net_total_export + flt(this.frm.doc.discount_amount),
precision("other_charges_total_export")); precision("other_charges_total_export"));
this.frm.doc.grand_total = flt(this.frm.doc.grand_total, precision("grand_total"));
this.frm.doc.grand_total_export = flt(this.frm.doc.grand_total_export, precision("grand_total_export"));
this.frm.doc.rounded_total = Math.round(this.frm.doc.grand_total); this.frm.doc.rounded_total = Math.round(this.frm.doc.grand_total);
this.frm.doc.rounded_total_export = Math.round(this.frm.doc.grand_total_export); this.frm.doc.rounded_total_export = Math.round(this.frm.doc.grand_total_export);
}, },
@@ -587,6 +587,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
}); });
frappe.ui.form.on(cur_frm.doctype,"project_name", function(frm) { frappe.ui.form.on(cur_frm.doctype,"project_name", function(frm) {
if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) {
frappe.call({ frappe.call({
method:'erpnext.projects.doctype.project.project.get_cost_center_name' , method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
args: { project_name: frm.doc.project_name }, args: { project_name: frm.doc.project_name },
@@ -599,4 +600,5 @@ frappe.ui.form.on(cur_frm.doctype,"project_name", function(frm) {
} }
} }
}) })
}
}) })

View File

@@ -98,9 +98,12 @@ class AuthorizationControl(TransactionBase):
if doc_obj: if doc_obj:
price_list_rate, base_rate = 0, 0 price_list_rate, base_rate = 0, 0
for d in doc_obj.get(doc_obj.fname): for d in doc_obj.get(doc_obj.fname):
if d.base_price_list_rate and d.base_rate: if d.base_rate:
price_list_rate += flt(d.base_price_list_rate) price_list_rate += flt(d.base_price_list_rate) or flt(d.base_rate)
base_rate += flt(d.base_rate) base_rate += flt(d.base_rate)
if doc_obj.get("discount_amount"):
base_rate -= flt(doc_obj.discount_amount)
if price_list_rate: av_dis = 100 - flt(base_rate * 100 / price_list_rate) if price_list_rate: av_dis = 100 - flt(base_rate * 100 / price_list_rate)
final_based_on = ['Grand Total','Average Discount','Customerwise Discount','Itemwise Discount'] final_based_on = ['Grand Total','Average Discount','Customerwise Discount','Itemwise Discount']

View File

@@ -1 +0,0 @@
[To deprecate] Common scripts for Contacts.

View File

@@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@@ -1,161 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
if(cur_frm.fields_dict['territory']) {
cur_frm.fields_dict['territory'].get_query = function(doc, dt, dn) {
return {
filters: {
'is_group': "No"
}
}
}
}
cur_frm.cscript.render_contact_row = function(wrapper, data) {
// prepare data
data.fullname = (data.first_name || '')
+ (data.last_name ? ' ' + data.last_name : '');
data.primary = data.is_primary_contact ? ' [Primary]' : '';
// prepare description
var description = [];
$.each([
['phone', 'Tel'],
['mobile_no', 'Mobile'],
['email_id', 'Email'],
['department', 'Department'],
['designation', 'Designation']],
function(i, v) {
if(v[0] && data[v[0]]) {
description.push(repl('<h6>%(label)s:</h6> %(value)s', {
label: v[1],
value: data[v[0]],
}));
}
});
data.description = description.join('<br />');
cur_frm.cscript.render_row_in_wrapper(wrapper, data, 'Contact');
}
cur_frm.cscript.render_address_row = function(wrapper, data) {
// prepare data
data.fullname = data.address_type;
data.primary = '';
if (data.is_primary_address) data.primary += ' [Preferred for Billing]';
if (data.is_shipping_address) data.primary += ' [Preferred for Shipping]';
// prepare address
var address = [];
$.each(['address_line1', 'address_line2', 'city', 'state', 'country', 'pincode'],
function(i, v) {
if(data[v]) address.push(data[v]);
});
data.address = address.join('<br />');
data.address = "<p class='address-list'>" + data.address + "</p>";
// prepare description
var description = [];
$.each([
['address', 'Address'],
['phone', 'Tel'],
['fax', 'Fax'],
['email_id', 'Email']],
function(i, v) {
if(data[v[0]]) {
description.push(repl('<h6>%(label)s:</h6> %(value)s', {
label: v[1],
value: data[v[0]],
}));
}
});
data.description = description.join('<br />');
cur_frm.cscript.render_row_in_wrapper(wrapper, data, 'Address');
$(wrapper).find('p.address-list').css({
'padding-left': '10px',
'margin-bottom': '-10px'
});
}
cur_frm.cscript.render_row_in_wrapper = function(wrapper, data, doctype) {
// render
var $wrapper = $(wrapper);
data.doctype = doctype.toLowerCase();
$wrapper.append(repl("\
<h4><a class='link_type'>%(fullname)s</a>%(primary)s</h4>\
<div class='description'>\
<p>%(description)s</p>\
<p><a class='delete link_type'>delete this %(doctype)s</a></p>\
</div>", data));
// make link
$wrapper.find('h4 a.link_type').click(function() {
loaddoc(doctype, data.name);
});
// css
$wrapper.css({ 'margin': '0px' });
$wrapper.find('div.description').css({
'padding': '5px 2px',
'line-height': '150%',
});
$wrapper.find('h6').css({ 'display': 'inline-block' });
// show delete
var $delete_doc = $wrapper.find('a.delete');
if (frappe.model.can_delete(doctype))
$delete_doc.toggle(true);
else
$delete_doc.toggle(false);
$delete_doc.css({ 'padding-left': '0px' });
$delete_doc.click(function() {
cur_frm.cscript.delete_doc(doctype, data.name);
return false;
});
}
cur_frm.cscript.delete_doc = function(doctype, name) {
// confirm deletion
var go_ahead = confirm(__("Delete {0} {1}?", [doctype, name]));
if (!go_ahead) return;
frappe.model.delete_doc(doctype, name, function(r) {
if (!r.exc) {
var list_name = doctype.toLowerCase() + '_list';
cur_frm[list_name].run();
}
});
}
// Render List
cur_frm.cscript.render_list = function(doc, doctype, wrapper, ListView, make_new_doc) {
frappe.model.with_doctype(doctype, function(r) {
if((r && r['403']) || frappe.boot.user.all_read.indexOf(doctype)===-1) {
return;
}
var RecordListView = frappe.views.RecordListView.extend({
default_docstatus: ['0', '1', '2'],
default_filters: [
[doctype, doc.doctype.toLowerCase().replace(" ", "_"), '=', doc.name],
],
});
if (make_new_doc) {
RecordListView = RecordListView.extend({
make_new_doc: make_new_doc,
});
}
var record_list_view = new RecordListView(doctype, wrapper, ListView);
if (!cur_frm[doctype.toLowerCase().replace(" ", "_") + "_list"]) {
cur_frm[doctype.toLowerCase().replace(" ", "_") + "_list"] = record_list_view;
}
});
}

View File

@@ -1,48 +0,0 @@
{
"creation": "2012-03-27 14:36:19.000000",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"fieldname": "header",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Header",
"permlevel": 0
},
{
"fieldname": "customer_intro",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Customer Intro",
"permlevel": 0
},
{
"fieldname": "supplier_intro",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Supplier Intro",
"permlevel": 0
}
],
"idx": 1,
"in_create": 1,
"issingle": 1,
"modified": "2013-12-20 19:23:02.000000",
"modified_by": "Administrator",
"module": "Setup",
"name": "Contact Control",
"owner": "Administrator",
"permissions": [
{
"create": 0,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"role": "System Manager",
"write": 1
}
],
"read_only": 1
}

View File

@@ -1,10 +0,0 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ContactControl(Document):
pass

View File

@@ -1,12 +1,6 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
{% include 'setup/doctype/contact_control/contact_control.js' %};
cur_frm.cscript.onload = function(doc,dt,dn){
}
cur_frm.cscript.refresh = function(doc,dt,dn){ cur_frm.cscript.refresh = function(doc,dt,dn){
if(doc.__islocal){ if(doc.__islocal){
@@ -15,8 +9,8 @@ cur_frm.cscript.refresh = function(doc,dt,dn){
else{ else{
unhide_field(['address_html', 'contact_html']); unhide_field(['address_html', 'contact_html']);
// make lists // make lists
cur_frm.cscript.make_address(doc,dt,dn);
cur_frm.cscript.make_contact(doc,dt,dn); erpnext.utils.render_address_and_contact(cur_frm)
if (doc.show_in_website) { if (doc.show_in_website) {
cur_frm.set_intro(__("Published on website at: {0}", cur_frm.set_intro(__("Published on website at: {0}",
@@ -25,57 +19,6 @@ cur_frm.cscript.refresh = function(doc,dt,dn){
} }
} }
cur_frm.cscript.make_address = function() {
if(!cur_frm.address_list) {
cur_frm.address_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['address_html'].wrapper,
page_length: 2,
new_doctype: "Address",
custom_new_doc: function(doctype) {
var address = frappe.model.make_new_doc_and_get_name('Address');
address = locals['Address'][address];
address.sales_partner = cur_frm.doc.name;
address.address_title = cur_frm.doc.name;
address.address_type = "Office";
frappe.set_route("Form", "Address", address.name);
},
get_query: function() {
return "select name, address_type, address_line1, address_line2, city, state, country, pincode, fax, email_id, phone, is_primary_address, is_shipping_address from tabAddress where sales_partner='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_address desc"
},
as_dict: 1,
no_results_message: __('No addresses created'),
render_row: cur_frm.cscript.render_address_row,
});
}
cur_frm.address_list.run();
}
cur_frm.cscript.make_contact = function() {
if(!cur_frm.contact_list) {
cur_frm.contact_list = new frappe.ui.Listing({
parent: cur_frm.fields_dict['contact_html'].wrapper,
page_length: 2,
new_doctype: "Contact",
custom_new_doc: function(doctype) {
var contact = frappe.model.make_new_doc_and_get_name('Contact');
contact = locals['Contact'][contact];
contact.sales_partner = cur_frm.doc.name;
frappe.set_route("Form", "Contact", contact.name);
},
get_query: function() {
return "select name, first_name, last_name, email_id, phone, mobile_no, department, designation, is_primary_contact from tabContact where sales_partner='" +
cur_frm.doc.name.replace(/'/g, "\\'") + "' and docstatus != 2 order by is_primary_contact desc"
},
as_dict: 1,
no_results_message: __('No contacts created'),
render_row: cur_frm.cscript.render_contact_row,
});
}
cur_frm.contact_list.run();
}
cur_frm.fields_dict['partner_target_details'].grid.get_field("item_group").get_query = function(doc, dt, dn) { cur_frm.fields_dict['partner_target_details'].grid.get_field("item_group").get_query = function(doc, dt, dn) {
return{ return{
filters:{ 'is_group': "No" } filters:{ 'is_group': "No" }

View File

@@ -5,11 +5,16 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cstr, filter_strip_join from frappe.utils import cstr, filter_strip_join
from frappe.website.website_generator import WebsiteGenerator from frappe.website.website_generator import WebsiteGenerator
from erpnext.utilities.address_and_contact import load_address_and_contact
class SalesPartner(WebsiteGenerator): class SalesPartner(WebsiteGenerator):
page_title_field = "partner_name" page_title_field = "partner_name"
condition_field = "show_in_website" condition_field = "show_in_website"
template = "templates/generators/sales_partner.html" template = "templates/generators/sales_partner.html"
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self, "sales_partner")
def autoname(self): def autoname(self):
self.name = self.partner_name self.name = self.partner_name

View File

@@ -59,15 +59,18 @@ frappe.pages['setup-wizard'].onload = function(wrapper) {
title: __("Select Your Language"), title: __("Select Your Language"),
icon: "icon-globe", icon: "icon-globe",
fields: [ fields: [
{"fieldname": "language", "label": __("Language"), "fieldtype": "Select", {
"fieldname": "language", "label": __("Language"), "fieldtype": "Select",
options: ["english", "العربية", "deutsch", "ελληνικά", "español", "français", "हिंदी", "hrvatski", options: ["english", "العربية", "deutsch", "ελληνικά", "español", "français", "हिंदी", "hrvatski",
"italiano", "nederlands", "polski", "português brasileiro", "português", "српски", "தமிழ்", "italiano", "nederlands", "polski", "português brasileiro", "português", "српски", "தமிழ்",
"ไทย", "中国(简体)", "中國(繁體)"], reqd:1}, "ไทย", "中国(简体)", "中國(繁體)"],
reqd:1, "default": "english"
},
], ],
help: __("Welcome to ERPNext. Please select your language to begin the Setup Wizard."), help: __("Welcome to ERPNext. Please select your language to begin the Setup Wizard."),
onload: function(slide) { onload: function(slide) {
slide.get_input("language").on("change", function() { slide.get_input("language").on("change", function() {
var lang = $(this).val(); var lang = $(this).val() || "english";
frappe._messages = {}; frappe._messages = {};
frappe.call({ frappe.call({
method: "erpnext.setup.page.setup_wizard.setup_wizard.load_messages", method: "erpnext.setup.page.setup_wizard.setup_wizard.load_messages",

View File

@@ -71,13 +71,14 @@ def setup_account(args=None):
frappe.db.set_default('desktop:home_page', 'desktop') frappe.db.set_default('desktop:home_page', 'desktop')
website_maker(args.company_name, args.company_tagline, args.name) website_maker(args.company_name.strip(), args.company_tagline, args.name)
create_logo(args) create_logo(args)
frappe.clear_cache() frappe.clear_cache()
frappe.db.commit() frappe.db.commit()
except: except:
if args:
traceback = frappe.get_traceback() traceback = frappe.get_traceback()
for hook in frappe.get_hooks("setup_wizard_exception"): for hook in frappe.get_hooks("setup_wizard_exception"):
frappe.get_attr(hook)(traceback, args) frappe.get_attr(hook)(traceback, args)
@@ -134,7 +135,7 @@ def create_fiscal_year_and_company(args):
frappe.get_doc({ frappe.get_doc({
"doctype":"Company", "doctype":"Company",
'domain': args.get("industry"), 'domain': args.get("industry"),
'company_name':args.get('company_name'), 'company_name':args.get('company_name').strip(),
'abbr':args.get('company_abbr'), 'abbr':args.get('company_abbr'),
'default_currency':args.get('currency'), 'default_currency':args.get('currency'),
'country': args.get('country'), 'country': args.get('country'),
@@ -165,7 +166,7 @@ def set_defaults(args):
global_defaults.update({ global_defaults.update({
'current_fiscal_year': args.curr_fiscal_year, 'current_fiscal_year': args.curr_fiscal_year,
'default_currency': args.get('currency'), 'default_currency': args.get('currency'),
'default_company':args.get('company_name'), 'default_company':args.get('company_name').strip(),
"country": args.get("country"), "country": args.get("country"),
}) })
@@ -284,7 +285,7 @@ def create_taxes(args):
try: try:
frappe.get_doc({ frappe.get_doc({
"doctype":"Account", "doctype":"Account",
"company": args.get("company_name"), "company": args.get("company_name").strip(),
"parent_account": _("Duties and Taxes") + " - " + args.get("company_abbr"), "parent_account": _("Duties and Taxes") + " - " + args.get("company_abbr"),
"account_name": args.get("tax_" + str(i)), "account_name": args.get("tax_" + str(i)),
"group_or_ledger": "Ledger", "group_or_ledger": "Ledger",
@@ -344,7 +345,7 @@ def create_customers(args):
"customer_type": "Company", "customer_type": "Company",
"customer_group": _("Commercial"), "customer_group": _("Commercial"),
"territory": args.get("country"), "territory": args.get("country"),
"company": args.get("company_name") "company": args.get("company_name").strip()
}).insert() }).insert()
if args.get("customer_contact_" + str(i)): if args.get("customer_contact_" + str(i)):
@@ -364,7 +365,7 @@ def create_suppliers(args):
"doctype":"Supplier", "doctype":"Supplier",
"supplier_name": supplier, "supplier_name": supplier,
"supplier_type": _("Local"), "supplier_type": _("Local"),
"company": args.get("company_name") "company": args.get("company_name").strip()
}).insert() }).insert()
if args.get("supplier_contact_" + str(i)): if args.get("supplier_contact_" + str(i)):

View File

@@ -78,7 +78,8 @@ data_map = {
"Stock Ledger Entry": { "Stock Ledger Entry": {
"columns": ["name", "posting_date", "posting_time", "item_code", "warehouse", "columns": ["name", "posting_date", "posting_time", "item_code", "warehouse",
"actual_qty as qty", "voucher_type", "voucher_no", "project", "actual_qty as qty", "voucher_type", "voucher_no", "project",
"ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no"], "ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no",
"qty_after_transaction", "valuation_rate"],
"order_by": "posting_date, posting_time, name", "order_by": "posting_date, posting_time, name",
"links": { "links": {
"item_code": ["Item", "name"], "item_code": ["Item", "name"],

View File

@@ -3,8 +3,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class Batch(Document): class Batch(Document):
pass
def validate(self):
self.item_has_batch_enabled()
def item_has_batch_enabled(self):
has_batch_no = frappe.db.get_value("Item",self.item,"has_batch_no")
if has_batch_no =='No':
frappe.throw(_("The selected item cannot have Batch"))

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.exceptions import ValidationError
import unittest
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch",
"name": "_test Batch",
"item": "_Test Item"
}).save)

View File

@@ -26,7 +26,7 @@ class Bin(Document):
def update_stock(self, args): def update_stock(self, args):
self.update_qty(args) self.update_qty(args)
if args.get("actual_qty"): if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
from erpnext.stock.stock_ledger import update_entries_after from erpnext.stock.stock_ledger import update_entries_after
if not args.get("posting_date"): if not args.get("posting_date"):
@@ -42,8 +42,21 @@ class Bin(Document):
def update_qty(self, args): def update_qty(self, args):
# update the stock values (for current quantities) # update the stock values (for current quantities)
if args.get("voucher_type")=="Stock Reconciliation":
if args.get('is_cancelled') == 'No':
self.actual_qty = args.get("qty_after_transaction")
else:
qty_after_transaction = frappe.db.get_value("""select qty_after_transaction
from `tabStock Ledger Entry`
where item_code=%s and warehouse=%s
and not (voucher_type='Stock Reconciliation' and voucher_no=%s)
order by posting_date desc limit 1""",
(self.item_code, self.warehouse, args.get('voucher_no')))
self.actual_qty = flt(qty_after_transaction[0][0]) if qty_after_transaction else 0.0
else:
self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty"))
self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty")) self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty"))
self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))

View File

@@ -52,11 +52,15 @@ class DeliveryNote(SellingController):
else: else:
df.delete_key("__print_hide") df.delete_key("__print_hide")
toggle_print_hide(self.meta, "currency")
item_meta = frappe.get_meta("Delivery Note Item") item_meta = frappe.get_meta("Delivery Note Item")
for fieldname in ("rate", "amount", "price_list_rate", "discount_percentage"): print_hide_fields = {
toggle_print_hide(item_meta, fieldname) "parent": ["grand_total_export", "rounded_total_export", "in_words_export", "currency", "net_total_export"],
"items": ["rate", "amount", "price_list_rate", "discount_percentage"]
}
for key, fieldname in print_hide_fields.items():
for f in fieldname:
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
def get_portal_page(self): def get_portal_page(self):
return "shipment" if self.docstatus==1 else None return "shipment" if self.docstatus==1 else None
@@ -245,7 +249,7 @@ class DeliveryNote(SellingController):
sl_entries = [] sl_entries = []
for d in self.get_item_list(): for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \ if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
and d.warehouse: and d.warehouse and flt(d['qty']):
self.update_reserved_qty(d) self.update_reserved_qty(d)
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
@@ -377,7 +381,8 @@ def make_packing_slip(source_name, target_doc=None):
"Delivery Note": { "Delivery Note": {
"doctype": "Packing Slip", "doctype": "Packing Slip",
"field_map": { "field_map": {
"name": "delivery_note" "name": "delivery_note",
"letter_head": "letter_head"
}, },
"validation": { "validation": {
"docstatus": ["=", 0] "docstatus": ["=", 0]

View File

@@ -19,7 +19,7 @@ cur_frm.cscript.refresh = function(doc) {
cur_frm.cscript.edit_prices_button(); cur_frm.cscript.edit_prices_button();
if (!doc.__islocal && doc.is_stock_item == 'Yes') { if (!doc.__islocal && doc.is_stock_item == 'Yes') {
cur_frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method'], cur_frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method', 'has_batch_no'],
(doc.__onload && doc.__onload.sle_exists=="exists") ? false : true); (doc.__onload && doc.__onload.sle_exists=="exists") ? false : true);
} }

View File

@@ -187,13 +187,14 @@ class Item(WebsiteGenerator):
def cant_change(self): def cant_change(self):
if not self.get("__islocal"): if not self.get("__islocal"):
vals = frappe.db.get_value("Item", self.name, vals = frappe.db.get_value("Item", self.name,
["has_serial_no", "is_stock_item", "valuation_method"], as_dict=True) ["has_serial_no", "is_stock_item", "valuation_method", "has_batch_no"], as_dict=True)
if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or
vals.has_serial_no != self.has_serial_no or vals.has_serial_no != self.has_serial_no or
vals.has_batch_no != self.has_batch_no or
cstr(vals.valuation_method) != cstr(self.valuation_method)): cstr(vals.valuation_method) != cstr(self.valuation_method)):
if self.check_if_sle_exists() == "exists": if self.check_if_sle_exists() == "exists":
frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Is Stock Item' and 'Valuation Method'")) frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
def validate_item_type_for_reorder(self): def validate_item_type_for_reorder(self):
if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})): if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})):

View File

@@ -9,6 +9,6 @@ class TestItem(unittest.TestCase):
def test_duplicate_item(self): def test_duplicate_item(self):
from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem
doc = frappe.copy_doc(test_records[0]) doc = frappe.copy_doc(test_records[0])
self.assertRaises(ItemPriceDuplicateItem, doc.insert) self.assertRaises(ItemPriceDuplicateItem, doc.save)
test_records = frappe.get_test_records('Item Price') test_records = frappe.get_test_records('Item Price')

View File

@@ -16,5 +16,11 @@
"item_code": "_Test Item 2", "item_code": "_Test Item 2",
"price_list": "_Test Price List Rest of the World", "price_list": "_Test Price List Rest of the World",
"price_list_rate": 20 "price_list_rate": 20
},
{
"doctype": "Item Price",
"item_code": "_Test Item Home Desktop 100",
"price_list": "_Test Price List",
"price_list_rate": 1000
} }
] ]

View File

@@ -97,10 +97,10 @@ class LandedCostVoucher(Document):
# update stock & gl entries for cancelled state of PR # update stock & gl entries for cancelled state of PR
pr.docstatus = 2 pr.docstatus = 2
pr.update_stock() pr.update_stock_ledger()
pr.make_gl_entries_on_cancel() pr.make_gl_entries_on_cancel()
# update stock & gl entries for submit state of PR # update stock & gl entries for submit state of PR
pr.docstatus = 1 pr.docstatus = 1
pr.update_stock() pr.update_stock_ledger()
pr.make_gl_entries() pr.make_gl_entries()

View File

@@ -79,30 +79,9 @@ class MaterialRequest(BuyingController):
# NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated # NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated
# Though the creation of Material Request from a Production Plan can be rethought to fix this # Though the creation of Material Request from a Production Plan can be rethought to fix this
def update_bin(self, is_submit, is_stopped):
""" Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'"""
from erpnext.stock.utils import update_bin
for d in self.get('indent_details'):
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes":
if not d.warehouse:
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
qty =flt(d.qty)
if is_stopped:
qty = (d.qty > d.ordered_qty) and flt(flt(d.qty) - flt(d.ordered_qty)) or 0
args = {
"item_code": d.item_code,
"warehouse": d.warehouse,
"indented_qty": (is_submit and 1 or -1) * flt(qty),
"posting_date": self.transaction_date
}
update_bin(args)
def on_submit(self): def on_submit(self):
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
self.update_bin(is_submit = 1, is_stopped = 0) self.update_requested_qty()
def check_modified_date(self): def check_modified_date(self):
mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""", mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""",
@@ -115,23 +94,18 @@ class MaterialRequest(BuyingController):
def update_status(self, status): def update_status(self, status):
self.check_modified_date() self.check_modified_date()
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) self.update_requested_qty()
frappe.db.set(self, 'status', cstr(status)) frappe.db.set(self, 'status', cstr(status))
frappe.msgprint(_("Status updated to {0}").format(_(status))) frappe.msgprint(_("Status updated to {0}").format(_(status)))
def on_cancel(self): def on_cancel(self):
# Step 1:=> Get Purchase Common Obj
pc_obj = frappe.get_doc('Purchase Common') pc_obj = frappe.get_doc('Purchase Common')
# Step 2:=> Check for stopped status
pc_obj.check_for_stopped_status(self.doctype, self.name) pc_obj.check_for_stopped_status(self.doctype, self.name)
# Step 3:=> Check if Purchase Order has been submitted against current Material Request
pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Order', docname = self.name, detail_doctype = 'Purchase Order Item') pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Order', docname = self.name, detail_doctype = 'Purchase Order Item')
# Step 4:=> Update Bin
self.update_bin(is_submit = 0, is_stopped = (cstr(self.status) == 'Stopped') and 1 or 0)
# Step 5:=> Set Status self.update_requested_qty()
frappe.db.set(self,'status','Cancelled') frappe.db.set(self,'status','Cancelled')
def update_completed_qty(self, mr_items=None): def update_completed_qty(self, mr_items=None):
@@ -162,56 +136,47 @@ class MaterialRequest(BuyingController):
self.per_ordered = flt((per_ordered / flt(len(item_doclist))) * 100.0, 2) self.per_ordered = flt((per_ordered / flt(len(item_doclist))) * 100.0, 2)
frappe.db.set_value(self.doctype, self.name, "per_ordered", self.per_ordered) frappe.db.set_value(self.doctype, self.name, "per_ordered", self.per_ordered)
def update_completed_qty(doc, method): def update_requested_qty(self, mr_item_rows=None):
if doc.doctype == "Stock Entry": """update requested qty (before ordered_qty is updated)"""
from erpnext.stock.utils import get_bin
def _update_requested_qty(item_code, warehouse):
requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0))
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
where mr_item.item_code=%s and mr_item.warehouse=%s
and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name
and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
bin_doc = get_bin(item_code, warehouse)
bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0
bin_doc.save()
item_wh_list = []
for d in self.get("indent_details"):
if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
_update_requested_qty(item_code, warehouse)
def update_completed_and_requested_qty(stock_entry, method):
if stock_entry.doctype == "Stock Entry":
material_request_map = {} material_request_map = {}
for d in doc.get("mtn_details"): for d in stock_entry.get("mtn_details"):
if d.material_request: if d.material_request:
material_request_map.setdefault(d.material_request, []).append(d.material_request_item) material_request_map.setdefault(d.material_request, []).append(d.material_request_item)
for mr_name, mr_items in material_request_map.items(): for mr, mr_item_rows in material_request_map.items():
mr_obj = frappe.get_doc("Material Request", mr_name) if mr and mr_item_rows:
mr_obj = frappe.get_doc("Material Request", mr)
if mr_obj.status in ["Stopped", "Cancelled"]: if mr_obj.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr_obj.name), frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError)
frappe.InvalidStatusError)
_update_requested_qty(doc, mr_obj, mr_items) mr_obj.update_completed_qty(mr_item_rows)
mr_obj.update_requested_qty(mr_item_rows)
# update ordered percentage and qty
mr_obj.update_completed_qty(mr_items)
def _update_requested_qty(doc, mr_obj, mr_items):
"""update requested qty (before ordered_qty is updated)"""
from erpnext.stock.utils import update_bin
for mr_item_name in mr_items:
mr_item = mr_obj.get("indent_details", {"name": mr_item_name})
se_detail = doc.get("mtn_details", {"material_request": mr_obj.name,
"material_request_item": mr_item_name})
if mr_item and se_detail:
mr_item = mr_item[0]
se_detail = se_detail[0]
mr_item.ordered_qty = flt(mr_item.ordered_qty)
mr_item.qty = flt(mr_item.qty)
se_detail.transfer_qty = flt(se_detail.transfer_qty)
if se_detail.docstatus == 2 and mr_item.ordered_qty > mr_item.qty \
and se_detail.transfer_qty == mr_item.ordered_qty:
add_indented_qty = mr_item.qty
elif se_detail.docstatus == 1 and \
mr_item.ordered_qty + se_detail.transfer_qty > mr_item.qty:
add_indented_qty = mr_item.qty - mr_item.ordered_qty
else:
add_indented_qty = se_detail.transfer_qty
update_bin({
"item_code": se_detail.item_code,
"warehouse": se_detail.t_warehouse,
"indented_qty": (se_detail.docstatus==2 and 1 or -1) * add_indented_qty,
"posting_date": doc.posting_date,
})
def set_missing_values(source, target_doc): def set_missing_values(source, target_doc):
target_doc.run_method("set_missing_values") target_doc.run_method("set_missing_values")
@@ -240,7 +205,8 @@ def make_purchase_order(source_name, target_doc=None):
["uom", "stock_uom"], ["uom", "stock_uom"],
["uom", "uom"] ["uom", "uom"]
], ],
"postprocess": update_item "postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.qty
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
@@ -278,7 +244,8 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None):
["uom", "stock_uom"], ["uom", "stock_uom"],
["uom", "uom"] ["uom", "uom"]
], ],
"postprocess": update_item "postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.qty
} }
}, target_doc, postprocess) }, target_doc, postprocess)
@@ -350,7 +317,8 @@ def make_stock_entry(source_name, target_doc=None):
"uom": "stock_uom", "uom": "stock_uom",
"warehouse": "t_warehouse" "warehouse": "t_warehouse"
}, },
"postprocess": update_item "postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.qty
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)

View File

@@ -58,12 +58,6 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(se.doctype, "Stock Entry") self.assertEquals(se.doctype, "Stock Entry")
self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details"))) self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details")))
def _test_requested_qty(self, qty1, qty2):
self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
"warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1)
self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 200",
"warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty2)
def _insert_stock_entry(self, qty1, qty2): def _insert_stock_entry(self, qty1, qty2):
se = frappe.get_doc({ se = frappe.get_doc({
"company": "_Test Company", "company": "_Test Company",
@@ -103,7 +97,8 @@ class TestMaterialRequest(unittest.TestCase):
se.submit() se.submit()
def test_completed_qty_for_purchase(self): def test_completed_qty_for_purchase(self):
frappe.db.sql("""delete from `tabBin`""") existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
# submit material request of type Purchase # submit material request of type Purchase
mr = frappe.copy_doc(test_records[0]) mr = frappe.copy_doc(test_records[0])
@@ -115,8 +110,6 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0)
# map a purchase order # map a purchase order
from erpnext.stock.doctype.material_request.material_request import make_purchase_order from erpnext.stock.doctype.material_request.material_request import make_purchase_order
po_doc = make_purchase_order(mr.name) po_doc = make_purchase_order(mr.name)
@@ -149,7 +142,12 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.per_ordered, 50) self.assertEquals(mr.per_ordered, 50)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5)
self._test_requested_qty(27.0, 1.5)
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
po.cancel() po.cancel()
# check if per complete is as expected # check if per complete is as expected
@@ -158,11 +156,15 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, None) self.assertEquals(mr.get("indent_details")[0].ordered_qty, None)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, None) self.assertEquals(mr.get("indent_details")[1].ordered_qty, None)
self._test_requested_qty(54.0, 3.0) current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_completed_qty_for_transfer(self): def test_completed_qty_for_transfer(self):
frappe.db.sql("""delete from `tabBin`""") existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
frappe.db.sql("""delete from `tabStock Ledger Entry`""") existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
# submit material request of type Purchase # submit material request of type Purchase
mr = frappe.copy_doc(test_records[0]) mr = frappe.copy_doc(test_records[0])
@@ -175,7 +177,11 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
from erpnext.stock.doctype.material_request.material_request import make_stock_entry from erpnext.stock.doctype.material_request.material_request import make_stock_entry
@@ -226,7 +232,11 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5)
self._test_requested_qty(27.0, 1.5) current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
# check if per complete is as expected for Stock Entry cancelled # check if per complete is as expected for Stock Entry cancelled
se.cancel() se.cancel()
@@ -235,11 +245,15 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_completed_qty_for_over_transfer(self): def test_completed_qty_for_over_transfer(self):
frappe.db.sql("""delete from `tabBin`""") existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
frappe.db.sql("""delete from `tabStock Ledger Entry`""") existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
# submit material request of type Purchase # submit material request of type Purchase
mr = frappe.copy_doc(test_records[0]) mr = frappe.copy_doc(test_records[0])
@@ -252,8 +266,6 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0)
# map a stock entry # map a stock entry
from erpnext.stock.doctype.material_request.material_request import make_stock_entry from erpnext.stock.doctype.material_request.material_request import make_stock_entry
@@ -297,7 +309,12 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.per_ordered, 100) self.assertEquals(mr.per_ordered, 100)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0)
self._test_requested_qty(0.0, 0.0)
current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2)
# check if per complete is as expected for Stock Entry cancelled # check if per complete is as expected for Stock Entry cancelled
se.cancel() se.cancel()
@@ -306,7 +323,11 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_incorrect_mapping_of_stock_entry(self): def test_incorrect_mapping_of_stock_entry(self):
# submit material request of type Purchase # submit material request of type Purchase
@@ -348,5 +369,9 @@ class TestMaterialRequest(unittest.TestCase):
mr.company = "_Test Company 1" mr.company = "_Test Company 1"
self.assertRaises(InvalidWarehouseCompany, mr.insert) self.assertRaises(InvalidWarehouseCompany, mr.insert)
def _get_requested_qty(self, item_code, warehouse):
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
test_dependencies = ["Currency Exchange"] test_dependencies = ["Currency Exchange"]
test_records = frappe.get_test_records('Material Request') test_records = frappe.get_test_records('Material Request')

View File

@@ -158,6 +158,23 @@
"permlevel": 0, "permlevel": 0,
"read_only": 0 "read_only": 0
}, },
{
"fieldname": "letter_head_details",
"fieldtype": "Section Break",
"label": "Letter Head",
"permlevel": 0,
"precision": ""
},
{
"allow_on_submit": 1,
"fieldname": "letter_head",
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
"permlevel": 0,
"precision": "",
"print_hide": 1
},
{ {
"fieldname": "misc_details", "fieldname": "misc_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -180,7 +197,7 @@
"icon": "icon-suitcase", "icon": "icon-suitcase",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2014-05-27 03:49:14.251039", "modified": "2014-11-13 16:50:50.423299",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Packing Slip", "name": "Packing Slip",

View File

@@ -162,8 +162,7 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from `tabItem` return frappe.db.sql("""select name, item_name, description from `tabItem`
where name in ( select item_code FROM `tabDelivery Note Item` where name in ( select item_code FROM `tabDelivery Note Item`
where parent= %s where parent= %s)
and ifnull(qty, 0) > ifnull(packed_qty, 0))
and %s like "%s" %s and %s like "%s" %s
limit %s, %s """ % ("%s", searchfield, "%s", limit %s, %s """ % ("%s", searchfield, "%s",
get_match_cond(doctype), "%s", "%s"), get_match_cond(doctype), "%s", "%s"),

View File

@@ -8,7 +8,6 @@ from frappe.utils import cstr, flt, cint
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
from erpnext.stock.utils import update_bin
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
@@ -130,7 +129,7 @@ class PurchaseReceipt(BuyingController):
if not d.prevdoc_docname: if not d.prevdoc_docname:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
def update_stock(self): def update_stock_ledger(self):
sl_entries = [] sl_entries = []
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
@@ -157,29 +156,19 @@ class PurchaseReceipt(BuyingController):
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def update_ordered_qty(self): def update_ordered_qty(self):
stock_items = self.get_stock_items() po_map = {}
for d in self.get("purchase_receipt_details"): for d in self.get("purchase_receipt_details"):
if d.item_code in stock_items and d.warehouse \ if d.prevdoc_doctype and d.prevdoc_doctype == "Purchase Order" and d.prevdoc_detail_docname:
and cstr(d.prevdoc_doctype) == 'Purchase Order': po_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname)
already_received_qty = self.get_already_received_qty(d.prevdoc_docname, for po, po_item_rows in po_map.items():
d.prevdoc_detail_docname) if po and po_item_rows:
po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname) po_obj = frappe.get_doc("Purchase Order", po)
if not ordered_warehouse: if po_obj.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Warehouse is missing in Purchase Order")) frappe.throw(_("Material Request {0} is cancelled or stopped").format(po), frappe.InvalidStatusError)
if already_received_qty + d.qty > po_qty: po_obj.update_ordered_qty(po_item_rows)
ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor)
else:
ordered_qty = - flt(d.qty) * flt(d.conversion_factor)
update_bin({
"item_code": d.item_code,
"warehouse": ordered_warehouse,
"posting_date": self.posting_date,
"ordered_qty": flt(ordered_qty) if self.docstatus==1 else -flt(ordered_qty)
})
def get_already_received_qty(self, po, po_detail): def get_already_received_qty(self, po, po_detail):
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
@@ -234,7 +223,7 @@ class PurchaseReceipt(BuyingController):
self.update_ordered_qty() self.update_ordered_qty()
self.update_stock() self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "purchase_receipt_details") update_serial_nos_after_submit(self, "purchase_receipt_details")
@@ -265,11 +254,13 @@ class PurchaseReceipt(BuyingController):
frappe.db.set(self,'status','Cancelled') frappe.db.set(self,'status','Cancelled')
self.update_ordered_qty() self.update_stock_ledger()
self.update_stock()
self.update_prevdoc_status() self.update_prevdoc_status()
# Must be called after updating received qty in PO
self.update_ordered_qty()
pc_obj.update_last_purchase_rate(self, 0) pc_obj.update_last_purchase_rate(self, 0)
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()

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