Compare commits

...

215 commits

Author SHA1 Message Date
Ludovic Stephan
27d26245a5 Bugfix : deep extend in history.js 2018-10-05 22:59:55 +02:00
Ludovic Stephan
7e15fd2d9e Fix CheckoutReadView test 2018-07-18 16:37:41 +02:00
Ludovic Stephan
a0bd437250 Merge branch 'master' into aureplop/kpsul_js_refactor 2018-07-18 15:02:19 +02:00
Aurélien Delobelle
5ef219c88e kfet -- Update tests to reflect last changes + fix
- Remove duplicate categories returned by the view 'kpsul_articles_data'.
- Fix in account filtering of the view 'history_json'.

- Tests of the view 'kpsul_articles_data' are updated to reflect changes of the
  data format.
- The views returning Account and Checkout data as JSON no longer
  exists. The related tests are moved.
- The view canceling transfers has been merged with the one canceling
  the other operations. The related tests are moved.
2018-01-12 18:48:42 +01:00
Ludovic Stephan
f03ce35126 Merge branch 'master' into aureplop/kpsul_js_refactor 2018-01-10 19:09:41 +01:00
Ludovic Stephan
b62f0293dd Merge branch 'master' into aureplop/kpsul_js_refactor 2018-01-10 18:55:33 +01:00
Ludovic Stephan
c76b729320 Merge branch 'aureplop/ws_checkout' into 'aureplop/kpsul_js_refactor'
Add last statement update support.

See merge request !232
2017-05-19 21:50:29 +02:00
Aurélien Delobelle
914d2ca870 Add last statement update support.
K-Psul
- Add handler for checkout data (balance update)
- New statements are sent through ws, and display live on K-Psul.
2017-05-18 13:33:53 +02:00
Ludovic Stephan
ef1f00c15b Merge branch 'aureplop/fix_ws' into 'aureplop/kpsul_js_refactor'
Fix some ws updates on khistory

See merge request !230
2017-05-18 03:39:53 +02:00
Aurélien Delobelle
24b9aaae39 Revert template literals use
They were adding spaces at some places.
2017-05-16 18:47:26 +02:00
Aurélien Delobelle
cb0c0be8a2 filters on ws for khistory
- fix filters on ws khistory updates
- add filters for transfers against accounts
2017-05-16 18:08:40 +02:00
Aurélien Delobelle
02a015e633 fix data sent to ws by perform_transfers 2017-05-16 17:08:51 +02:00
Aurélien Delobelle
bacc079778 Merge branch 'master' into aureplop/kpsul_js_refactor 2017-05-16 16:48:12 +02:00
Aurélien Delobelle
bc71e1628a keep eslint quiet 2017-05-16 12:27:39 +02:00
Aurélien Delobelle
311e0c48bd Merge branch 'Aufinal/refactor_history' into 'aureplop/kpsul_js_refactor'
Aufinal/refactor history

See merge request !192
2017-05-16 10:48:07 +02:00
Ludovic Stephan
6a6fc38ead Add selection reset to cancel_opes 2017-05-15 21:10:39 -03:00
Ludovic Stephan
43e772363e Extend history options 2017-05-15 19:29:12 -03:00
Ludovic Stephan
93c8844b3f typo 2017-05-15 19:28:24 -03:00
Ludovic Stephan
c2da055b60 Remove duplicate ws updates 2017-05-15 18:59:18 -03:00
Ludovic Stephan
ac33e6302e Fix addExistingPurchase + few other bugs 2017-05-15 17:39:33 -03:00
Ludovic Stephan
ad42687293 Fix tranfers page 2017-05-15 15:27:46 -03:00
Ludovic Stephan
f4cb1e2e83 Add opesonly option 2017-05-15 14:17:58 -03:00
Ludovic Stephan
c12c705f8b Bind ForestDisplay to initial data 2017-05-14 20:18:31 -03:00
Ludovic Stephan
31b742fdb7 Move ws update to respective classes 2017-05-14 17:19:09 -03:00
Ludovic Stephan
8b8a3f8a25 Update button in history 2017-04-24 14:46:16 -03:00
Ludovic Stephan
16dbfed977 Add chidren div in display 2017-04-24 14:12:03 -03:00
Ludovic Stephan
5096e5f129 Adapt ArticleManager 2017-04-24 13:25:18 -03:00
Ludovic Stephan
46ac82fd27 Adapt history to changes 2017-04-24 13:25:07 -03:00
Ludovic Stephan
e283439ebc Create ForestDisplay class
- Store templates, container and data
- All display functions removed from ModelForest
2017-04-24 13:21:56 -03:00
Ludovic Stephan
20d635137c Merge remote-tracking branch 'origin/aureplop/kpsul_js_refactor' into Aufinal/refactor_history 2017-04-24 11:30:01 -03:00
Ludovic Stephan
b2a5dfd682 Move permission check 2017-04-14 12:51:58 -03:00
Ludovic Stephan
100686457b Change select_related for future compatibility 2017-04-14 12:47:15 -03:00
Ludovic Stephan
b544d6c5b3 Better alignment 2017-04-14 12:45:38 -03:00
Ludovic Stephan
659b20891e Add filter to cancel_opes 2017-04-14 12:43:59 -03:00
Ludovic Stephan
034a661444 Tweaks on kfet.js 2017-04-14 12:12:43 -03:00
Ludovic Stephan
de865c61aa Move container clearing 2017-04-14 11:36:52 -03:00
Aurélien Delobelle
b0d35667b3 Merge branch 'master' into aureplop/kpsul_js_refactor 2017-04-14 15:56:44 +02:00
Ludovic Stephan
8c02e5da0c Trigger event on history itself 2017-04-10 15:40:29 -03:00
Ludovic Stephan
2eba6892a2 Revert "Change event triggered when canceling opes"
This reverts commit ce3d8aa6f7.
2017-04-10 15:34:57 -03:00
Ludovic Stephan
ce3d8aa6f7 Change event triggered when canceling opes 2017-04-10 15:08:17 -03:00
Ludovic Stephan
84d478271b Compatibility changes on history.js 2017-04-10 12:34:43 -03:00
Ludovic Stephan
cd0e4c6f3e Allow (basic) chaining on api_with_auth 2017-04-10 12:33:24 -03:00
Ludovic Stephan
983a55780f Add cancel_history event 2017-04-10 12:32:35 -03:00
Ludovic Stephan
cfb39b1050 Better default options 2017-04-10 11:30:00 -03:00
Aurélien Delobelle
c75b2946e3 Merge branch 'master' into aureplop/kpsul_js_refactor 2017-04-10 13:08:23 +02:00
Ludovic Stephan
47da80f21c Add related objects 2017-04-09 23:24:50 -03:00
Ludovic Stephan
5e8752632c Add index for Day objects 2017-04-09 21:50:47 -03:00
Ludovic Stephan
688d5bba29 Adapt history to new structure 2017-04-09 17:54:37 -03:00
Ludovic Stephan
051231a031 Merge remote-tracking branch 'origin/aureplop/kpsul_js_refactor' into Aufinal/refactor_history 2017-04-09 16:47:05 -03:00
Aurélien Delobelle
7fc07ac603 Merge branch 'Aufinal/clean_modelforest' into 'aureplop/kpsul_js_refactor'
Change ModelForest inner structure
- Intermediary model for nodes removed
- More descriptive ModelForest structure in constructor.structure, child_sort not defined in Python function anymore.
- More intuitive access to parent and children of nodes (e.g. article.category, category.articles...)

See merge request !215
2017-04-09 21:28:16 +02:00
Ludovic Stephan
323f019c0d Check if children is non empty 2017-04-09 13:35:01 -03:00
Ludovic Stephan
9ad208a171 Change child sort + bugfix grom prev commit 2017-04-09 12:30:15 -03:00
Ludovic Stephan
73fb3c419e Add stop check in traverse 2017-04-09 11:43:51 -03:00
Ludovic Stephan
9ba13a81ee Adapt add_to_container + small improvements 2017-04-06 00:10:39 -03:00
Ludovic Stephan
23d19545a7 Add back root_sort 2017-04-05 22:23:56 -03:00
Ludovic Stephan
df47bedae1 Change ModelForest inner structure 2017-04-05 22:10:21 -03:00
Aurélien Delobelle
e4dd434608 no longer use model_to_dict
- fix cof status on k-psul
2017-04-05 23:33:45 +02:00
Aurélien Delobelle
e4ccd88dfd Merge branch 'master' into aureplop/kpsul_js_refactor 2017-04-05 23:18:33 +02:00
Ludovic Stephan
ed0a82ed5d Add no_trigramme option 2017-04-05 16:51:17 -03:00
Aurélien Delobelle
6a8f41849b Merge branch 'Aufinal/refactor_articles' into 'aureplop/kpsul_js_refactor'
Aufinal/refactor articles

See merge request !173
2017-04-05 17:56:07 +02:00
Ludovic Stephan
5c422e892a Add children fo traverse callback 2017-04-05 12:31:19 -03:00
Ludovic Stephan
f57c292184 Rename history var 2017-04-05 12:13:24 -03:00
Ludovic Stephan
508e7ec23f Change traverse and find behavior 2017-04-05 12:00:39 -03:00
Ludovic Stephan
88f7ea941d Move selection logic to another class 2017-04-05 11:26:33 -03:00
Ludovic Stephan
8d13c0a4bb Add fetch method 2017-04-05 10:59:59 -03:00
Ludovic Stephan
290d4ecb6e Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-04-05 10:03:30 -03:00
Ludovic Stephan
7ec7ed2696 Rename History class 2017-04-05 09:53:12 -03:00
Ludovic Stephan
e051631a34 Use WebSocket classes 2017-04-05 09:28:32 -03:00
Ludovic Stephan
360c442a4e Remove useless class 2017-04-05 09:24:27 -03:00
Ludovic Stephan
1761c5f1bd Change fromAPI logic 2017-04-05 09:13:00 -03:00
Ludovic Stephan
840010b63f Merge remote-tracking branch 'origin/aureplop/kpsul_js_refactor' into Aufinal/refactor_articles 2017-04-05 09:03:59 -03:00
Ludovic Stephan
a29de134f1 Move focus ; move is_low_stock to method 2017-04-05 08:58:46 -03:00
Aurélien Delobelle
2e0de75471 kpsul - fix account balance ukf 2017-04-05 13:18:01 +02:00
Aurélien Delobelle
6bb9280b0d Merge branch 'aureplop/clean_js' into 'aureplop/kpsul_js_refactor'
Enhance APIModelObject

See merge request !213
2017-04-05 04:30:34 +02:00
Aurélien Delobelle
6be6202b3f few cleans 2017-04-05 04:26:50 +02:00
Aurélien Delobelle
f1aaad7317 Better jquery ajax calls management
It becomes the same as the original jQuery ajax object.
For example, callbacks can be queued.
get_by_apipk and from_API of ModelObject returns the ajax object.

Example (js):
Account.get_by_apipk('AAA')
    .done(function (data) {
        console.log(data)
    })
    .fail( () => console.log('cool') )
    ...
    .done( ...
2017-04-05 04:05:31 +02:00
Aurélien Delobelle
efbcde163b clean some js
- clean buttons code on account and checkout
- merge CheckoutRead and kpsul_checkout_data views (the first won)

APIModelObject interface
- add url_create, url_update, url_update_for
- rename url_object_for and url_object to url_read_for and url_read
2017-04-05 03:43:51 +02:00
Ludovic Stephan
3b9affb3f3 Add focus methods 2017-04-04 20:18:53 -03:00
Ludovic Stephan
9c559d9ec3 Add articles reset to kpsul.reset 2017-04-04 19:54:03 -03:00
Ludovic Stephan
021937a38e Small bugfixes 2017-04-04 19:52:12 -03:00
Ludovic Stephan
cb28b928c4 Remove articleSelect from _env 2017-04-04 19:42:30 -03:00
Ludovic Stephan
e5791efe4d Remove last traces of old articles 2017-04-04 19:41:15 -03:00
Ludovic Stephan
05156f37c6 Update addExistingPurchase 2017-04-04 19:34:22 -03:00
Ludovic Stephan
514f1da6df Fix SpecialOpeFormatter 2017-04-03 21:26:33 -03:00
Ludovic Stephan
ec9f47274a Fix WS update functions 2017-04-03 21:24:07 -03:00
Ludovic Stephan
20b7156e1f Improve type display 2017-04-03 21:18:07 -03:00
Ludovic Stephan
a173be4f7d Use api_with_auth in history 2017-04-03 21:14:06 -03:00
Ludovic Stephan
07290f6fec Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-04-03 21:00:12 -03:00
Ludovic Stephan
3ce4dc5c85 Add article stock management 2017-04-03 20:14:45 -03:00
Ludovic Stephan
b91edc9c7d Merge remote-tracking branch 'origin/aureplop/kpsul_js_refactor' into Aufinal/refactor_articles 2017-04-03 20:03:58 -03:00
Aurélien Delobelle
9d2298a089 Merge branch 'master' into aureplop/kpsul_js_refactor
Not chill.
2017-04-04 00:51:49 +02:00
Aurélien Delobelle
dcda67aaf7 Merge branch 'Aufinal/dialog_utils' into 'aureplop/kpsul_js_refactor'
Utilitaires de dialogue

Ajoute deux type de dialogue avec l'utilisateur
- une classe UserDialog pour ouvrir un simple dialogue jconfirm
- une fonction api_with_auth pour gérer toutes les requêtes API pouvant 
nécessiter un mot de passe

See merge request !199
2017-04-03 23:25:55 +02:00
Ludovic Stephan
5020037103 api_lock inside kfet.js 2017-04-03 16:08:40 -03:00
Ludovic Stephan
4af2562121 More clarity in argument names 2017-04-01 09:34:02 -03:00
Ludovic Stephan
29836fd15c Remove deprecated option 2017-04-01 09:24:18 -03:00
Ludovic Stephan
2774dbb5de Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-03-31 23:51:13 -03:00
Ludovic Stephan
7d93d91af9 Merge remote-tracking branch 'origin/aureplop/kpsul_js_refactor' into Aufinal/refactor_articles 2017-03-31 23:50:33 -03:00
Ludovic Stephan
236dcb4644 Tweaks to UserDialog 2017-03-31 18:10:06 -03:00
Ludovic Stephan
582cdebaa1 Better callback management 2017-03-31 17:49:22 -03:00
Ludovic Stephan
08c752f1b3 Simplify addcost management 2017-03-26 18:10:26 -03:00
Ludovic Stephan
5101400f64 Use callback_as_dict for addcost 2017-03-26 17:57:51 -03:00
Ludovic Stephan
c1f70d9d0a Add capslock support for inputs 2017-03-26 17:51:55 -03:00
Ludovic Stephan
6d92df4155 Merge branch 'aureplop/kpsul_js_refactor' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/dialog_utils 2017-03-26 15:21:33 -03:00
Aurélien Delobelle
842f2cecc1 fix import, fix ope with addcost enabled, move Config location 2017-03-26 15:12:03 +02:00
Aurélien Delobelle
bc6ecda0c8 fix addcost kspul 2017-03-26 14:59:21 +02:00
Aurélien Delobelle
24f72ae7d4 add missing ; 2017-03-26 14:52:23 +02:00
Aurélien Delobelle
b81b33c056 allow chaining on container in display method 2017-03-26 14:51:16 +02:00
Aurélien Delobelle
485ae86a42 add update method to ModelObject 2017-03-26 14:46:46 +02:00
Ludovic Stephan
3bf7a066a2 Adapt history.html to new functions 2017-03-26 00:33:33 -03:00
Ludovic Stephan
3f07bf56fa Remove console log 2017-03-26 00:25:51 -03:00
Ludovic Stephan
dbcfc6df46 Remove duplicate kfet.js import 2017-03-26 00:21:17 -03:00
Ludovic Stephan
87943ea7b9 Adapt kpsul to new functions 2017-03-26 00:20:47 -03:00
Ludovic Stephan
6be2f086df Add generic functions for confirm dialogs 2017-03-26 00:20:28 -03:00
Ludovic Stephan
a1c976185c Fix transfer sort 2017-03-25 13:44:35 -03:00
Ludovic Stephan
e6735d44ba Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-03-25 12:42:26 -03:00
Ludovic Stephan
0997d85083 Merge branch 'aureplop/kpsul_js_refactor' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/refactor_history 2017-03-25 12:39:30 -03:00
Ludovic Stephan
5ff8f69bfa Merge branch 'aureplop/kpsul_js_refactor' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/refactor_articles 2017-03-25 12:35:04 -03:00
Aurélien Delobelle
6afbcb44a1 delete array comprehesion 2017-03-25 10:26:45 +01:00
Aurélien Delobelle
967748ded3 details link to read instead of update
- It was the old behaviour.
2017-03-25 09:58:20 +01:00
Aurélien Delobelle
72970c6be7 Fix api call on new transfers
- Use django-js-reverse
- Replace old url (deleted) with standard url
2017-03-25 09:44:55 +01:00
Ludovic Stephan
aa6a50a6e7 Simplify JS-Python interface for cancel_ops 2017-03-24 23:57:27 -03:00
Aurélien Delobelle
ac18cbd9d9 fix by on last statement 2017-03-24 22:10:34 +01:00
Aurélien Delobelle
0d02d47d33 move utils functions 2017-03-24 22:05:04 +01:00
Aurélien Delobelle
2e3bd5bd7a fix search box 2017-03-24 21:41:34 +01:00
Aurélien Delobelle
abce961d91 use django-js-reverse 2017-03-24 21:28:33 +01:00
Aurélien Delobelle
a9d1a6aae9 clean array iteration 2017-03-24 21:24:06 +01:00
Ludovic Stephan
1fcd53d780 Continue renaming node.type to node.modelname 2017-03-20 00:42:12 -03:00
Ludovic Stephan
fe965875f7 Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-03-20 00:33:30 -03:00
Ludovic Stephan
3465dd7045 Change node.type to node.modelname for clarity 2017-03-20 00:26:11 -03:00
Ludovic Stephan
c99e4f26d0 Move history initialisation as Config.reset callback 2017-03-19 04:59:55 +01:00
Ludovic Stephan
66c5a6953c Improve websocket filter for special history pages 2017-03-19 04:29:54 +01:00
Ludovic Stephan
7a00096170 Add support for account_read history 2017-03-18 22:48:50 -03:00
Ludovic Stephan
951932a6c8 Add support and websocket to transfers.html 2017-03-18 22:35:47 -03:00
Ludovic Stephan
fa64a68378 Add strict mode to history.html script 2017-03-18 22:15:09 -03:00
Ludovic Stephan
fc3e86aea6 Add websocket support to ArticleManager 2017-03-18 22:06:30 -03:00
Ludovic Stephan
b8a307b4a6 Add support for kfet/history page 2017-03-18 14:05:11 -03:00
Ludovic Stephan
1d532616b7 Fix bugs introduced by previous commit 2017-03-18 02:47:30 -03:00
Ludovic Stephan
9e905b0f8b Remove kpsul dependence from history (oops) 2017-03-18 02:40:34 -03:00
Ludovic Stephan
644b08973a Add websocket support for history 2017-03-18 02:32:27 -03:00
Ludovic Stephan
34bb680570 Add History equivalent functions 2017-03-18 02:31:52 -03:00
Ludovic Stephan
b655907bd4 Add history to KPsul manager 2017-03-18 02:29:02 -03:00
Ludovic Stephan
14b922634d Remove deprecated history functions 2017-03-18 02:28:01 -03:00
Ludovic Stephan
5c9c206f68 Adapt css to new canceled syntax 2017-03-18 02:25:44 -03:00
Ludovic Stephan
b0b1fdf936 Add jsdoc comments to history models 2017-03-17 21:00:58 -03:00
Ludovic Stephan
565a054323 Add support for low stock css 2017-03-17 17:30:22 -03:00
Ludovic Stephan
58c57c6f89 Add hierarchy of needed models, w/ formatters 2017-03-17 16:00:49 -03:00
Ludovic Stephan
47fe74fbb0 template specification 2017-03-17 15:59:52 -03:00
Ludovic Stephan
df0ea96b41 Adapt history_json return value to ModelForest standards 2017-03-17 15:59:21 -03:00
Ludovic Stephan
53f89f53e0 Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-03-17 12:34:48 -03:00
Ludovic Stephan
a7de396aa3 Better comparison control 2017-03-17 12:33:43 -03:00
Ludovic Stephan
11603cee69 Merge branch 'Aufinal/refactor_articles' into Aufinal/refactor_history 2017-03-17 00:19:45 -03:00
Ludovic Stephan
9ab2a11432 Finish adapting ArticleManager and Autocomplete 2017-03-16 22:26:59 -03:00
Ludovic Stephan
91f14deda1 last tweaks and doc 2017-03-16 22:26:30 -03:00
Ludovic Stephan
1c5ac561a3 Change article table into divs 2017-03-16 22:26:08 -03:00
Ludovic Stephan
f0a80561ed Add article display to Config callback 2017-03-16 22:24:57 -03:00
Ludovic Stephan
01295d464d Adapt ArticleAutocomplete to new format 2017-03-16 01:22:46 -03:00
Ludovic Stephan
2ce96bce1b Add traverse function to ModelTree 2017-03-16 01:21:50 -03:00
Ludovic Stephan
770c185bd0 Modify sort in ModelTree 2017-03-16 01:21:18 -03:00
Ludovic Stephan
3d76079439 Add correct syntax to category data 2017-03-16 01:20:06 -03:00
Ludovic Stephan
08d1521d81 Adapt ArticleManagerto new API 2017-03-15 22:40:06 -03:00
Ludovic Stephan
fe6823fc7b Adapt article_data return value to ModelTree standards 2017-03-15 22:39:30 -03:00
Ludovic Stephan
1570d9f494 Polish ModelForest class 2017-03-15 22:10:56 -03:00
Ludovic Stephan
0219d998ac model tree struct draft 2017-03-15 02:45:13 -03:00
Ludovic Stephan
66beeb5bd0 transfer history added 2017-03-11 01:41:21 -03:00
Ludovic Stephan
ab6b0d52f2 Merge branch 'Aufinal/transferts_historique' into Aufinal/refactor_history 2017-03-11 00:45:54 -03:00
Ludovic Stephan
ac2e773f9e opelist class 2017-03-10 22:09:23 -03:00
Ludovic Stephan
741bac880b dummy History test class 2017-03-10 22:09:02 -03:00
Ludovic Stephan
8eae3cee7f adapt history view 2017-03-10 22:08:44 -03:00
Ludovic Stephan
02485afd9b doc and compare function 2017-03-10 19:59:15 -03:00
Ludovic Stephan
eac6c42041 day, opegroup and ope model drafts 2017-03-10 19:57:36 -03:00
Ludovic Stephan
e1abff2242 last tweaks 2017-03-09 22:31:59 -03:00
Ludovic Stephan
8aa4fa2dce modify article API return 2017-03-09 09:21:07 -03:00
Ludovic Stephan
c9b7683238 articlelist and modellist finished 2017-03-09 09:20:53 -03:00
Ludovic Stephan
fe8e5d7e46 move and adapt manager and completion 2017-03-09 09:20:23 -03:00
Ludovic Stephan
a05a075962 apimodellist & articlelist 2017-03-08 10:47:51 -03:00
Ludovic Stephan
d5dfd5fa93 move ModelList def 2017-03-07 18:36:56 -03:00
Ludovic Stephan
c9cce5b125 remove temp file 2017-03-07 17:58:10 -03:00
Ludovic Stephan
2cc0e0cffe modellist suite et fin 2017-03-07 17:57:40 -03:00
Ludovic Stephan
db9c14f768 articlelist wip 2017-03-06 02:43:48 -03:00
Ludovic Stephan
643503269e add Article and Category models 2017-03-02 06:50:47 -03:00
Aurélien Delobelle
a9cb50b38d Better k-fet js and more
JavaScript
----------
- Basic classes that can be inherited to define a new class for a
  django model in javascript.
- Formatters classes are used to render properties and attributes of
  the instances of models classes.
- New classes to handle Account, Checkout, Statement models.
- Refactor K-Psul JS (part n/m).
- Better file organization.

Views
-----
- 'kpsul.checkout_data' is cleaner. Last statement is added to the JSON
  response with GET paramater 'last_statement'.
- 'account.read.json' is merged in account.read. JSON response is sent if
  GET parametter 'format' is set to 'json'.
- Fix PEP8 of concerned views.

New requirement: django-js-reverse
----------------------------------
Used to resolve the URLs defined in the project in JavaScript.
See https://github.com/ierror/django-js-reverse
2017-02-23 22:07:38 +01:00
Aurélien Delobelle
ee848d210e refactor account k-psul js - part 2/?
- les événements de AccountSearch sont enregistrés depuis ces classes (plus depuis le manager)
- ajout d'une classe AccountSelection s'occupant de la sélection d'un
  compte par l'utilisateur
- la méthode update de AccountManager peut maintenant prendre un
  trigramme et le set correctement, à défaut elle récupère le trigramme
  via AccountSelection
2017-02-15 14:09:20 +01:00
Aurélien Delobelle
fdcf4c3ab0 fix property location 2017-02-13 13:48:57 +01:00
Aurélien Delobelle
d9fc683525 update account_data[] to account_manager.account. 2017-02-13 00:41:41 +01:00
Aurélien Delobelle
890be9b343 refactor account k-psul js - part 1
- nouvelle classe - Account: stocke, sert et récupère les données
  associées à un compte
- nouvelle classe - AccountManager: interface pour le management de la
  partie Account de K-Psul
- nouvelle classe - AccountSearch: module de recherche d'un compte
- nouvelles classes - AccountFormatter, StandardAccountFormatter,
  LIQAccountFormatter: styles de formattage des données d'un compte

- désactive l'autocomplétion dans la recherche d'un compte
- fix #89: "Entrée" dans le champ de trigramme met le compte LIQ
2017-02-13 00:23:32 +01:00
Aurélien Delobelle
5c7a1d6874 Refactor JS Settings K-Psul
- change name: Settings -> Config
- provide interface `Config` to get/set parameters
- `Config` uses global object `window.config` to store key/value
- `Config` setters handle types
2017-02-12 13:26:02 +01:00
Aurélien Delobelle
8279bddf4e clean js k-psul
- K-Psul JavaScript uses strict-mode (when JS try to do better things,
  we should follow)
2017-02-12 06:10:17 +01:00
Ludovic Stephan
481409253b simpler string pluralizing 2017-02-11 21:44:32 -02:00
Ludovic Stephan
02735642f1 better pluralize 2017-02-11 21:41:09 -02:00
Ludovic Stephan
fc2de20ab8 use switch + move text 2017-02-11 21:38:07 -02:00
Ludovic Stephan
db94a8904c remove addition of new opegroups 2017-02-07 18:45:57 -02:00
Ludovic Stephan
b404c989ff pep8 2017-02-05 03:42:02 -02:00
Ludovic Stephan
1d5e693045 ws for transfers 2017-02-05 03:26:15 -02:00
Ludovic Stephan
1ea334341b ws update for history and transfer pages 2017-02-05 03:04:41 -02:00
Ludovic Stephan
f8aa67721c fix socket update 2017-02-05 02:19:20 -02:00
Ludovic Stephan
8895daff6a Merge branch 'Aufinal/transferts_historique' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/transferts_historique 2017-02-04 23:22:54 -02:00
Ludovic Stephan
f06a732da5 remove unnecessary function 2016-12-11 23:29:05 -02:00
Ludovic Stephan
e52c44580f pluralize function 2016-12-11 23:23:12 -02:00
Ludovic Stephan
ee54b36696 minor imprevements to history 2016-12-11 23:22:59 -02:00
Ludovic Stephan
a9e1cd01db add transfersonly option 2016-12-11 23:22:14 -02:00
Ludovic Stephan
3f35dc2c06 unite transfer history 2016-12-11 23:21:36 -02:00
Ludovic Stephan
1dbbad38b9 transfer cancellation html 2016-12-11 21:00:42 -02:00
Ludovic Stephan
66304359c0 unite cancel_ope and cancel_transfer 2016-12-11 16:22:55 -02:00
Ludovic Stephan
49bef61e53 filter transfers frop opes 2016-12-11 14:45:52 -02:00
Ludovic Stephan
2c2da60e54 send data for cancel 2016-12-10 23:52:26 -02:00
Ludovic Stephan
0b61a48c65 fix selection 2016-12-10 23:13:43 -02:00
Ludovic Stephan
85af7fe485 filter on id 2016-12-09 17:29:40 -02:00
Ludovic Stephan
b3b49d5768 Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/transferts_historique 2016-12-09 16:48:18 -02:00
Ludovic Stephan
b0a21119fa Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/transferts_historique 2016-12-09 01:25:40 -02:00
Ludovic Stephan
36edc334d4 add transfer information 2016-12-09 00:26:25 -02:00
Ludovic Stephan
ac0356386a add css for transfers 2016-12-09 00:26:07 -02:00
Ludovic Stephan
027bc2e94b Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/transferts_historique 2016-12-04 00:22:46 -02:00
Ludovic Stephan
f747c0c389 print transfers (BROKEN) 2016-12-02 00:17:03 -02:00
Ludovic Stephan
5a354c61a0 fetch transfers as well 2016-12-02 00:16:40 -02:00
Ludovic Stephan
971848cb1b database lookups 2016-12-01 01:29:28 -02:00
19 changed files with 3898 additions and 1843 deletions

View file

@ -81,6 +81,7 @@ INSTALLED_APPS = [
'kfet.open',
'channels',
'widget_tweaks',
'django_js_reverse',
'custommail',
'djconfig',
'wagtail.wagtailforms',

View file

@ -6,9 +6,11 @@ from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib import admin
from django.views.decorators.cache import cache_page
from django.views.generic.base import TemplateView
from django.contrib.auth import views as django_views
from django_cas_ng import views as django_cas_views
from django_js_reverse.views import urls_js
from wagtail.wagtailadmin import urls as wagtailadmin_urls
from wagtail.wagtailcore import urls as wagtail_urls
@ -91,6 +93,7 @@ urlpatterns = [
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente,
name="ml_bda_revente"),
url(r'^k-fet/', include('kfet.urls')),
url(r'^jsreverse/$', cache_page(3600)(urls_js), name='js_reverse'),
url(r'^cms/', include(wagtailadmin_urls)),
url(r'^documents/', include(wagtaildocs_urls)),
# djconfig

View file

@ -44,6 +44,11 @@
width:90px;
}
#history .opegroup .infos {
text-align:center;
width:145px;
}
#history .opegroup .valid_by {
padding-left:20px
}
@ -71,6 +76,10 @@
text-align:right;
}
#history .ope .glyphicon {
padding-left:15px;
}
#history .ope .infos2 {
padding-left:15px;
}
@ -88,11 +97,11 @@
color:#FFF;
}
#history .ope.canceled, #history .transfer.canceled {
#history [canceled="true"] {
color:#444;
}
#history .ope.canceled::before, #history.transfer.canceled::before {
#history [canceled="true"]::before {
position: absolute;
content: ' ';
width:100%;

View file

@ -49,10 +49,10 @@ input[type=number]::-webkit-outer-spin-button {
height:120px;
}
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account[data_balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data_balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data_balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data_balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account_form {
padding:0;
@ -90,7 +90,7 @@ input[type=number]::-webkit-outer-spin-button {
font-size:12px;
}
#account_data #account-balance {
#account_data #account-balance_ukf {
height:40px;
line-height:40px;
@ -102,6 +102,10 @@ input[type=number]::-webkit-outer-spin-button {
font-weight:bold;
}
#account-is_cof {
font-weight:bold;
}
#account .buttons {
position:absolute;
bottom:0;
@ -119,7 +123,7 @@ input[type=number]::-webkit-outer-spin-button {
font-size:14px;
line-height:24px;
}
#account_data #account-balance {
#account_data #account-balance_ukf {
font-size:50px;
line-height:60px;
height:60px;
@ -306,28 +310,40 @@ input[type=number]::-webkit-outer-spin-button {
#articles_data {
overflow:auto;
max-height:500px;
}
#articles_data table {
width: 100%;
}
#articles_data table tr.article {
#articles_data div.article {
height:25px;
font-size:14px;
}
#articles_data table tr.article td:first-child {
#articles_data .article[data_stock="low"] {
background:rgba(236,100,0,0.3);
}
#articles_data span {
height:25px;
line-height:25px;
display: inline-block;
}
#articles_data span.name {
padding-left:10px;
width:78%;
}
#articles_data table tr.article td + td {
padding-right:10px;
text-align:right;
#articles_data span.price {
width:8%;
}
#articles_data table tr.category {
#articles_data span.stock {
width:14%;
}
#articles_data div.category {
height:35px;
line-height:35px;
background-color:#c8102e;
font-family:"Roboto Slab";
font-size:16px;
@ -335,7 +351,7 @@ input[type=number]::-webkit-outer-spin-button {
color:#FFF;
}
#articles_data table tr.category>td:first-child {
#articles_data div.category>span:first-child {
padding-left:20px;
}

View file

@ -73,14 +73,19 @@
.jconfirm .capslock .glyphicon {
position: absolute;
display:none;
padding: 10px;
right: 0px;
top: 15px;
font-size: 30px;
display: none ;
margin-left: 60px !important;
}
.capslock_on .capslock .glyphicon{
display: inline-block !important;
}
.jconfirm .capslock input {
padding-right: 50px;
padding-left: 50px;

View file

@ -1,148 +1,235 @@
function KHistory(options={}) {
$.extend(this, KHistory.default_options, options);
var cancelHistory = new Event("cancel_done");
this.$container = $(this.container);
class KHistory {
this.reset = function() {
this.$container.html('');
};
static get default_options() {
return {
'templates': {
'purchase': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
'specialope': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
'opegroup': '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
'transfergroup': '<div class="opegroup"><span class="time"></span><span class="infos"></span><span class="valid_by"></span><span class="comment"></span></div>',
'day': '<div class="day"><span class="date"></span></div>',
'transfer': '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="glyphicon glyphicon-arrow-right"></span><span class="infos2"></span><span class="canceled"></span></div>',
},
this.addOpeGroup = function(opegroup) {
var $day = this._getOrCreateDay(opegroup['at']);
var $opegroup = this._opeGroupHtml(opegroup);
'api_options': {
from: moment().subtract(1, 'days').format('YYYY-MM-DD HH:mm:ss'),
},
$day.after($opegroup);
var trigramme = opegroup['on_acc_trigramme'];
var is_cof = opegroup['is_cof'];
for (var i=0; i<opegroup['opes'].length; i++) {
var $ope = this._opeHtml(opegroup['opes'][i], is_cof, trigramme);
$ope.data('opegroup', opegroup['id']);
$opegroup.after($ope);
}
};
}
this._opeHtml = function(ope, is_cof, trigramme) {
var $ope_html = $(this.template_ope);
var parsed_amount = parseFloat(ope['amount']);
var amount = amountDisplay(parsed_amount, is_cof, trigramme);
var infos1 = '', infos2 = '';
constructor(options) {
var all_options = $.extend(true, {}, this.constructor.default_options, options);
this.api_options = all_options.api_options;
if (ope['type'] == 'purchase') {
infos1 = ope['article_nb'];
infos2 = ope['article__name'];
} else {
infos1 = parsed_amount.toFixed(2)+'€';
switch (ope['type']) {
case 'initial':
infos2 = 'Initial';
break;
case 'withdraw':
infos2 = 'Retrait';
break;
case 'deposit':
infos2 = 'Charge';
break;
case 'edit':
infos2 = 'Édition';
break;
this._$container = $('#history');
this._$nb_opes = $('#nb_opes');
this.data = new OperationList();
if (!all_options.no_select)
this.selection = new KHistorySelection(this);
if (!all_options.static)
OperationWebSocket.add_handler(data => this.update_data(data));
var templates = all_options.templates;
if (all_options.no_trigramme)
templates['opegroup'] = '<div class="opegroup"><span class="time"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>';
this.display = new ForestDisplay(this._$container, templates, this.data);
this._init_events();
}
fetch(api_options) {
this.data.clear();
$.extend(this.api_options, api_options);
this.data.fromAPI(this.api_options)
.done( () => this.display_data() );
}
display_data() {
this.display.clear();
this.display.render(this.data);
var nb_opes = this._$container.find('.ope[canceled="false"]').length;
this._$nb_opes.text(nb_opes);
}
_init_events() {
var that = this;
$(document).on('keydown', function(e) {
if (e.keyCode == 46 && that.selection) {
//DEL key ; we delete the selected operations (if any)
var to_cancel = that.selection.get_selected();
if (to_cancel['opes'].length > 0 || to_cancel['transfers'].length > 0)
that.cancel_operations(to_cancel);
}
});
}
cancel_operations(to_cancel) {
var that = this;
var on_success = function() {
if (that.selection)
that.selection.reset();
$(that).trigger("cancel_done");
};
api_with_auth({
url: Urls['kfet.kpsul.cancel_operations'](),
data: to_cancel,
on_success: on_success,
});
}
add_node(data) {
var node = this.data.get_or_create(data.modelname, data.content, 0);
this.display.add(node);
}
update_node(modelname, id, update_data) {
var updated = this.data.update(modelname, id, update_data);
if (!updated)
return false;
this.display.update(updated);
return true;
}
is_valid(opegroup) {
var options = this.api_options;
if (options.from && dateUTCToParis(opegroup.content.at).isBefore(moment(options.from)))
return false;
if (options.to && dateUTCToParis(opegroup.content.at).isAfter(moment(options.to)))
return false;
var accounts_filter = options.accounts && options.accounts.length;
var checkouts_filter = options.checkouts && options.checkouts.length;
if (opegroup.modelname == 'opegroup') {
if (options.transfersonly)
return false;
if (accounts_filter && options.accounts.indexOf(opegroup.content.account_id) < 0)
return false;
if (checkouts_filter && options.checkouts.indexOf(opegroup.content.checkout_id) < 0)
return false;
} else if (opegroup.modelname == 'transfergroup') {
if (options.opesonly)
return false;
if (checkouts_filter)
return false;
if (accounts_filter) {
opegroup.content.children =
opegroup.content.children.filter( function(transfer) {
var is_from_in =
options.accounts.indexOf(transfer.content.from_acc_id) >= 0;
var is_to_in =
options.accounts.indexOf(transfer.content.to_acc_id) >= 0;
return is_from_in || is_to_in;
});
if (opegroup.content.children.length == 0)
return false;
}
}
$ope_html
.data('ope', ope['id'])
.find('.amount').text(amount).end()
.find('.infos1').text(infos1).end()
.find('.infos2').text(infos2).end();
return true;
}
var addcost_for = ope['addcost_for__trigramme'];
if (addcost_for) {
var addcost_amount = parseFloat(ope['addcost_amount']);
$ope_html.find('.addcost').text('('+amountDisplay(addcost_amount, is_cof)+'UKF pour '+addcost_for+')');
update_data(data) {
var opegroups = data['opegroups'];
var opes = data['opes'];
for (let ope of opes) {
if (ope['cancellation']) {
let update_data = {
'canceled_at': ope.canceled_at,
'canceled_by': ope.canceled_by,
};
if (ope.modelname === 'ope') {
this.update_node('purchase', ope.id, update_data)
|| this.update_node('specialope', ope.id, update_data);
} else if (ope.modelname === 'transfer') {
this.update_node('transfer', ope.id, update_data);
}
}
}
if (ope['canceled_at'])
this.cancelOpe(ope, $ope_html);
for (let opegroup of opegroups) {
if (opegroup['cancellation']) {
let update_data = { 'amount': opegroup.amount };
this.update_node('opegroup', opegroup.id, update_data);
}
return $ope_html;
if (opegroup['add'] && this.is_valid(opegroup)) {
this.add_node(opegroup);
}
}
var nb_opes = this._$container.find('.ope[canceled="false"]').length;
$('#nb_opes').text(nb_opes);
}
this.cancelOpe = function(ope, $ope = null) {
if (!$ope)
$ope = this.findOpe(ope['id']);
var cancel = 'Annulé';
var canceled_at = dateUTCToParis(ope['canceled_at']);
if (ope['canceled_by__trigramme'])
cancel += ' par '+ope['canceled_by__trigramme'];
cancel += ' le '+canceled_at.format('DD/MM/YY à HH:mm:ss');
$ope.addClass('canceled').find('.canceled').text(cancel);
}
this._opeGroupHtml = function(opegroup) {
var $opegroup_html = $(this.template_opegroup);
var at = dateUTCToParis(opegroup['at']).format('HH:mm:ss');
var trigramme = opegroup['on_acc__trigramme'];
var amount = amountDisplay(
parseFloat(opegroup['amount']), opegroup['is_cof'], trigramme);
var comment = opegroup['comment'] || '';
$opegroup_html
.data('opegroup', opegroup['id'])
.find('.time').text(at).end()
.find('.amount').text(amount).end()
.find('.comment').text(comment).end()
.find('.trigramme').text(trigramme).end();
if (!this.display_trigramme)
$opegroup_html.find('.trigramme').remove();
if (opegroup['valid_by__trigramme'])
$opegroup_html.find('.valid_by').text('Par '+opegroup['valid_by__trigramme']);
return $opegroup_html;
}
this._getOrCreateDay = function(date) {
var at = dateUTCToParis(date);
var at_ser = at.format('YYYY-MM-DD');
var $day = this.$container.find('.day').filter(function() {
return $(this).data('date') == at_ser
});
if ($day.length == 1)
return $day;
var $day = $(this.template_day).prependTo(this.$container);
return $day.data('date', at_ser).text(at.format('D MMMM'));
}
this.findOpeGroup = function(id) {
return this.$container.find('.opegroup').filter(function() {
return $(this).data('opegroup') == id
});
}
this.findOpe = function(id) {
return this.$container.find('.ope').filter(function() {
return $(this).data('ope') == id
});
}
this.cancelOpeGroup = function(opegroup) {
var $opegroup = this.findOpeGroup(opegroup['id']);
var trigramme = $opegroup.find('.trigramme').text();
var amount = amountDisplay(
parseFloat(opegroup['amount'], opegroup['is_cof'], trigramme));
$opegroup.find('.amount').text(amount);
}
}
KHistory.default_options = {
container: '#history',
template_day: '<div class="day"></div>',
template_opegroup: '<div class="opegroup"><span class="time"></span><span class="trigramme"></span><span class="amount"></span><span class="valid_by"></span><span class="comment"></span></div>',
template_ope: '<div class="ope"><span class="amount"></span><span class="infos1"></span><span class="infos2"></span><span class="addcost"></span><span class="canceled"></span></div>',
display_trigramme: true,
class KHistorySelection {
constructor(history) {
this._$container = history._$container;
this._init();
}
_init() {
this._$container.selectable({
filter: 'div.opegroup, div.ope',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
$(this).parent().find('.ope').addClass('ui-selected');
}
});
},
unselected: function(e, ui) {
$(ui.unselected).each(function() {
if ($(this).hasClass('opegroup')) {
$(this).parent().find('.ope').removeClass('ui-selected');
}
});
},
});
}
get_selected() {
var selected = {'transfers': [], 'opes': [],};
this._$container.find('.ope.ui-selected').each(function() {
var [type, id] = $(this).parent().attr('id').split('-');
if (type === 'transfer')
selected['transfers'].push(id);
else
selected['opes'].push(id);
});
return selected;
}
reset() {
this._$container.find('.ui-selected')
.removeClass('.ui-selected');
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,139 @@
/**
* @file Miscellaneous JS definitions for <tt>k-fet</tt> app.
* @copyright 2017 cof-geek
* @license MIT
*/
/**
* String method
* @memberof String
* @return {String} String formatted as trigramme
*/
String.prototype.formatTri = function() {
return this.toUpperCase().substr(0, 3);
}
/**
* String method
* @global
* @return {Boolean} true iff String follows trigramme pattern
*/
String.prototype.isValidTri = function() {
var pattern = /^[^a-z]{3}$/;
return pattern.test(this);
}
/**
* Checks if given argument is float ;
* if not, parses given argument to float value.
* @global
* @return {float}
*/
function floatCheck(v) {
if (typeof v === 'number')
return v;
return Number.parseFloat(v);
}
function intCheck(v) {
return Number.parseInt(v);
}
function floatCheck(v) {
if (typeof v === 'number')
return v;
return Number.parseFloat(v);
}
function booleanCheck(v) {
return v == true;
}
/**
* Short: Equivalent to python str format.
* Source: [MDN]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals}.
* @example
* var t1Closure = template`${0}${1}${0}!`;
* t1Closure('Y', 'A'); // "YAY!"
* @example
* var t2Closure = template`${0} ${'foo'}!`;
* t2Closure('Hello', {foo: 'World'}); // "Hello World!"
*/
function template(strings, ...keys) {
return (function(...values) {
var dict = values[values.length - 1] || {};
var result = [strings[0]];
keys.forEach(function(key, i) {
var value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
});
}
/**
* Get and store K-Psul config from API.
* <br><br>
*
* Config should be accessed statically only.
*/
class Config {
/**
* Get or create config object.
* @private
* @return {object} object - config keys/values
*/
static _get_or_create_config() {
if (window.config === undefined)
window.config = {};
return window.config;
}
/**
* Get config from API.
* @param {jQueryAjaxComplete} [callback] - A function to be called when
* the request finishes.
*/
static reset(callback) {
$.getJSON(Urls['kfet.kpsul.get_settings']())
.done(function(data) {
for (var key in data) {
Config.set(key, data[key]);
}
})
.always(callback);
}
/**
* Get value for key in config.
* @param {string} key
*/
static get(key) {
return this._get_or_create_config()[key];
}
/**
* Set value for key in config.
* @param {string} key
* @param {*} value
*/
static set(key, value) {
// API currently returns string for Decimal type
if (['addcost_amount', 'subvention_cof'].indexOf(key) > -1)
value = floatCheck(value);
this._get_or_create_config()[key] = value;
}
}
/*
* CSRF Token
*/
@ -19,6 +154,7 @@ $.ajaxSetup({
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
function add_csrf_form($form) {
@ -28,6 +164,29 @@ function add_csrf_form($form) {
}
/*
* Capslock management
*/
window.capslock = -1;
$(document).on('keypress', function(e) {
var s = String.fromCharCode(e.which);
if ((s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey)|| //caps on, shift off
(s.toUpperCase() !== s && s.toLowerCase() === s && e.shiftKey)) { //caps on, shift on
$('body').addClass('capslock_on')
} else if ((s.toLowerCase() === s && s.toUpperCase() !== s && !e.shiftKey)|| //caps off, shift off
(s.toLowerCase() !== s && s.toUpperCase() === s && e.shiftKey)) { //caps off, shift on
$('body').removeClass('capslock_on')
}
});
$(document).on('keydown', function(e) {
if (e.which == 20) {
$('body').toggleClass('capslock_on')
}
});
/*
* Generic Websocket class and k-psul ws instanciation
*/
@ -92,7 +251,7 @@ function amountDisplay(amount, is_cof=false, tri='') {
function amountToUKF(amount, is_cof=false, account=false) {
var rounding = account ? Math.floor : Math.round ;
var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1;
var coef_cof = is_cof ? 1 + Config.get('subvention_cof') / 100 : 1;
return rounding(amount * coef_cof * 10);
}
@ -101,6 +260,58 @@ function isValidTrigramme(trigramme) {
return trigramme.match(pattern);
}
/**
* Dialogs with user via jconfirm
*/
class UserDialog {
static get defaults() {
return {'title': '', 'content': ''};
}
constructor(data) {
$.extend(this, this.constructor.defaults, data);
}
open(settings) {
// Arg management
var pre_content = settings.pre_content || '';
var post_content = settings.post_content || '';
var callback = settings.callback || $.noop;
var that = this;
$.confirm({
title: this.title,
content: pre_content + this.content + post_content,
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirm: function() {
var inputs = {};
this.$content.find('input').each(function () {
inputs[$(this).attr('name')] = $(this).val();
});
if (Object.keys(inputs).length > 1)
return callback(inputs);
else
return callback(inputs[Object.keys(inputs)[0]]);
},
onOpen: function() {
var that = this
this.$content.find('input').on('keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
that.$confirmButton.click();
}
});
},
onClose: function() { if (settings.next_focus) { this._lastFocused = settings.next_focus; } }
});
}
}
function getErrorsHtml(data) {
var content = '';
if (!data)
@ -148,59 +359,77 @@ function getErrorsHtml(data) {
return content;
}
function requestAuth(data, callback, focus_next = null) {
var content = getErrorsHtml(data);
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
$.confirm({
title: 'Authentification requise',
content: content,
function displayErrors(html) {
$.alert({
title: 'Erreurs',
content: html,
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
confirm: function() {
var password = this.$content.find('input').val();
callback(password);
},
onOpen: function() {
var that = this;
var capslock = -1 ; // 1 -> caps on ; 0 -> caps off ; -1 or 2 -> unknown
this.$content.find('input').on('keypress', function(e) {
if (e.keyCode == 13)
that.$confirmButton.click();
var s = String.fromCharCode(e.which);
if ((s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey)|| //caps on, shift off
(s.toUpperCase() !== s && s.toLowerCase() === s && e.shiftKey)) { //caps on, shift on
capslock = 1 ;
} else if ((s.toLowerCase() === s && s.toUpperCase() !== s && !e.shiftKey)|| //caps off, shift off
(s.toLowerCase() !== s && s.toUpperCase() === s && e.shiftKey)) { //caps off, shift on
capslock = 0 ;
}
if (capslock == 1)
$('.capslock .glyphicon').show() ;
else if (capslock == 0)
$('.capslock .glyphicon').hide() ;
});
// Capslock key is not detected by keypress
this.$content.find('input').on('keydown', function(e) {
if (e.which == 20) {
capslock = 1-capslock ;
}
if (capslock == 1)
$('.capslock .glyphicon').show() ;
else if (capslock == 0)
$('.capslock .glyphicon').hide() ;
});
},
onClose: function() {
if (focus_next)
this._lastFocused = focus_next;
}
});
}
var authDialog = new UserDialog({
'title': 'Authentification requise',
'content': '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
});
//Note/TODO: the returned ajax object can be improved by allowing chaining on errors 403/400
function api_with_auth(settings, password) {
if (window.api_lock == 1)
return false;
window.api_lock = 1;
var url = settings.url;
if (!url)
return false;
var data = settings.data || {} ;
var on_success = settings.on_success || $.noop ;
var on_400 = settings.on_400 || $.noop ;
return $.ajax({
dataType: "json",
url: url,
method: "POST",
data: data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password)
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
on_success(data);
})
.fail(function($xhr) {
var response = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
authDialog.open({
callback: function(password) {
api_with_auth(settings, password)
},
pre_content: getErrorsHtml(response),
next_focus: settings.next_focus,
});
break;
case 400:
on_400(response);
break;
}
})
.always(function() {
window.api_lock = 0;
});
}
String.prototype.pluralize = function(count, irreg_plural) {
if (Math.abs(count) >= 2)
return irreg_plural ? irreg_plural : this+'s' ;
return this ;
}
/**
* Setup jquery-confirm

View file

@ -0,0 +1,678 @@
/**
* @file K-Psul JS
* @copyright 2017 cof-geek
* @license MIT
*/
class KPsulManager {
constructor(env) {
this._env = env;
this.account_manager = new AccountManager(this);
this.checkout_manager = new CheckoutManager(this);
this.article_manager = new ArticleManager(this);
this.history = new KHistory({
api_options: {'opesonly': true},
});
this._init_events();
}
reset(soft) {
soft = soft || false;
this.account_manager.reset();
this.article_manager.reset();
if (!soft) {
this.checkout_manager.reset();
this.article_manager.reset_data();
this.history.fetch();
}
return this;
}
focus() {
if (this.checkout_manager.is_empty())
this.checkout_manager.focus();
else if (this.account_manager.is_empty())
this.account_manager.focus();
else
this.article_manager.focus();
return this;
}
_init_events() {
$(this.history).on("cancel_done", () => this.reset(true).focus());
}
}
class AccountManager {
constructor() {
this._$container = $('#account');
this.account = new Account();
this.selection = new AccountSelection(this);
this.search = new AccountSearch(this);
// buttons: search, read or create
this._$buttons_container = this._$container.find('.buttons');
this._buttons_templates = {
create: template`<a href="${'url'}" class="btn btn-primary" target="_blank" title="Créer ce compte"><span class="glyphicon glyphicon-plus"></span></a>`,
read: template`<a href="${'url'}" class="btn btn-primary" target="_blank" title="Détails du compte"><span class="glyphicon glyphicon-info-sign"></span></a>`,
search: template`<button class="btn btn-primary search" title="Rechercher"><span class="glyphicon glyphicon-search"></span></button>`,
};
}
is_empty() { return this.account.is_empty(); }
display() {
this._display_data();
this._display_buttons();
}
_display_data() {
this.account.display(this._$container, {
'prefix_prop': '#account-',
});
}
_display_buttons() {
var buttons;
if (this.is_empty()) {
var trigramme = this.selection.get();
if (trigramme.isValidTri()) {
let url = Account.url_create(trigramme);
buttons = this._buttons_templates['create']({url: url});
} else { /* trigramme input is empty or invalid */
buttons = this._buttons_templates['search']();
}
} else { /* an account is loaded */
let url = this.account.url_read;
buttons = this._buttons_templates['read']({url: url});
}
this._$buttons_container.html(buttons);
}
update(trigramme) {
if (trigramme !== undefined)
this.selection.set(trigramme);
trigramme = trigramme || this.selection.get();
if (trigramme.isValidTri()) {
this.account.get_by_apipk(trigramme)
.done( () => this._update_on_success() )
.fail( () => this.reset_data() );
} else {
this.reset_data();
}
}
_update_on_success() {
$('#id_on_acc').val(this.account.id);
this.display();
kpsul.focus();
kpsul._env.updateBasketAmount();
kpsul._env.updateBasketRel();
}
reset() {
$('#id_on_acc').val(0);
this.selection.reset();
this.search.reset();
this.reset_data();
}
reset_data() {
this.account.clear();
this.display();
}
focus() {
this.selection.focus();
return this;
}
}
class AccountSelection {
constructor(manager) {
this.manager = manager;
this._$input = $('#id_trigramme');
this._init_events();
}
_init_events() {
var that = this;
// user change trigramme
this._$input
.on('input', () => this.manager.update());
// LIQ shortcuts
this._$input
.on('keydown', function(e) {
// keys: 13:Enter|40:Arrow-Down
if (e.keyCode == 13 || e.keyCode == 40)
that.manager.update('LIQ');
});
}
get() {
return this._$input.val().formatTri();
}
set(v) {
this._$input.val(v);
}
focus() {
this._$input.focus();
}
reset() {
this.set('');
}
}
class AccountSearch {
constructor(manager) {
this.manager = manager;
this._content = '<input type="text" name="q" id="search_autocomplete" autocomplete="off" spellcheck="false" autofocus><div id="account_results"></div>';
this._input = '#search_autocomplete';
this._results_container = '#account_results';
this._init_outer_events();
}
open() {
var that = this;
this._$dialog = $.dialog({
title: 'Recherche de compte',
content: this._content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
onOpen: function() {
that._$input = $(that._input);
that._$results_container = $(that._results_container);
that._init_form()
._init_inner_events();
},
});
}
_init_form() {
var that = this;
this._$input.yourlabsAutocomplete({
url: Urls['kfet.account.search.autocomplete'](),
minimumCharacters: 2,
id: 'search_autocomplete',
choiceSelector: '.choice',
placeholder: "Chercher un utilisateur K-Fêt",
container: that._$results_container,
box: that._$results_container,
fixPosition: function() {},
});
return this;
}
_init_outer_events() {
var that = this;
/* open search on button click */
this.manager._$container
.on('click', '.search', () => this.open());
/* open search on Ctrl-F */
this.manager._$container
.on('keydown', function(e) {
if (e.which == 70 && e.ctrlKey) {
that.open();
e.preventDefault();
}
});
}
_init_inner_events() {
this._$input.bind('selectChoice',
(e, choice, autocomplete) => this._on_select(e, choice, autocomplete)
);
return this;
}
_on_select(e, choice, autocomplete) {
this.manager.update(choice.find('.trigramme').text());
this.reset();
}
reset() {
if (this._$dialog !== undefined) {
this._$dialog.close();
}
}
}
class CheckoutManager {
constructor() {
this._$container = $('#checkout');
this.display_prefix = '#checkout-';
this.checkout = new Checkout();
this.selection = new CheckoutSelection(this);
this._$laststatement_container = $('#last_statement');
this.laststatement = new Statement();
this.laststatement_display_prefix = '#checkout-last_statement_';
this._$buttons_container = this._$container.find('.buttons');
this._buttons_templates = {
read: template`<a class="btn btn-primary" href="${'url'}" title="En savoir plus" target="_blank"><span class="glyphicon glyphicon-info-sign"></span></a>`,
statement_create: template`<a href="${'url'}" title="Effectuer un relevé" class="btn btn-primary" target="_blank"><span class="glyphicon glyphicon-euro"></span></a>`,
};
OperationWebSocket.add_handler(data => this.update_data(data));
}
update(id) {
if (id !== undefined)
this.selection.set(id);
id = id || this.selection.get();
var api_options = {
'last_statement': true,
};
this.checkout.get_by_apipk(id, api_options)
.done( (data) => this._update_on_success(data) )
.fail( () => this.reset_data() );
kpsul.focus();
}
_update_on_success(data) {
if (data['laststatement'] !== undefined)
this.laststatement.from(data['laststatement']);
$('#id_checkout').val(this.checkout.id);
this.display();
}
update_data(ws_data) {
let data = ws_data["checkouts"].find(o => o.id === this.checkout.id);
if (!data)
return;
this.checkout.update(data);
this._update_on_success(data);
}
is_empty() { return this.checkout.is_empty(); }
display() {
this._display_data();
this._display_laststatement();
this._display_buttons();
}
_display_data() {
this.checkout.display(this._$container, {
'prefix_prop': this.display_prefix,
});
}
_display_laststatement() {
if (this.laststatement.is_empty()) {
this._$laststatement_container.hide();
} else {
this.laststatement.display(this._$laststatement_container, {
'prefix_prop': this.laststatement_display_prefix
});
this._$laststatement_container.show();
}
}
_display_buttons() {
var buttons = '';
if (!this.is_empty()) {
var url_newcheckout = Statement.url_create(this.checkout.id);
buttons += this._buttons_templates['statement_create']({
url: url_newcheckout});
var url_read = this.checkout.url_read;
buttons += this._buttons_templates['read']({url: url_read});
}
this._$buttons_container.html(buttons);
}
reset() {
$('#id_checkout').val(0);
this.selection.reset();
this.reset_data();
if (this.selection.choices.length == 1)
this.update(this.selection.choices[0]);
}
reset_data() {
this.checkout.clear();
this.laststatement.clear();
this.display();
}
focus() {
this.selection.focus();
return this;
}
}
class CheckoutSelection {
constructor(manager) {
this.manager = manager;
this._$input = $('#id_checkout_select');
this._init_events();
this.choices =
this._$input.find('option[value!=""]')
.toArray()
.map(function(opt) {
return parseInt($(opt).attr('value'));
});
}
_init_events() {
this._$input.on('change', () => this.manager.update());
}
get() {
return this._$input.val() || 0;
}
set(v) {
this._$input.find('option[value='+ v +']').prop('selected', true);
}
reset() {
this._$input.find('option:first').prop('selected', true);
}
focus() {
this._$input.focus();
return this;
}
}
class ArticleManager {
constructor(env) {
this._env = env; // Global K-Psul Manager
this._$container = $('#articles_data');
this._$input = $('#article_autocomplete');
this._$nb = $('#article_number');
this._$stock = $('#article_stock');
this.selected = new Article();
this.data = new ArticleList();
var $container = $('#articles_data');
var templates = {
category: '<div class="category"><span class="name"></span></div>',
article: '<div class="article"><span class="name"></span><span class="price"></span><span class="stock"></span></div>',
};
this.display = new ForestDisplay($container, templates, this.data);
this.autocomplete = new ArticleAutocomplete(this, $container);
this._init_events();
OperationWebSocket.add_handler(data => this.update_data(data));
}
get nb() {
return this._$nb.val();
}
display_list() {
this.display.render(this.data);
}
validate(article) {
this.selected.from(article);
this._$input.val(article.name);
this._$nb.val('1');
this._$stock.text('/'+article.stock);
this._$nb.focus().select();
}
unset() {
this.selected.clear();
}
is_empty() {
return this.selected.is_empty();
}
reset_data() {
this.display.clear();
this.data.clear();
this.data.fromAPI()
.done( () => this.display_list() );
}
update_data(data) {
for (let article_dict of data.articles) {
var updated = this.data.update('article', article_dict.id, article_dict);
if (updated) {
this.display.update(updated);
}
}
}
reset() {
this.unset();
this._$stock.text('');
this._$nb.val('');
this._$input.val('');
this.autocomplete.showAll();
}
_init_events() {
var that = this;
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
var arrowKeys = /^(37|38|39|40)$/;
//Global input event (to merge ?)
this._$input.on('keydown', function(e) {
if (e.keyCode == 13 && that._$input.val() == '') {
kpsul._env.performOperations();
}
});
this._$container.on('click', '.article', function() {
var id = $(this).parent().attr('id').split('-')[1];
var article = that.data.find('article', id);
if (article)
that.validate(article);
});
this._$nb.on('keydown', function(e) {
if (e.keyCode == 13 && that.constructor.check_nb(that.nb) && !that.is_empty()) {
kpsul._env.addPurchase(that.selected, that.nb);
that.reset();
that.focus();
}
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.ctrlKey) {
if (e.ctrlKey && e.charCode == 65)
that._$nb.val('');
return true;
}
if (that.constructor.check_nb(that.nb+e.key))
return true;
return false;
});
}
//Note : this function may not be needed after the whole rework
get_article(id) {
return this.data.find('article', id);
}
focus() {
if (this.is_empty())
this._$input.focus();
else
this._$nb.focus();
return this;
}
static check_nb(nb) {
return /^[0-9]+$/.test(nb) && nb > 0 && nb <= 24;
}
}
class ArticleAutocomplete {
constructor(article_manager, $container) {
this.manager = article_manager;
this._$container = $container;
this._$input = $('#article_autocomplete');
this.showAll();
this._init_events();
}
_init_events() {
var that = this;
// 8:Backspace|9:Tab|13:Enter|46:DEL|112-117:F1-6|119-123:F8-F12
var normalKeys = /^(8|9|13|46|112|113|114|115|116|117|119|120|121|122|123)$/;
var arrowKeys = /^(37|38|39|40)$/;
this._$input
.on('keydown', function(e) {
var text = that._$input.val();
if (normalKeys.test(e.keyCode) || arrowKeys.test(e.keyCode) || e.ctrlKey) {
// For the backspace key, we suppose the cursor is at the very end
if(e.keyCode == 8) {
that.update(text.substring(0, text.length-1), true);
}
return true;
}
that.update(text+e.key, false);
return false;
});
}
update(prefix, backspace) {
this.resetMatch();
var article_list = this.manager.data;
var lower = prefix.toLowerCase();
var that = this;
article_list.traverse('article', function(article) {
if (article.name.toLowerCase().startsWith(lower))
that.matching.push(article);
});
if (this.matching.length == 1) {
if (!backspace) {
this.manager.validate(this.matching[0]);
this.showAll();
} else {
this.manager.unset();
this.updateDisplay();
}
} else if (this.matching.length > 1) {
this.manager.unset();
this.updateDisplay();
if (!backspace)
this.updatePrefix();
}
}
updateDisplay() {
var that = this;
this.manager.data.traverse('category', function(category) {
var is_active = false;
for (let article of category.articles) {
if (that.matching.indexOf(article) != -1) {
is_active = true;
that._$container.find('#article-'+article.id).show();
} else {
that._$container.find('#article-'+article.id).hide();
}
}
if (is_active) {
that._$container.find('#category-'+category.id).show();
} else {
that._$container.find('#category-'+category.id).hide();
}
});
}
updatePrefix() {
var lower = this.matching.map(function (article) {
return article.name.toLowerCase();
});
lower.sort();
var first = lower[0], last = lower[lower.length-1],
length = first.length, i = 0;
while (i < length && first.charAt(i) === last.charAt(i)) i++;
this._$input.val(first.substring(0,i));
}
showAll() {
var that = this;
this.resetMatch();
this.manager.data.traverse('article', function(article) {
that.matching.push(article);
});
this.updateDisplay();
}
resetMatch() {
this.matching = [];
}
}

View file

@ -4,6 +4,7 @@
{% load l10n %}
{% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
{% if account.user == request.user %}
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
@ -85,33 +86,11 @@ $(document).ready(function() {
<script type="text/javascript">
$(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
'use strict';
khistory = new KHistory({
display_trigramme: false,
});
var khistory = new KHistory({'no_trigramme': true});
function getHistory() {
var data = {
'accounts': [{{ account.pk }}],
}
$.ajax({
dataType: "json",
url : "{% url 'kfet.history.json' %}",
method : "POST",
data : data,
})
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
khistory.addOpeGroup(data['opegroups'][i]);
}
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes);
});
}
getHistory();
Config.reset(() => khistory.fetch({'accounts': [{{account.pk}}]}));
});
</script>

View file

@ -30,6 +30,7 @@
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
{% include "kfetopen/init.html" %}

View file

@ -3,6 +3,7 @@
{% block extra_head %}
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
{{ filter_form.media }}
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
@ -32,14 +33,14 @@
{% block main %}
<table id="history" class="table">
</table>
<div id="history">
</div>
<script type="text/javascript">
$(document).ready(function() {
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
'use strict';
khistory = new KHistory();
var khistory = new KHistory();
var $from_date = $('#id_from_date');
var $to_date = $('#id_to_date');
@ -54,7 +55,9 @@ $(document).ready(function() {
return selected;
}
function getHistory() {
function updateHistory() {
// Get API options
var data = {};
if ($from_date.val())
data['from'] = moment($from_date.val()).format('YYYY-MM-DD HH:mm:ss');
@ -64,21 +67,11 @@ $(document).ready(function() {
if ($checkouts)
data['checkouts'] = checkouts;
var accounts = getSelectedMultiple($accounts);
data['accounts'] = accounts;
if (accounts)
data['accounts'] = accounts.map(id => parseInt(id));
$.ajax({
dataType: "json",
url : "{% url 'kfet.history.json' %}",
method : "POST",
data : data,
})
.done(function(data) {
for (var i=0; i<data['opegroups'].length; i++) {
khistory.addOpeGroup(data['opegroups'][i]);
}
var nb_opes = khistory.$container.find('.ope:not(.canceled)').length;
$('#nb_opes').text(nb_opes);
});
// Update history
khistory.fetch(data);
}
let defaults_datetimepicker = {
@ -92,8 +85,9 @@ $(document).ready(function() {
$from_date.datetimepicker($.extend({}, defaults_datetimepicker, {
defaultDate: moment().subtract(24, 'hours'),
}));
$to_date.datetimepicker($.extend({}, defaults_datetimepicker, {
defaultDate: moment(),
defaultDate: moment().add(5, 'minutes') // workaround for 'stepping' rounding
}));
$("#from_date").on("dp.change", function (e) {
@ -111,131 +105,11 @@ $(document).ready(function() {
countSelected: "# sur %"
});
$("input").on('dp.change change', function() {
khistory.reset();
getHistory();
$("#update_history").on('click', function() {
updateHistory();
});
khistory.$container.selectable({
filter: 'div.opegroup, div.ope',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('opegroup')) {
var opegroup = $(this).data('opegroup');
$(this).siblings('.ope').filter(function() {
return $(this).data('opegroup') == opegroup
}).addClass('ui-selected');
}
});
},
});
$(document).on('keydown', function (e) {
if (e.keyCode == 46) {
// DEL (Suppr)
var opes_to_cancel = [];
khistory.$container.find('.ope.ui-selected').each(function () {
opes_to_cancel.push($(this).data('ope'));
});
if (opes_to_cancel.length > 0)
confirmCancel(opes_to_cancel);
}
});
function confirmCancel(opes_to_cancel) {
var nb = opes_to_cancel.length;
var content = nb+" opérations vont être annulées";
$.confirm({
title: 'Confirmation',
content: content,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
confirm: function() {
cancelOperations(opes_to_cancel);
}
});
}
function requestAuth(data, callback) {
var content = getErrorsHtml(data);
content += '<input type="password" name="password" autofocus>',
$.confirm({
title: 'Authentification requise',
content: content,
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirm: function() {
var password = this.$content.find('input').val();
callback(password);
},
onOpen: function() {
var that = this;
this.$content.find('input').on('keypress', function(e) {
if (e.keyCode == 13)
that.$confirmButton.click();
});
},
});
}
function getErrorsHtml(data) {
var content = '';
if ('missing_perms' in data['errors']) {
content += 'Permissions manquantes';
content += '<ul>';
for (var i=0; i<data['errors']['missing_perms'].length; i++)
content += '<li>'+data['errors']['missing_perms'][i]+'</li>';
content += '</ul>';
}
if ('negative' in data['errors']) {
var url_base = "{% url 'kfet.account.update' LIQ}";
url_base = base_url(0, url_base.length-8);
for (var i=0; i<data['errors']['negative'].length; i++) {
content += '<a class="btn btn-primary" href="'+url_base+data['errors']['negative'][i]+'/edit" target="_blank">Autorisation de négatif requise pour '+data['errors']['negative'][i]+'</a>';
}
}
return content;
}
function cancelOperations(opes_array, password = '') {
var data = { 'operations' : opes_array }
$.ajax({
dataType: "json",
url : "{% url 'kfet.kpsul.cancel_operations' %}",
method : "POST",
data : data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
khistory.$container.find('.ui-selected').removeClass('ui-selected');
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelOperations(opes_array, password);
});
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
});
}
getHistory();
Config.reset(updateHistory);
});
</script>

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,22 @@
{% extends 'kfet/base_col_2.html' %}
{% load staticfiles %}
{% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/js/kfet.api.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
{% endblock %}
{% block title %}Transferts{% endblock %}
{% block header-title %}Transferts{% endblock %}
{% block fixed %}
<aside>
<div class="heading">
<div id="nb_opes"></div>
<div class="sub">transferts</div>
</div>
</aside>
<div class="buttons">
<a class="btn btn-primary" href="{% url 'kfet.transfers.create' %}">
Nouveaux
@ -16,109 +27,17 @@
{% block main %}
<div id="history">
{% for transfergroup in transfergroups %}
<div class="opegroup transfergroup" data-transfergroup="{{ transfergroup.pk }}">
<span>{{ transfergroup.at }}</span>
<span>{{ transfergroup.valid_by.trigramme }}</span>
<span>{{ transfergroup.comment }}</span>
</div>
{% for transfer in transfergroup.transfers.all %}
<div class="ope transfer{% if transfer.canceled_at %} canceled{% endif %}" data-transfer="{{ transfer.pk }}" data-transfergroup="{{ transfergroup.pk }}">
<span class="amount">{{ transfer.amount }} €</span>
<span class="from_acc">{{ transfer.from_acc.trigramme }}</span>
<span class="glyphicon glyphicon-arrow-right"></span>
<span class="to_acc">{{ transfer.to_acc.trigramme }}</span>
</div>
{% endfor %}
{% endfor %}
<div id="history" class="table">
</div>
<script type="text/javascript">
$(document).ready(function() {
'use strict';
lock = 0;
function displayErrors(html) {
$.alert({
title: 'Erreurs',
content: html,
backgroundDismiss: true,
animation: 'top',
closeAnimation: 'bottom',
keyboardEnabled: true,
});
}
function cancelTransfers(transfers_array, password = '') {
if (lock == 1)
return false
lock = 1;
var data = { 'transfers' : transfers_array }
$.ajax({
dataType: "json",
url : "{% url 'kfet.transfers.cancel' %}",
method : "POST",
data : data,
beforeSend: function ($xhr) {
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
if (password != '')
$xhr.setRequestHeader("KFetPassword", password);
},
})
.done(function(data) {
for (var i=0; i<data['canceled'].length; i++) {
$('#history').find('.transfer[data-transfer='+data['canceled'][i]+']')
.addClass('canceled');
}
$('#history').find('.ui-selected').removeClass('ui-selected');
lock = 0;
})
.fail(function($xhr) {
var data = $xhr.responseJSON;
switch ($xhr.status) {
case 403:
requestAuth(data, function(password) {
cancelTransfers(transfers_array, password);
});
break;
case 400:
displayErrors(getErrorsHtml(data));
break;
}
lock = 0;
});
}
$('#history').selectable({
filter: 'div.transfergroup, div.transfer',
selected: function(e, ui) {
$(ui.selected).each(function() {
if ($(this).hasClass('transfergroup')) {
var transfergroup = $(this).attr('data-transfergroup');
$(this).siblings('.ope').filter(function() {
return $(this).attr('data-transfergroup') == transfergroup
}).addClass('ui-selected');
}
});
},
});
$(document).on('keydown', function (e) {
if (e.keyCode == 46) {
// DEL (Suppr)
var transfers_to_cancel = [];
$('#history').find('.transfer.ui-selected').each(function () {
transfers_to_cancel.push($(this).attr('data-transfer'));
});
if (transfers_to_cancel.length > 0)
cancelTransfers(transfers_to_cancel);
}
});
var khistory = new KHistory();
Config.reset(() => khistory.fetch({'transfersonly': true}));
});
</script>

View file

@ -3,6 +3,7 @@
{% block extra_head %}
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
<script type="text/javascript" src="{% url 'js_reverse' %}"></script>
{% endblock %}
{% block title %}Nouveaux transferts{% endblock %}
@ -51,14 +52,12 @@
<script type="text/javascript">
$(document).ready(function () {
function getAccountData(trigramme, callback = function() {}) {
$.ajax({
dataType: "json",
url : "{% url 'kfet.account.read.json' %}",
method : "POST",
data : { trigramme: trigramme },
success : callback,
});
function getAccountData(trigramme, callback) {
callback = callback || $.noop;
$.getJSON(Urls['kfet.account.read'](trigramme), {
'format': 'json',
})
.done(callback);
}
function updateAccountData(trigramme, $input) {

View file

@ -4,6 +4,7 @@ from decimal import Decimal
from unittest import mock
from django.contrib.auth.models import Group
from django.core.serializers.json import DjangoJSONEncoder
from django.core.urlresolvers import reverse
from django.test import Client, TestCase
from django.utils import timezone
@ -12,7 +13,7 @@ from ..config import kfet_config
from ..models import (
Account, Article, ArticleCategory, Checkout, CheckoutStatement, Inventory,
InventoryArticle, Operation, OperationGroup, Order, OrderArticle, Supplier,
SupplierArticle, Transfer, TransferGroup,
SupplierArticle, TransferGroup,
)
from .testcases import ViewTestCaseMixin
from .utils import create_team, create_user, get_perms
@ -248,6 +249,26 @@ class AccountReadViewTests(ViewTestCaseMixin, TestCase):
r = client.get(self.url)
self.assertEqual(r.status_code, 200)
def test_json(self):
r = self.client.get(self.url, {'format': 'json'})
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode('utf-8'))
expected = {
'name': 'first last',
'trigramme': '001',
'balance': '0.00',
}
self.assertDictContainsSubset(expected, content)
self.assertSetEqual(set(content.keys()), set([
'id', 'trigramme', 'first_name', 'last_name', 'name', 'email',
'is_cof', 'promo', 'balance', 'is_frozen', 'departement',
'nickname',
]))
class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.account.update'
@ -762,6 +783,35 @@ class CheckoutReadViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertEqual(r.context['checkout'], self.checkout)
def test_json(self):
r = self.client.get(self.url, {
'format': 'json',
'last_statement': '1',
})
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode('utf-8'))
json_now = json.dumps(self.now, cls=DjangoJSONEncoder).strip('"')
json_tomorrow = json.dumps(
self.now + timedelta(days=1),
cls=DjangoJSONEncoder
).strip('"')
self.assertEqual(content, {
'id': self.checkout.pk,
'name': 'Checkout',
'balance': '10.00',
'valid_from': json_now,
'valid_to': json_tomorrow,
'laststatement': {
'id': self.checkout.statements.all()[0].pk,
'at': json_now,
'balance_new': '10.00',
'balance_old': '10.00',
'by': str(self.accounts['team']),
},
})
class CheckoutUpdateViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.checkout.update'
@ -1411,46 +1461,6 @@ class KPsulViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
class KPsulCheckoutDataViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.kpsul.checkout_data'
url_expected = '/k-fet/k-psul/checkout_data'
http_methods = ['POST']
auth_user = 'team'
auth_forbidden = [None, 'user']
def setUp(self):
super().setUp()
self.checkout = Checkout.objects.create(
name='Checkout',
balance=Decimal('10'),
created_by=self.accounts['team'],
valid_from=self.now,
valid_to=self.now + timedelta(days=5),
)
def test_ok(self):
r = self.client.post(self.url, {'pk': self.checkout.pk})
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode('utf-8'))
expected = {
'name': 'Checkout',
'balance': '10.00',
}
self.assertDictContainsSubset(expected, content)
self.assertSetEqual(set(content.keys()), set([
'balance', 'id', 'name', 'valid_from', 'valid_to',
'last_statement_at', 'last_statement_balance',
'last_statement_by_first_name', 'last_statement_by_last_name',
'last_statement_by_trigramme',
]))
class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.kpsul.perform_operations'
url_expected = '/k-fet/k-psul/perform_operations'
@ -1470,11 +1480,82 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
http_methods = ['POST']
auth_user = 'team'
auth_user = 'team1'
auth_forbidden = [None, 'user']
def get_users_extra(self):
return {
'team1': create_team('team1', '101', perms=[
'kfet.perform_negative_operations',
]),
'u1': create_user('user1', '001'),
'u2': create_user('user2', '002'),
'u3': create_user('user3', '003'),
}
def setUp(self):
super().setUp()
trg1 = TransferGroup.objects.create()
self.trg1_1 = trg1.transfers.create(
from_acc=self.accounts['u1'],
to_acc=self.accounts['u2'],
amount='3.5',
)
self.trg1_2 = trg1.transfers.create(
from_acc=self.accounts['u2'],
to_acc=self.accounts['u3'],
amount='2.4',
)
trg2 = TransferGroup.objects.create(
at=(
timezone.now() -
(kfet_config.cancel_duration + timedelta(seconds=1))
),
)
self.trg2_1 = trg2.transfers.create(
from_acc=self.accounts['u1'],
to_acc=self.accounts['u2'],
amount='5',
)
def test_ok(self):
pass
data = {
'transfers[]': [str(self.trg1_1.pk), str(self.trg1_2.pk)],
}
r = self.client.post(self.url, data)
self.assertEqual(r.status_code, 200)
u1 = self.accounts['u1']
u1.refresh_from_db()
self.assertEqual(u1.balance, Decimal('3.5'))
u2 = self.accounts['u2']
u2.refresh_from_db()
self.assertEqual(u2.balance, Decimal('-1.1'))
u3 = self.accounts['u3']
u3.refresh_from_db()
self.assertEqual(u3.balance, Decimal('-2.4'))
def test_error_tooold(self):
data = {
'transfers[]': [str(self.trg2_1.pk)],
}
r = self.client.post(self.url, data)
self.assertEqual(r.status_code, 403)
self.assertDictEqual(json.loads(r.content.decode('utf-8')), {
'canceled': {},
'warnings': {},
'errors': {
'missing_perms': ['Annuler des commandes non récentes'],
},
})
class KPsulArticlesData(ViewTestCaseMixin, TestCase):
@ -1486,16 +1567,10 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
def setUp(self):
super().setUp()
category = ArticleCategory.objects.create(name='Catégorie')
self.article1 = Article.objects.create(
category=category,
name='Article 1',
)
self.article2 = Article.objects.create(
category=category,
name='Article 2',
price=Decimal('2.5'),
)
self.ac = ArticleCategory.objects.create(name='Catégorie')
self.a1 = self.ac.articles.create(name='Article 1')
self.a2 = self.ac.articles.create(name='Article 2', price='2.5')
def test_ok(self):
r = self.client.get(self.url)
@ -1503,24 +1578,35 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
content = json.loads(r.content.decode('utf-8'))
articles = content['articles']
expected_list = [{
'category__name': 'Catégorie',
'name': 'Article 1',
'price': '0.00',
}, {
'category__name': 'Catégorie',
'name': 'Article 2',
'price': '2.50',
}]
for expected, article in zip(expected_list, articles):
self.assertDictContainsSubset(expected, article)
self.assertSetEqual(set(article.keys()), set([
'id', 'name', 'price', 'stock',
'category_id', 'category__name', 'category__has_addcost',
]))
self.assertEqual(content, {
'objects': {
'article': [
{
'id': self.a1.pk,
'name': 'Article 1',
'price': '0.00',
'stock': 0,
'category__id': self.a1.category.pk,
},
{
'id': self.a2.pk,
'name': 'Article 2',
'price': '2.50',
'stock': 0,
'category__id': self.a2.category.pk,
},
],
},
'related': {
'category': [
{
'id': self.ac.pk,
'name': 'Catégorie',
'has_addcost': True,
},
],
},
})
class KPsulUpdateAddcost(ViewTestCaseMixin, TestCase):
@ -1586,34 +1672,6 @@ class HistoryJSONViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.account.read.json'
url_expected = '/k-fet/accounts/read.json'
http_methods = ['POST']
auth_user = 'team'
auth_forbidden = [None, 'user']
def test_ok(self):
r = self.client.post(self.url, {'trigramme': '000'})
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode('utf-8'))
expected = {
'name': 'first last',
'trigramme': '000',
'balance': '0.00',
}
self.assertDictContainsSubset(expected, content)
self.assertSetEqual(set(content.keys()), set([
'balance', 'departement', 'email', 'id', 'is_cof', 'is_frozen',
'name', 'nickname', 'promo', 'trigramme',
]))
class SettingsListViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.settings'
url_expected = '/k-fet/settings/'
@ -1766,62 +1824,6 @@ class TransferPerformViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(team1.balance, Decimal('2.4'))
class TransferCancelViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.transfers.cancel'
url_expected = '/k-fet/transfers/cancel'
http_methods = ['POST']
auth_user = 'team1'
auth_forbidden = [None, 'user', 'team']
def get_users_extra(self):
return {
'team1': create_team('team1', '101', perms=[
# Convenience
'kfet.perform_negative_operations',
]),
}
@property
def post_data(self):
return {
'transfers[]': [self.transfer1.pk, self.transfer2.pk],
}
def setUp(self):
super().setUp()
group = TransferGroup.objects.create()
self.transfer1 = Transfer.objects.create(
group=group,
from_acc=self.accounts['user'],
to_acc=self.accounts['team'],
amount='3.5',
)
self.transfer2 = Transfer.objects.create(
group=group,
from_acc=self.accounts['team'],
to_acc=self.accounts['root'],
amount='2.4',
)
def test_ok(self):
r = self.client.post(self.url, self.post_data)
self.assertEqual(r.status_code, 200)
user = self.accounts['user']
user.refresh_from_db()
self.assertEqual(user.balance, Decimal('3.5'))
team = self.accounts['team']
team.refresh_from_db()
self.assertEqual(team.balance, Decimal('-1.1'))
root = self.accounts['root']
root.refresh_from_db()
self.assertEqual(root.balance, Decimal('-2.4'))
class InventoryListViewTests(ViewTestCaseMixin, TestCase):
url_name = 'kfet.inventory'
url_expected = '/k-fet/inventaires/'

View file

@ -159,8 +159,6 @@ urlpatterns = [
# -----
url('^k-psul/$', views.kpsul, name='kfet.kpsul'),
url('^k-psul/checkout_data$', views.kpsul_checkout_data,
name='kfet.kpsul.checkout_data'),
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
name='kfet.kpsul.perform_operations'),
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
@ -178,9 +176,6 @@ urlpatterns = [
url(r'^history.json$', views.history_json,
name='kfet.history.json'),
url(r'^accounts/read.json$', views.account_read_json,
name='kfet.account.read.json'),
# -----
# Settings urls
@ -202,8 +197,6 @@ urlpatterns = [
name='kfet.transfers.create'),
url(r'^transfers/perform$', views.perform_transfers,
name='kfet.transfers.perform'),
url(r'^transfers/cancel$', views.cancel_transfers,
name='kfet.transfers.cancel'),
# -----
# Inventories urls

View file

@ -15,7 +15,7 @@ from django.contrib.auth.models import User, Permission
from django.http import JsonResponse, Http404
from django.forms import formset_factory
from django.db import transaction
from django.db.models import F, Sum, Prefetch, Count
from django.db.models import Q, F, Sum, Prefetch, Count
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.utils.decorators import method_decorator
@ -47,12 +47,36 @@ from decimal import Decimal
import heapq
import statistics
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
from .auth.views import ( # noqa
account_group, login_generic, AccountGroupCreate, AccountGroupUpdate,
)
# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
class JSONResponseMixin(object):
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(
self.get_data(context),
**response_kwargs
)
def get_data(self, context):
"""
Returns an object that will be serialized as JSON by json.dumps().
"""
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return context
def put_cleaned_data_in_dict(dict, form):
for field in form.cleaned_data:
dict[field] = form.cleaned_data[field]
@ -323,6 +347,13 @@ def account_read(request, trigramme):
request.user != account.user):
raise PermissionDenied
if request.GET.get('format') == 'json':
export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name',
'email', 'is_cof', 'promo', 'balance', 'is_frozen',
'departement', 'nickname']
data = {k: getattr(account, k) for k in export_keys}
return JsonResponse(data)
addcosts = (
OperationGroup.objects
.filter(opes__addcost_for=account,
@ -338,6 +369,7 @@ def account_read(request, trigramme):
'addcosts': addcosts,
})
# Account - Update
@ -530,18 +562,38 @@ class CheckoutCreate(SuccessMessageMixin, CreateView):
return super().form_valid(form)
# Checkout - Read
class CheckoutRead(DetailView):
class CheckoutRead(JSONResponseMixin, DetailView):
model = Checkout
template_name = 'kfet/checkout_read.html'
context_object_name = 'checkout'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['statements'] = context['checkout'].statements.order_by('-at')
checkout = self.object
if self.request.GET.get('last_statement'):
context['laststatement'] = checkout.statements.latest('at')
else:
context['statements'] = checkout.statements.order_by('-at')
return context
def render_to_response(self, context, **kwargs):
if self.request.GET.get('format') == 'json':
export_keys = ['id', 'name', 'balance', 'valid_from', 'valid_to']
data = {k: getattr(self.object, k) for k in export_keys}
if 'laststatement' in context:
last_st = context['laststatement']
export_keys = ['id', 'at', 'balance_new', 'balance_old']
last_st_data = {k: getattr(last_st, k) for k in export_keys}
last_st_data['by'] = str(last_st.by)
data['laststatement'] = last_st_data
return self.render_to_json_response(data)
else:
return super().render_to_response(context, **kwargs)
# Checkout - Update
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
@ -625,7 +677,24 @@ class CheckoutStatementCreate(SuccessMessageMixin, CreateView):
form.instance.balance_new = getAmountBalance(form.cleaned_data)
form.instance.checkout_id = self.kwargs['pk_checkout']
form.instance.by = self.request.user.profile.account_kfet
return super().form_valid(form)
res = super(CheckoutStatementCreate, self).form_valid(form)
ws_data = {
'id': self.object.id,
'at': self.object.at,
'balance_new': self.object.balance_new,
'balance_old': self.object.balance_old,
'by': str(self.object.by),
}
consumers.KPsul.group_send('kfet.kpsul', {
'checkouts': [{
'id': self.object.checkout.id,
'laststatement': ws_data,
}],
})
return res
class CheckoutStatementUpdate(SuccessMessageMixin, UpdateView):
model = CheckoutStatement
@ -848,50 +917,6 @@ def kpsul_get_settings(request):
return JsonResponse(data)
@teamkfet_required
def account_read_json(request):
trigramme = request.POST.get('trigramme', '')
account = get_object_or_404(Account, trigramme=trigramme)
data = { 'id': account.pk, 'name': account.name, 'email': account.email,
'is_cof': account.is_cof, 'promo': account.promo,
'balance': account.balance, 'is_frozen': account.is_frozen,
'departement': account.departement, 'nickname': account.nickname,
'trigramme': account.trigramme }
return JsonResponse(data)
@teamkfet_required
def kpsul_checkout_data(request):
pk = request.POST.get('pk', 0)
if not pk:
pk = 0
data = (
Checkout.objects
.annotate(
last_statement_by_first_name=F('statements__by__cofprofile__user__first_name'),
last_statement_by_last_name=F('statements__by__cofprofile__user__last_name'),
last_statement_by_trigramme=F('statements__by__trigramme'),
last_statement_balance=F('statements__balance_new'),
last_statement_at=F('statements__at'))
.select_related(
'statements'
'statements__by',
'statements__by__cofprofile__user')
.filter(pk=pk)
.order_by('statements__at')
.values(
'id', 'name', 'balance', 'valid_from', 'valid_to',
'last_statement_balance', 'last_statement_at',
'last_statement_by_trigramme', 'last_statement_by_last_name',
'last_statement_by_first_name')
.last()
)
if data is None:
raise Http404
return JsonResponse(data)
@teamkfet_required
def kpsul_update_addcost(request):
addcost_form = AddcostForm(request.POST)
@ -1081,31 +1106,47 @@ def kpsul_perform_operations(request):
websocket_data = {}
websocket_data['opegroups'] = [{
'add': True,
'id': operationgroup.pk,
'amount': operationgroup.amount,
'checkout__name': operationgroup.checkout.name,
'at': operationgroup.at,
'is_cof': operationgroup.is_cof,
'comment': operationgroup.comment,
'valid_by__trigramme': (operationgroup.valid_by and
operationgroup.valid_by.trigramme or None),
'on_acc__trigramme': operationgroup.on_acc.trigramme,
'opes': [],
'modelname': 'opegroup',
'content': {
'id': operationgroup.pk,
'amount': operationgroup.amount,
'at': operationgroup.at,
'is_cof': operationgroup.is_cof,
'comment': operationgroup.comment,
'valid_by': (operationgroup.valid_by and
operationgroup.valid_by.trigramme or None),
'trigramme': operationgroup.on_acc.trigramme,
# Used to filter websocket updates
'account_id': operationgroup.on_acc.pk,
'checkout_id': operationgroup.checkout.pk,
'children': [],
},
}]
for operation in operations:
for ope in operations:
ope_data = {
'id': operation.pk, 'type': operation.type,
'amount': operation.amount,
'addcost_amount': operation.addcost_amount,
'addcost_for__trigramme': (
operation.addcost_for and addcost_for.trigramme or None),
'article__name': (
operation.article and operation.article.name or None),
'article_nb': operation.article_nb,
'group_id': operationgroup.pk,
'canceled_by__trigramme': None, 'canceled_at': None,
'content': {
'id': ope.id,
'amount': ope.amount,
'canceled_at': None,
'canceled_by': None,
},
}
websocket_data['opegroups'][0]['opes'].append(ope_data)
if ope.type == Operation.PURCHASE:
ope_data['modelname'] = 'purchase'
ope_data['content'].update({
'article_name': ope.article.name,
'article_nb': ope.article_nb,
'addcost_amount': ope.addcost_amount,
'addcost_for':
ope.addcost_for and ope.addcost_for.trigramme or None,
})
else:
ope_data['modelname'] = 'specialope'
ope_data['content'].update({
'type': ope.type,
})
websocket_data['opegroups'][0]['content']['children'].append(ope_data)
# Need refresh from db cause we used update on queryset
operationgroup.checkout.refresh_from_db()
websocket_data['checkouts'] = [{
@ -1128,37 +1169,63 @@ def kpsul_perform_operations(request):
@teamkfet_required
def kpsul_cancel_operations(request):
# Pour la réponse
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
data = {'canceled': {}, 'warnings': {}, 'errors': {}}
# Checking if BAD REQUEST (opes_pk not int or not existing)
try:
# Set pour virer les doublons
opes_post = set(map(int, filter(None, request.POST.getlist('operations[]', []))))
opes_post = (
set(map(int, filter(None, request.POST.getlist('opes[]', []))))
)
transfers_post = (
set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
)
except ValueError:
return JsonResponse(data, status=400)
opes_all = (
Operation.objects
.select_related('group', 'group__on_acc', 'group__on_acc__negative')
.filter(pk__in=opes_post))
opes_pk = [ ope.pk for ope in opes_all ]
opes_notexisting = [ ope for ope in opes_post if ope not in opes_pk ]
if opes_notexisting:
data['errors']['opes_notexisting'] = opes_notexisting
opes_pk = [ope.pk for ope in opes_all]
opes_notexisting = [ope for ope in opes_post if ope not in opes_pk]
transfers_all = (
Transfer.objects
.select_related('group', 'from_acc', 'from_acc__negative',
'to_acc', 'to_acc__negative')
.filter(pk__in=transfers_post))
transfers_pk = [transfer.pk for transfer in transfers_all]
transfers_notexisting = [transfer for transfer in transfers_post
if transfer not in transfers_pk]
if transfers_notexisting or opes_notexisting:
if transfers_notexisting:
data['errors']['transfers_notexisting'] = transfers_notexisting
if opes_notexisting:
data['errors']['opes_notexisting'] = opes_notexisting
return JsonResponse(data, status=400)
opes_already_canceled = [] # Déjà annulée
opes = [] # Pas déjà annulée
already_canceled = {} # Opération/Transfert déjà annulé
opes = [] # Pas déjà annulée
transfers = []
required_perms = set()
stop_all = False
stop_all = False
cancel_duration = kfet_config.cancel_duration
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
to_groups_amounts = defaultdict(lambda:0) # ------ sur les montants des groupes d'opé
to_checkouts_balances = defaultdict(lambda:0) # ------ sur les balances de caisses
to_articles_stocks = defaultdict(lambda:0) # ------ sur les stocks d'articles
# Modifs à faire sur les balances des comptes
to_accounts_balances = defaultdict(lambda: 0)
# ------ sur les montants des groupes d'opé
to_groups_amounts = defaultdict(lambda: 0)
# ------ sur les balances de caisses
to_checkouts_balances = defaultdict(lambda: 0)
# ------ sur les stocks d'articles
to_articles_stocks = defaultdict(lambda: 0)
for ope in opes_all:
if ope.canceled_at:
# Opération déjà annulée, va pour un warning en Response
opes_already_canceled.append(ope.pk)
already_canceled['opes'].append(ope.pk)
else:
opes.append(ope.pk)
# Si opé il y a plus de CANCEL_DURATION, permission requise
@ -1185,10 +1252,11 @@ def kpsul_cancel_operations(request):
# par `.save()`, amount_error est recalculé automatiquement,
# ce qui n'est pas le cas en faisant un update sur queryset
# TODO ? : Maj les balance_old de relevés pour modifier l'erreur
last_statement = (CheckoutStatement.objects
.filter(checkout=ope.group.checkout)
.order_by('at')
.last())
last_statement = \
(CheckoutStatement.objects
.filter(checkout=ope.group.checkout)
.order_by('at')
.last())
if not last_statement or last_statement.at < ope.group.at:
if ope.is_checkout:
if ope.group.on_acc.is_cash:
@ -1204,23 +1272,41 @@ def kpsul_cancel_operations(request):
# Note : si InventoryArticle est maj par .save(), stock_error
# est recalculé automatiquement
if ope.article and ope.article_nb:
last_stock = (InventoryArticle.objects
last_stock = (
InventoryArticle.objects
.select_related('inventory')
.filter(article=ope.article)
.order_by('inventory__at')
.last())
.last()
)
if not last_stock or last_stock.inventory.at < ope.group.at:
to_articles_stocks[ope.article] += ope.article_nb
if not opes:
data['warnings']['already_canceled'] = opes_already_canceled
for transfer in transfers_all:
if transfer.canceled_at:
# Transfert déjà annulé, va pour un warning en Response
already_canceled['transfers'].append(transfer.pk)
else:
transfers.append(transfer.pk)
# Si transfer il y a plus de CANCEL_DURATION, permission requise
if transfer.group.at + cancel_duration < timezone.now():
required_perms.add('kfet.cancel_old_operations')
# Calcul de toutes modifs à faire en cas de validation
# Pour les balances de comptes
to_accounts_balances[transfer.from_acc] += transfer.amount
to_accounts_balances[transfer.to_acc] += -transfer.amount
if not opes and not transfers:
data['warnings']['already_canceled'] = already_canceled
return JsonResponse(data)
negative_accounts = []
# Checking permissions or stop
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account])
amount=to_accounts_balances[account])
required_perms |= perms
stop_all = stop_all or stop
if stop:
@ -1240,6 +1326,10 @@ def kpsul_cancel_operations(request):
with transaction.atomic():
(Operation.objects.filter(pk__in=opes)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
(Transfer.objects.filter(pk__in=transfers)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances:
(
Account.objects
@ -1252,20 +1342,22 @@ def kpsul_cancel_operations(request):
account.update_negative()
for checkout in to_checkouts_balances:
Checkout.objects.filter(pk=checkout.pk).update(
balance = F('balance') + to_checkouts_balances[checkout])
balance=F('balance') + to_checkouts_balances[checkout])
for group in to_groups_amounts:
OperationGroup.objects.filter(pk=group.pk).update(
amount = F('amount') + to_groups_amounts[group])
amount=F('amount') + to_groups_amounts[group])
for article in to_articles_stocks:
Article.objects.filter(pk=article.pk).update(
stock = F('stock') + to_articles_stocks[article])
stock=F('stock') + to_articles_stocks[article])
# Websocket data
websocket_data = { 'opegroups': [], 'opes': [], 'checkouts': [], 'articles': [] }
websocket_data = {'opegroups': [], 'opes': [],
'checkouts': [], 'articles': []}
# Need refresh from db cause we used update on querysets
opegroups_pk = [ opegroup.pk for opegroup in to_groups_amounts ]
opegroups_pk = [opegroup.pk for opegroup in to_groups_amounts]
opegroups = (OperationGroup.objects
.values('id','amount','is_cof').filter(pk__in=opegroups_pk))
.values('id', 'amount', 'is_cof')
.filter(pk__in=opegroups_pk))
for opegroup in opegroups:
websocket_data['opegroups'].append({
'cancellation': True,
@ -1273,24 +1365,35 @@ def kpsul_cancel_operations(request):
'amount': opegroup['amount'],
'is_cof': opegroup['is_cof'],
})
canceled_by__trigramme = canceled_by and canceled_by.trigramme or None
canceled_by = canceled_by and canceled_by.trigramme or None
for ope in opes:
websocket_data['opes'].append({
'cancellation': True,
'modelname': 'ope',
'id': ope,
'canceled_by__trigramme': canceled_by__trigramme,
'canceled_by': canceled_by,
'canceled_at': canceled_at,
})
for ope in transfers:
websocket_data['opes'].append({
'cancellation': True,
'modelname': 'transfer',
'id': ope,
'canceled_by': canceled_by,
'canceled_at': canceled_at,
})
# Need refresh from db cause we used update on querysets
checkouts_pk = [ checkout.pk for checkout in to_checkouts_balances]
checkouts_pk = [checkout.pk for checkout in to_checkouts_balances]
checkouts = (Checkout.objects
.values('id', 'balance').filter(pk__in=checkouts_pk))
.values('id', 'balance')
.filter(pk__in=checkouts_pk))
for checkout in checkouts:
websocket_data['checkouts'].append({
'id': checkout['id'],
'balance': checkout['balance']})
# Need refresh from db cause we used update on querysets
articles_pk = [ article.pk for articles in to_articles_stocks]
articles_pk = [article.pk for articles in to_articles_stocks]
articles = Article.objects.values('id', 'stock').filter(pk__in=articles_pk)
for article in articles:
websocket_data['articles'].append({
@ -1298,92 +1401,195 @@ def kpsul_cancel_operations(request):
'stock': article['stock']})
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
data['canceled'] = opes
if opes_already_canceled:
data['warnings']['already_canceled'] = opes_already_canceled
data['canceled']['opes'] = opes
data['canceled']['transfers'] = transfers
if already_canceled:
data['warnings']['already_canceled'] = already_canceled
return JsonResponse(data)
@login_required
def history_json(request):
# Récupération des paramètres
from_date = request.POST.get('from', None)
to_date = request.POST.get('to', None)
limit = request.POST.get('limit', None);
checkouts = request.POST.getlist('checkouts[]', None)
accounts = request.POST.getlist('accounts[]', None)
from_date = request.GET.get('from', None)
to_date = request.GET.get('to', None)
checkouts = request.GET.getlist('checkouts[]', None)
accounts = request.GET.getlist('accounts[]', None)
transfers_only = request.GET.get('transfersonly', None)
opes_only = request.GET.get('opesonly', None)
# Un non-membre de l'équipe n'a que accès à son historique
if not request.user.has_perm('kfet.is_team'):
accounts = [request.user.profile.account_kfet]
# Construction de la requête (sur les opérations) pour le prefetch
queryset_prefetch = Operation.objects.select_related(
'article', 'canceled_by', 'addcost_for')
ope_queryset_prefetch = Operation.objects.select_related(
'canceled_by', 'addcost_for', 'article')
ope_prefetch = Prefetch('opes',
queryset=ope_queryset_prefetch)
transfer_queryset_prefetch = Transfer.objects.select_related(
'from_acc', 'to_acc', 'canceled_by')
if accounts:
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc__in=accounts) |
Q(to_acc__in=accounts))
if not request.user.has_perm('kfet.is_team'):
acc = request.user.profile.account_kfet
transfer_queryset_prefetch = transfer_queryset_prefetch.filter(
Q(from_acc=acc) | Q(to_acc=acc))
transfer_prefetch = Prefetch('transfers',
queryset=transfer_queryset_prefetch,
to_attr='filtered_transfers')
# Construction de la requête principale
opegroups = (
OperationGroup.objects
.prefetch_related(Prefetch('opes', queryset=queryset_prefetch))
.select_related('on_acc', 'valid_by')
.order_by('at')
.prefetch_related(ope_prefetch)
.select_related('on_acc',
'valid_by')
.order_by('at')
)
transfergroups = (
TransferGroup.objects
.prefetch_related(transfer_prefetch)
.select_related('valid_by')
.order_by('at')
)
# Application des filtres
if from_date:
opegroups = opegroups.filter(at__gte=from_date)
transfergroups = transfergroups.filter(at__gte=from_date)
if to_date:
opegroups = opegroups.filter(at__lt=to_date)
transfergroups = transfergroups.filter(at__lt=to_date)
if checkouts:
opegroups = opegroups.filter(checkout_id__in=checkouts)
transfergroups = TransferGroup.objects.none()
if transfers_only:
opegroups = OperationGroup.objects.none()
if opes_only:
transfergroups = TransferGroup.objects.none()
if accounts:
opegroups = opegroups.filter(on_acc_id__in=accounts)
# Un non-membre de l'équipe n'a que accès à son historique
if not request.user.has_perm('kfet.is_team'):
opegroups = opegroups.filter(on_acc=request.user.profile.account_kfet)
if limit:
opegroups = opegroups[:limit]
opegroups = opegroups.filter(on_acc__in=accounts)
# Construction de la réponse
opegroups_list = []
related_data = defaultdict(list)
objects_data = defaultdict(list)
for opegroup in opegroups:
opegroup_dict = {
'id' : opegroup.id,
'amount' : opegroup.amount,
'at' : opegroup.at,
'checkout_id': opegroup.checkout_id,
'is_cof' : opegroup.is_cof,
'comment' : opegroup.comment,
'opes' : [],
'on_acc__trigramme':
opegroup.on_acc and opegroup.on_acc.trigramme or None,
'id': opegroup.id,
'amount': opegroup.amount,
'at': opegroup.at,
'is_cof': opegroup.is_cof,
'comment': opegroup.comment,
'trigramme':
opegroup.on_acc and opegroup.on_acc.trigramme or None,
}
if request.user.has_perm('kfet.is_team'):
opegroup_dict['valid_by__trigramme'] = (
opegroup_dict['valid_by'] = (
opegroup.valid_by and opegroup.valid_by.trigramme or None)
for ope in opegroup.opes.all():
ope_dict = {
'id' : ope.id,
'type' : ope.type,
'amount' : ope.amount,
'article_nb' : ope.article_nb,
'addcost_amount': ope.addcost_amount,
'canceled_at' : ope.canceled_at,
'article__name':
ope.article and ope.article.name or None,
'addcost_for__trigramme':
ope.addcost_for and ope.addcost_for.trigramme or None,
'id': ope.id,
'amount': ope.amount,
'canceled_at': ope.canceled_at,
'is_cof': opegroup.is_cof,
'trigramme':
opegroup.on_acc and opegroup.on_acc.trigramme or None,
'opegroup__id': opegroup.id,
}
if request.user.has_perm('kfet.is_team'):
ope_dict['canceled_by__trigramme'] = (
ope_dict['canceled_by'] = (
ope.canceled_by and ope.canceled_by.trigramme or None)
opegroup_dict['opes'].append(ope_dict)
opegroups_list.append(opegroup_dict)
return JsonResponse({ 'opegroups': opegroups_list })
if ope.type == Operation.PURCHASE:
ope_dict.update({
'article_name': ope.article.name,
'article_nb': ope.article_nb,
'addcost_amount': ope.addcost_amount,
'addcost_for':
ope.addcost_for and ope.addcost_for.trigramme or None,
})
objects_data['purchase'].append(ope_dict)
else:
ope_dict.update({
'type': ope.type,
})
objects_data['specialope'].append(ope_dict)
related_data['opegroup'].append(opegroup_dict)
for transfergroup in transfergroups:
if transfergroup.filtered_transfers:
transfergroup_dict = {
'id': transfergroup.id,
'at': transfergroup.at,
'comment': transfergroup.comment,
}
if request.user.has_perm('kfet.is_team'):
transfergroup_dict['valid_by'] = (
transfergroup.valid_by and
transfergroup.valid_by.trigramme or
None)
for transfer in transfergroup.filtered_transfers:
transfer_dict = {
'id': transfer.id,
'amount': transfer.amount,
'canceled_at': transfer.canceled_at,
'from_acc': transfer.from_acc.trigramme,
'to_acc': transfer.to_acc.trigramme,
'transfergroup__id': transfergroup.id,
}
if request.user.has_perm('kfet.is_team'):
transfer_dict['canceled_by'] = (
transfer.canceled_by and
transfer.canceled_by.trigramme or
None)
objects_data['transfer'].append(transfer_dict)
related_data['transfergroup'].append(transfergroup_dict)
data = {
'objects': objects_data,
'related': related_data,
}
return JsonResponse(data)
@teamkfet_required
def kpsul_articles_data(request):
articles = (
Article.objects
.values('id', 'name', 'price', 'stock', 'category_id',
'category__name', 'category__has_addcost')
.filter(is_sold=True))
return JsonResponse({ 'articles': list(articles) })
data = {'objects': {}, 'related': {}}
data['objects']['article'] = [
{
'id': article.id,
'name': article.name,
'price': article.price,
'stock': article.stock,
'category__id': article.category_id,
}
for article in Article.objects.filter(is_sold=True)
]
data['related']['category'] = [
{
'id': category.id,
'name': category.name,
'has_addcost': category.has_addcost,
}
for category in ArticleCategory.objects.all()
]
return JsonResponse(data)
@teamkfet_required
@ -1430,25 +1636,10 @@ config_update = (
# Transfer views
# -----
@teamkfet_required
def transfers(request):
transfers_pre = Prefetch(
'transfers',
queryset=(
Transfer.objects
.select_related('from_acc', 'to_acc')
),
)
transfergroups = (
TransferGroup.objects
.select_related('valid_by')
.prefetch_related(transfers_pre)
.order_by('-at')
)
return render(request, 'kfet/transfers.html', {
'transfergroups': transfergroups,
})
return render(request, 'kfet/transfers.html')
@teamkfet_required
@ -1457,20 +1648,24 @@ def transfers_create(request):
return render(request, 'kfet/transfers_create.html',
{ 'transfer_formset': transfer_formset })
@teamkfet_required
def perform_transfers(request):
data = { 'errors': {}, 'transfers': [], 'transfergroup': 0 }
data = {'errors': {}, 'transfers': [], 'transfergroup': 0}
# Checking transfer_formset
transfer_formset = TransferFormSet(request.POST)
if not transfer_formset.is_valid():
return JsonResponse({ 'errors': list(transfer_formset.errors)}, status=400)
return JsonResponse({'errors': list(transfer_formset.errors)},
status=400)
transfers = transfer_formset.save(commit = False)
transfers = transfer_formset.save(commit=False)
# Initializing vars
required_perms = set(['kfet.add_transfer']) # Required perms to perform all transfers
to_accounts_balances = defaultdict(lambda:0) # For balances of accounts
# Required perms to perform all transfers
required_perms = set(['kfet.add_transfer'])
# For balances of accounts
to_accounts_balances = defaultdict(lambda: 0)
for transfer in transfers:
to_accounts_balances[transfer.from_acc] -= transfer.amount
@ -1482,7 +1677,7 @@ def perform_transfers(request):
# Checking if ok on all accounts
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account])
amount=to_accounts_balances[account])
required_perms |= perms
stop_all = stop_all or stop
if stop:
@ -1508,7 +1703,7 @@ def perform_transfers(request):
# Updating balances accounts
for account in to_accounts_balances:
Account.objects.filter(pk=account.pk).update(
balance = F('balance') + to_accounts_balances[account])
balance=F('balance') + to_accounts_balances[account])
account.refresh_from_db()
if account.balance < 0:
if hasattr(account, 'negative'):
@ -1517,10 +1712,10 @@ def perform_transfers(request):
account.negative.save()
else:
negative = AccountNegative(
account = account, start = timezone.now())
account=account, start=timezone.now())
negative.save()
elif (hasattr(account, 'negative')
and not account.negative.balance_offset):
elif (hasattr(account, 'negative') and
not account.negative.balance_offset):
account.negative.delete()
# Saving transfer group
@ -1533,103 +1728,38 @@ def perform_transfers(request):
transfer.save()
data['transfers'].append(transfer.pk)
# Websocket data
websocket_data = {}
websocket_data['opegroups'] = [{
'add': True,
'modelname': 'transfergroup',
'content': {
'id': transfergroup.pk,
'at': transfergroup.at,
'comment': transfergroup.comment,
'valid_by__trigramme': (transfergroup.valid_by and
transfergroup.valid_by.trigramme or None),
'children': []
},
}]
for transfer in transfers:
ope_data = {
'modelname': 'transfer',
'content': {
'id': transfer.pk,
'amount': transfer.amount,
'from_acc': transfer.from_acc.trigramme,
'to_acc': transfer.to_acc.trigramme,
'canceled_by__trigramme': None, 'canceled_at': None,
'from_acc_id': transfer.from_acc.id,
'to_acc_id': transfer.to_acc.id,
},
}
websocket_data['opegroups'][0]['content']['children'].append(ope_data)
consumers.KPsul.group_send('kfet.kpsul', websocket_data)
return JsonResponse(data)
@teamkfet_required
def cancel_transfers(request):
# Pour la réponse
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
# Checking if BAD REQUEST (transfers_pk not int or not existing)
try:
# Set pour virer les doublons
transfers_post = set(map(int, filter(None, request.POST.getlist('transfers[]', []))))
except ValueError:
return JsonResponse(data, status=400)
transfers_all = (
Transfer.objects
.select_related('group', 'from_acc', 'from_acc__negative',
'to_acc', 'to_acc__negative')
.filter(pk__in=transfers_post))
transfers_pk = [ transfer.pk for transfer in transfers_all ]
transfers_notexisting = [ transfer for transfer in transfers_post
if transfer not in transfers_pk ]
if transfers_notexisting:
data['errors']['transfers_notexisting'] = transfers_notexisting
return JsonResponse(data, status=400)
transfers_already_canceled = [] # Déjà annulée
transfers = [] # Pas déjà annulée
required_perms = set()
stop_all = False
cancel_duration = kfet_config.cancel_duration
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
for transfer in transfers_all:
if transfer.canceled_at:
# Transfert déjà annulé, va pour un warning en Response
transfers_already_canceled.append(transfer.pk)
else:
transfers.append(transfer.pk)
# Si transfer il y a plus de CANCEL_DURATION, permission requise
if transfer.group.at + cancel_duration < timezone.now():
required_perms.add('kfet.cancel_old_operations')
# Calcul de toutes modifs à faire en cas de validation
# Pour les balances de comptes
to_accounts_balances[transfer.from_acc] += transfer.amount
to_accounts_balances[transfer.to_acc] += -transfer.amount
if not transfers:
data['warnings']['already_canceled'] = transfers_already_canceled
return JsonResponse(data)
negative_accounts = []
# Checking permissions or stop
for account in to_accounts_balances:
(perms, stop) = account.perms_to_perform_operation(
amount = to_accounts_balances[account])
required_perms |= perms
stop_all = stop_all or stop
if stop:
negative_accounts.append(account.trigramme)
if stop_all or not request.user.has_perms(required_perms):
missing_perms = get_missing_perms(required_perms, request.user)
if missing_perms:
data['errors']['missing_perms'] = missing_perms
if stop_all:
data['errors']['negative'] = negative_accounts
return JsonResponse(data, status=403)
canceled_by = required_perms and request.user.profile.account_kfet or None
canceled_at = timezone.now()
with transaction.atomic():
(Transfer.objects.filter(pk__in=transfers)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances:
Account.objects.filter(pk=account.pk).update(
balance = F('balance') + to_accounts_balances[account])
account.refresh_from_db()
if account.balance < 0:
if hasattr(account, 'negative'):
if not account.negative.start:
account.negative.start = timezone.now()
account.negative.save()
else:
negative = AccountNegative(
account = account, start = timezone.now())
negative.save()
elif (hasattr(account, 'negative')
and not account.negative.balance_offset):
account.negative.delete()
data['canceled'] = transfers
if transfers_already_canceled:
data['warnings']['already_canceled'] = transfers_already_canceled
return JsonResponse(data)
class InventoryList(ListView):
queryset = (Inventory.objects
@ -2020,29 +2150,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
# ---------------
# Vues génériques
# ---------------
# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/
class JSONResponseMixin(object):
"""
A mixin that can be used to render a JSON response.
"""
def render_to_json_response(self, context, **response_kwargs):
"""
Returns a JSON response, transforming 'context' to make the payload.
"""
return JsonResponse(
self.get_data(context),
**response_kwargs
)
def get_data(self, context):
"""
Returns an object that will be serialized as JSON by json.dumps().
"""
# Note: This is *EXTREMELY* naive; in reality, you'll need
# to do much more complex handling to ensure that arbitrary
# objects -- such as Django model instances or querysets
# -- can be serialized as JSON.
return context
class JSONDetailView(JSONResponseMixin, BaseDetailView):

View file

@ -18,6 +18,7 @@ statistics==1.0.3.5
django-widget-tweaks==1.4.1
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
ldap3
django-js-reverse==0.7.3
channels==1.1.5
python-dateutil
wagtail==1.10.*