Compare commits
320 commits
v0.9.6
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
|
e495f744d9 | ||
|
48f8f20202 | ||
|
d9673f32e3 | ||
|
5149121e11 | ||
|
0d323c985f | ||
|
ff04f44232 | ||
|
fdbe86a464 | ||
|
acad46a9b5 | ||
|
9299c46fdf | ||
|
1efabeb45e | ||
|
103cf61ec6 | ||
|
653f495a70 | ||
|
eabc4e04fb | ||
|
f89572e3de | ||
|
d484b342df | ||
|
916e77a238 | ||
|
962cf972da | ||
|
3b8b5b97f4 | ||
|
af9069b17a | ||
|
c977cf3279 | ||
|
9b7d8530b6 | ||
|
bbb7edd269 | ||
|
0328bdc1f6 | ||
|
b1ab789455 | ||
|
51f0fb406e | ||
|
5accf45cde | ||
|
7dc7acb1a1 | ||
|
86d52e7bbc | ||
|
43808f5510 | ||
|
ed2b6970f4 | ||
|
9357405445 | ||
|
b02f8b7c5f | ||
|
4fdff21f50 | ||
|
546193f0cb | ||
|
314c3e4bd5 | ||
|
135fd890b2 | ||
|
06b08cff07 | ||
|
4d3aca2457 | ||
|
d661ab5996 | ||
|
00dbb77130 | ||
|
330a4a93ed | ||
|
39aa2d2083 | ||
|
7d862290d3 | ||
|
71189d18fc | ||
|
a45c354834 | ||
|
cc84dbb76e | ||
|
536d11f298 | ||
|
f6ed82a02f | ||
|
6ecc3d3c56 | ||
|
94aced117d | ||
|
ee4e5a868c | ||
|
275b9cef46 | ||
|
8f44e26a6d | ||
|
eeb7a852be | ||
|
5203df3d50 | ||
|
efbf8cbad7 | ||
|
aa2d429403 | ||
|
1444574d26 | ||
|
9c79e50eba | ||
|
1c942f0be7 | ||
|
d0506c2a95 | ||
|
22e6001362 | ||
|
bc74496738 | ||
|
afc635fdb4 | ||
|
d1cd30928e | ||
|
46b5496901 | ||
|
bf4b092a12 | ||
|
4b7550f569 | ||
|
c456428681 | ||
|
bea1301475 | ||
|
1d22da2bed | ||
|
8f3da8ae1b | ||
|
e974325d98 | ||
|
aabfc6505b | ||
|
adb19e84f7 | ||
|
8d4c9703a0 | ||
|
2b0abd2606 | ||
|
4323e49cfc | ||
|
969b529a66 | ||
|
644ccadbfc | ||
|
2296fb8d42 | ||
|
aabe98cc67 | ||
|
b8b06c06fe | ||
|
61f5d761da | ||
|
2fd0eed782 | ||
|
0f98f22404 | ||
|
25b6b3a9a4 | ||
|
ccdbff42e6 | ||
|
10ea8cfe92 | ||
|
e30717383c | ||
|
6741aba746 | ||
|
779a6e77ff | ||
|
174ce1f105 | ||
|
6d2cbecffa | ||
|
eeeaaaaddd | ||
|
eee925aa80 | ||
|
59180ed310 | ||
|
c3ac620a2d | ||
|
2ceecaa0a1 | ||
|
376db29a7c | ||
|
a4bcd9c6d7 | ||
|
78fdfc1e1d | ||
|
bc92d2eb11 | ||
|
9284218431 | ||
|
ec9fac293d | ||
|
b036d5494b | ||
|
4b4d50de07 | ||
|
b22b85e0f2 | ||
|
4b98de9075 | ||
|
4844174ead | ||
|
dc8f240243 | ||
|
e817dc29b1 | ||
|
8a2aa3a73a | ||
|
2286e54104 | ||
|
2b5bb9cd8c | ||
|
2d03d25916 | ||
|
b35e18f181 | ||
|
3514a9e0ed | ||
|
47bca8d39f | ||
|
055b95e2fc | ||
|
c5341af3e7 | ||
|
6527d1e36c | ||
|
a53479c6c8 | ||
|
1302f9e843 | ||
|
fb60ba4c0f | ||
|
047ec888fa | ||
|
4ff3f07f9a | ||
|
49e5e91860 | ||
|
73d44feace | ||
|
2cf0678126 | ||
|
bcb0cd642c | ||
|
97462df1a2 | ||
|
cc6ea41ce5 | ||
|
58ae5b5250 | ||
|
44f2a321b2 | ||
|
75ae16cd8d | ||
|
4fb85b9094 | ||
|
b157ba0843 | ||
|
b233acfd64 | ||
|
cea9e62073 | ||
|
3ceb926cd4 | ||
|
60438ea51c | ||
|
551a40a9a0 | ||
|
d5e03cc9e3 | ||
|
0cb3426825 | ||
|
29eaa291c5 | ||
|
fdea4be36f | ||
|
ca389a1788 | ||
|
371cf1ea42 | ||
|
af43a3ce9f | ||
|
2edc45a22e | ||
|
241fe26f26 | ||
|
0441128023 | ||
|
12bde771b2 | ||
|
ea18e467f4 | ||
|
a3dcd57fea | ||
|
d4d1b841d7 | ||
|
bdf23b2f3d | ||
|
813cbb82a5 | ||
|
98cd134d84 | ||
|
3cf73aca37 | ||
|
38dc29f4a1 | ||
|
d937e64663 | ||
|
79324ff5f7 | ||
|
786cd1618a | ||
|
7aa93bf380 | ||
|
ab88755b00 | ||
|
712959c37e | ||
|
23185985b8 | ||
|
1377742acd | ||
|
ebcc79fd36 | ||
|
af6d198c8f | ||
|
396cb3c8f6 | ||
|
28a33cf27f | ||
|
f519bb9f2f | ||
|
413479b94f | ||
|
940d96921c | ||
|
480c8ed78f | ||
|
156af841c2 | ||
|
26191d413d | ||
|
6879a8b3b8 | ||
|
613272f413 | ||
|
da86e52735 | ||
|
a4706ef05e | ||
|
1b74931248 | ||
|
9f3971815e | ||
|
3ea79ad8de | ||
|
ecbcd205fe | ||
|
6cd7df1259 | ||
|
6e970de92c | ||
|
41928e1ff6 | ||
|
7a44797a56 | ||
|
65a4447478 | ||
|
e64e4bcbc8 | ||
|
cab32fbf92 | ||
|
8c8da1f795 | ||
|
39549d257f | ||
|
b247183914 | ||
|
de9d70432f | ||
|
cd4f13bf8c | ||
|
3e9746ca22 | ||
|
6fe4cd9a31 | ||
|
b383284519 | ||
|
36947bfeab | ||
|
a9ca2efe9a | ||
|
7f9c53aa5a | ||
|
8e00135390 | ||
|
dddbb04023 | ||
|
ad30cf9cd9 | ||
|
8a0f933f07 | ||
|
1f70f9b40a | ||
|
c4f68332b9 | ||
|
175026e37e | ||
|
53b63aa655 | ||
|
4369a95054 | ||
|
4e74c871a2 | ||
|
c2a099c3e2 | ||
|
7ea96a74d0 | ||
|
4ee40b0732 | ||
|
40dfa1c102 | ||
|
bef55a85d5 | ||
|
cc2360ab22 | ||
|
500b61928b | ||
|
0f9a3eadd8 | ||
|
e0d3035929 | ||
|
8be16867bb | ||
|
d4ff855251 | ||
|
d6d4121cb3 | ||
|
67394ce8fa | ||
|
0214ad4558 | ||
|
3346dbd0d4 | ||
|
2673f79f98 | ||
|
2bbc1d508e | ||
|
bb254cb278 | ||
|
12364c2a17 | ||
|
cdde2f5ca8 | ||
|
38f8883ab1 | ||
|
feeba76523 | ||
|
9851ef5b6f | ||
|
8e34f34bd6 | ||
|
cc0ca06470 | ||
|
4653f713fa | ||
|
f1c1748b34 | ||
|
3914ec44a8 | ||
|
2dceb4067e | ||
|
5ce2f0beea | ||
|
a89a320685 | ||
|
5dabc7a28a | ||
|
74a1b3d238 | ||
|
229c2bb801 | ||
|
88aa5becff | ||
|
c4b7fa5102 | ||
|
b9a57c7dcb | ||
|
60615e9b4a | ||
|
b2627eea4f | ||
|
af95b31eb8 | ||
|
f50c5e685c | ||
|
8067d3cb96 | ||
|
8e1e9b2ec2 | ||
|
59dd053b45 | ||
|
3100fec233 | ||
|
b3f1a018b5 | ||
|
20ca523f1e | ||
|
5b0f8bffab | ||
|
da320d250f | ||
|
db95f239cd | ||
|
e6c236e1ff | ||
|
fb25cbe773 | ||
|
0b6d823145 | ||
|
1e02062ca3 | ||
|
8b91c3b3c8 | ||
|
4eb5a4fd3f | ||
|
3868721324 | ||
|
a95bfccdf9 | ||
|
a14277ee48 | ||
|
738a72b64e | ||
|
6e016641cd | ||
|
c8e876711c | ||
|
843037ea78 | ||
|
6c738993f4 | ||
|
65cadb5d24 | ||
|
9a71c1b7f5 | ||
|
9adf9668b1 | ||
|
b6397aedf4 | ||
|
0799d6eeb1 | ||
|
1bc56016e7 | ||
|
6d138af016 | ||
|
460c9e6049 | ||
|
8ada3f86b6 | ||
|
0072a97a3a | ||
|
8c166464cc | ||
|
d36091528f | ||
|
cb9c219b27 | ||
|
cf83137f9f | ||
|
fc0d2f6fa0 | ||
|
13cd5ed950 | ||
|
9c500ab24b | ||
|
951a0b233f | ||
|
9f4d39e029 | ||
|
addd5c640c | ||
|
6d508cc432 | ||
|
b0883b5522 | ||
|
9aaf3b4718 | ||
|
bccb292441 | ||
|
14410b475a | ||
|
a0cdaac370 | ||
|
e02638ac58 | ||
|
7155011841 | ||
|
f49fb93658 | ||
|
1015be7810 | ||
|
7df6fecfc4 | ||
|
37bc29829f | ||
|
cb7ab797f5 | ||
|
ead0dff76d | ||
|
747a834156 | ||
|
233df00c7b | ||
|
594fc865eb | ||
|
105c9093b8 | ||
|
049b4836c5 | ||
|
c4681ffc3a |
254 changed files with 57062 additions and 583 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,9 +2,13 @@
|
|||
myapp.conf
|
||||
hypnotoad.pid
|
||||
*.db
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
*.db-journal
|
||||
*~
|
||||
/images
|
||||
*.swp
|
||||
/upload
|
||||
|
||||
cover_db/
|
||||
schema.png
|
||||
|
|
7
.idea/.gitignore
generated
vendored
Normal file
7
.idea/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Default ignored files
|
||||
/workspace.xml
|
||||
/perl5local.xml
|
||||
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
13
.idea/Foodloop-Server.iml
generated
Normal file
13
.idea/Foodloop-Server.iml
generated
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<perl5>
|
||||
<path value="$MODULE_DIR$/lib" type="perl-library" />
|
||||
<path value="$MODULE_DIR$/templates" type="mojo-template" />
|
||||
</perl5>
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
20
.idea/dataSources.xml
generated
Normal file
20
.idea/dataSources.xml
generated
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="foodloop" uuid="7c088e2a-6667-4259-a2b3-9f440345d008">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/foodloop.db</jdbc-url>
|
||||
<driver-properties>
|
||||
<property name="enable_load_extension" value="true" />
|
||||
</driver-properties>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="PostgreSQL foodloop@localhost" uuid="8161e393-4db4-4e03-aa8b-7a961f14a591">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/</jdbc-url>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Foodloop-Server.iml" filepath="$PROJECT_DIR$/.idea/Foodloop-Server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
7
.idea/sqldialects.xml
generated
Normal file
7
.idea/sqldialects.xml
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/share/ddl/PostgreSQL" dialect="PostgreSQL" />
|
||||
<file url="file://$PROJECT_DIR$/share/ddl/SQLite" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,13 +1,18 @@
|
|||
sudo: false
|
||||
|
||||
language: perl
|
||||
|
||||
#addons:
|
||||
# postgresql: "9.6"
|
||||
|
||||
perl:
|
||||
- "5.20"
|
||||
- "5.26"
|
||||
env:
|
||||
- HARNESS_PERL_SWITCHES="-MDevel::Cover"
|
||||
install:
|
||||
- cpanm --quiet --notest --installdeps .
|
||||
- cpanm --quiet --notest --installdeps . #--with-feature=postgres .
|
||||
- cpanm Devel::Cover
|
||||
script:
|
||||
- prove -lr
|
||||
#- PEAR_TEST_PG=1 prove -lr
|
||||
- cover
|
||||
|
|
108
CHANGELOG.md
108
CHANGELOG.md
|
@ -2,11 +2,119 @@
|
|||
|
||||
# Next Release
|
||||
|
||||
# v0.10.10
|
||||
|
||||
* Added proper minion job support
|
||||
* **Admin Feature** Added importing of CSVs from Lancaster City Council
|
||||
* Added pagination support to searching of organisations during transaction submission in API
|
||||
|
||||
# v0.10.9
|
||||
|
||||
* Removed sector list from dashboard stats and swapped it for category list
|
||||
* Added fix to recurring transaction script
|
||||
|
||||
# v0.10.8
|
||||
|
||||
* Added yearly recurring payments
|
||||
|
||||
# v0.10.7
|
||||
|
||||
* Added `cron_daily` script for holding all daily cronjobs
|
||||
* **Admin Fix** Parse currency without a currency symbol on import
|
||||
* **Admin Fix** Fix large CSV issue on import
|
||||
* Use custom secrets for encryption
|
||||
* Made purchase categories easier to pull
|
||||
* Added dashboard data for getting essential for all purchases along with
|
||||
weekly and monthly view of category purchases
|
||||
* Amended tests where relevant
|
||||
|
||||
# v0.10.6
|
||||
|
||||
* Fixed organisation submission
|
||||
* Changed category listing code
|
||||
* Made transaction upload code more lenient
|
||||
* Added API ability to edit and delete transactions
|
||||
* Added test for above
|
||||
* Made test dumping more sane
|
||||
* Fixed quantised transaction calcuations for weeks on sqlite
|
||||
* Amended customer snippet, category list and customer stats tests
|
||||
|
||||
# v0.10.5
|
||||
|
||||
* **Admin Feature** Removed generic Transaction List, replaced with a new
|
||||
transaction statistic viewing list
|
||||
* **Admin Fix** Amended user view to have accordion
|
||||
|
||||
# v0.10.4
|
||||
|
||||
* Added API for category budget
|
||||
* Added working test for the new API
|
||||
* Added initial placeholder API for medals & user points being used in testing
|
||||
* Added initial schema for medals
|
||||
* Added essential flag to purchases in schema
|
||||
* Amended upload API to account for essential purchases
|
||||
* **Admin Feature** Added ability to view essential flag on purchases
|
||||
* Made fixes to category viewing API
|
||||
* Added schema for storing recurring purchases
|
||||
* Amended Upload code to allow for if purchases are recurring
|
||||
* Added script for checking recurring purchases and creating them if required
|
||||
|
||||
# v0.10.3
|
||||
|
||||
* Added Category and Transaction Category tables to DB
|
||||
* Added API for categories in Transactions
|
||||
* **Admin Feature** Added ability to add and delete categories
|
||||
* **Admin Feature** Added ability to view transaction category
|
||||
* Fixed all relevant tests to match
|
||||
|
||||
# v0.10.2
|
||||
|
||||
* Added fairly traded column for organisations
|
||||
* **Admin Fix** Fix issue with setting location on Admin side
|
||||
|
||||
# v0.10.1
|
||||
|
||||
* Added API for customer graphs
|
||||
* Revamped graphs code
|
||||
* Added API for customer local purchase pie charts
|
||||
* Added API for customer snippets
|
||||
* Added API for sector purchase list for customer dashboard
|
||||
* **Admin Fix** Fixed org sector on user edit layout and text
|
||||
* **Admin Feature** Added Sector U
|
||||
|
||||
# v0.10.0
|
||||
|
||||
* **API Change** Updated API for story trail maps
|
||||
* **Admin Feature** Improved links in relevant places to automatically open in
|
||||
a new tab
|
||||
* **Admin Feature** Ability to add ESTA to entity Added
|
||||
* Trail map code updated
|
||||
|
||||
# v0.9.7
|
||||
|
||||
* **Admin Fix**: Fix error in Importing under Postgres
|
||||
* **Admin Feature** Ability to add entity to LIS Added
|
||||
* Added code endpoint for LIS organisations for web app use
|
||||
* Schema updated to account for these changes
|
||||
|
||||
# v0.9.6
|
||||
|
||||
* **Admin Feature** Merged organisation lists into one list
|
||||
* **Admin Feature** Paginated Organisation listings
|
||||
* **Admin Feature** Added flags to Organisations listings
|
||||
* **Admin Feature** Added `is_local` flag to Organisations to start categorising odd stores
|
||||
* **Admin Feature** Feedback items now word wrap
|
||||
* **Admin Feature** Rework transaction viewing
|
||||
* **Admin Feature** Implemented import method for importing previous data from csv
|
||||
* **Admin Feature** Added badges for various organisation flags eg. local, user, validated
|
||||
* **Admin Feature** Enabled merging of organisations to reduce duplicates
|
||||
* **Admin Feature** Added badges to user listing to show whether customer or organisation
|
||||
* **Admin Feature** Added pagination to user listings
|
||||
* Improved logging for debugging issues with login
|
||||
|
||||
# v0.9.5
|
||||
|
||||
* Added leaderboard api for web-app with pagination
|
||||
* Location is now updated on registration. Customers location is truncated to 2
|
||||
decimal places based on their postcode.
|
||||
* Location is also updated on changing a users postcode
|
||||
|
|
126
README.md
126
README.md
|
@ -22,3 +22,129 @@ cpanm --installdeps . --with-feature postgres
|
|||
PEAR_TEST_PG=1 prove -lr
|
||||
```
|
||||
|
||||
# Minion
|
||||
|
||||
to set up minion support, you will need to create a database and user for
|
||||
minion to connect to. In production his should be a PostgreSQL database,
|
||||
however an SQLite db can be used in testing.
|
||||
|
||||
To use the SQLite version, run the following commands:
|
||||
|
||||
```
|
||||
cpanm --installdeps --with-feature sqlite .
|
||||
```
|
||||
|
||||
And then add the following to your configuration file:
|
||||
|
||||
```
|
||||
minion => {
|
||||
SQLite => 'sqlite:minion.db',
|
||||
},
|
||||
```
|
||||
|
||||
This will then use an SQLite db for the minion backend, using `minion.db` as
|
||||
the database file. To start the minion itself, run:
|
||||
|
||||
```
|
||||
./script/pear-local_loop minion worker
|
||||
```
|
||||
|
||||
# Importing Ward Data
|
||||
|
||||
To import ward data, get the ward data csv and then run the following command:
|
||||
|
||||
```shell script
|
||||
./script/pear-local_loop minion job \
|
||||
--enqueue 'csv_postcode_import' \
|
||||
--args '[ "/path/to/ward/csv" ]'
|
||||
```
|
||||
|
||||
# Setting up Entity Postcodes
|
||||
|
||||
Assuming you have imported codepoint open, then to properly assign all
|
||||
postcodes:
|
||||
|
||||
```shell script
|
||||
./script/pear-local_loop minion job \
|
||||
--enqueue entity_postcode_lookup
|
||||
```
|
||||
|
||||
## Example PostgreSQL setup
|
||||
|
||||
```
|
||||
# Example commands - probably not the best ones
|
||||
# TODO come back and improve these with proper ownership and DDL rights
|
||||
sudo -u postgres createuser minion
|
||||
sudo -u postgres createdb localloop_minion
|
||||
sudo -u postgres psql
|
||||
psql=# alter user minion with encrypted password 'abc123';
|
||||
psql=# grant all privileges on database localloop_minion to minion;
|
||||
```
|
||||
|
||||
# Development
|
||||
|
||||
There are a couple of setup steps to getting a development environment ready.
|
||||
Use the corresponding instructions depending on what state your current setup
|
||||
is in.
|
||||
|
||||
## First Time Setup
|
||||
|
||||
First, decide if you're using SQLite or PostgreSQL locally. Development supports
|
||||
both, however production uses PostgreSQL. For this example we will use SQLite.
|
||||
As the default config is set up for this, no configuration changes are
|
||||
needed initially. So, first off, install dependencies:
|
||||
|
||||
```shell script
|
||||
cpanm --installdeps . --with-feature=sqlite
|
||||
```
|
||||
|
||||
Then install the database:
|
||||
|
||||
```shell script
|
||||
./script/deploy_db install -c 'dbi:SQLite:dbname=foodloop.db'
|
||||
```
|
||||
|
||||
Then set up the development users:
|
||||
|
||||
```shell script
|
||||
./script/pear-local_loop dev_data --force
|
||||
```
|
||||
|
||||
***Note: do NOT run that script on production.***
|
||||
|
||||
Then you can start the application:
|
||||
|
||||
```shell script
|
||||
morbo script/pear-local_loop -l http://*:3000
|
||||
```
|
||||
|
||||
You can modify the host and port for listening as needed.
|
||||
|
||||
# Old Docs
|
||||
|
||||
## Local test database
|
||||
|
||||
To install a local DB:
|
||||
|
||||
```
|
||||
./script/deploy_db install -c 'dbi:SQLite:dbname=foodloop.db'
|
||||
```
|
||||
|
||||
To do an upgrade of it after making DB changes to commit:
|
||||
|
||||
```
|
||||
./script/deploy_db write_ddl -c 'dbi:SQLite:dbname=foodloop.db'
|
||||
./script/deploy_db upgrade -c 'dbi:SQLite:dbname=foodloop.db'
|
||||
```
|
||||
|
||||
To redo leaderboards:
|
||||
|
||||
```
|
||||
./script/pear-local_loop recalc_leaderboards
|
||||
```
|
||||
|
||||
To serve a test version locally of the server:
|
||||
|
||||
```
|
||||
morbo script/pear-local_loop
|
||||
```
|
||||
|
|
17
cpanfile
17
cpanfile
|
@ -6,7 +6,6 @@ requires 'Mojo::JSON';
|
|||
requires 'Email::Valid';
|
||||
requires 'Geo::UK::Postcode::Regex' => '0.017';
|
||||
requires 'Authen::Passphrase::BlowfishCrypt';
|
||||
requires 'Time::Fake';
|
||||
requires 'Scalar::Util';
|
||||
requires 'DBIx::Class';
|
||||
requires 'DBIx::Class::PassphraseColumn';
|
||||
|
@ -15,7 +14,6 @@ requires 'DBIx::Class::Schema::Loader';
|
|||
requires 'SQL::Translator';
|
||||
requires 'DateTime';
|
||||
requires 'DateTime::Format::Strptime', "1.73";
|
||||
requires 'DateTime::Format::SQLite';
|
||||
requires 'Try::Tiny';
|
||||
requires 'MooX::Options::Actions';
|
||||
requires 'Module::Runtime';
|
||||
|
@ -24,6 +22,13 @@ requires 'DBIx::Class::Fixtures';
|
|||
requires 'GIS::Distance';
|
||||
requires 'Text::CSV';
|
||||
requires 'Try::Tiny';
|
||||
requires 'Throwable::Error';
|
||||
requires 'Minion';
|
||||
|
||||
on 'test' => sub {
|
||||
requires 'Test::More';
|
||||
requires 'Test::MockTime';
|
||||
};
|
||||
|
||||
feature 'schema-graph', 'Draw diagrams of Schema' => sub {
|
||||
requires 'GraphViz';
|
||||
|
@ -33,9 +38,15 @@ feature 'schema-graph', 'Draw diagrams of Schema' => sub {
|
|||
feature 'postgres', 'PostgreSQL Support' => sub {
|
||||
requires 'DBD::Pg';
|
||||
requires 'Test::PostgreSQL';
|
||||
requires 'Mojo::Pg';
|
||||
requires 'DateTime::Format::Pg';
|
||||
};
|
||||
|
||||
feature 'sqlite', 'SQLite Support' => sub {
|
||||
requires 'Minion::Backend::SQLite';
|
||||
requires 'DateTime::Format::SQLite';
|
||||
};
|
||||
|
||||
feature 'codepoint-open', 'Code Point Open manipulation' => sub {
|
||||
requires 'Geo::UK::Postcode::CodePointOpen';
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ sub startup {
|
|||
$self->plugin('Config', {
|
||||
default => {
|
||||
storage_path => tempdir,
|
||||
upload_path => $self->home->child('upload'),
|
||||
sessionTimeSeconds => 60 * 60 * 24 * 7,
|
||||
sessionTokenJsonName => 'session_key',
|
||||
sessionExpiresJsonName => 'sessionExpires',
|
||||
|
@ -34,13 +35,22 @@ sub startup {
|
|||
});
|
||||
my $config = $self->config;
|
||||
|
||||
if ( defined $config->{secret} ) {
|
||||
$self->secrets([ $config->{secret} ]);
|
||||
} elsif ( $self->mode eq 'production' ) {
|
||||
# Just incase we end up in production and it hasnt been set!
|
||||
$self->secrets([ Data::UUID->new->create() ]);
|
||||
}
|
||||
|
||||
push @{ $self->commands->namespaces }, __PACKAGE__ . '::Command';
|
||||
|
||||
$self->plugin('Pear::LocalLoop::Plugin::BootstrapPagination', { bootstrap4 => 1 } );
|
||||
$self->plugin('Pear::LocalLoop::Plugin::Validators');
|
||||
$self->plugin('Pear::LocalLoop::Plugin::Datetime');
|
||||
$self->plugin('Pear::LocalLoop::Plugin::Currency');
|
||||
$self->plugin('Pear::LocalLoop::Plugin::Postcodes');
|
||||
$self->plugin('Pear::LocalLoop::Plugin::TemplateHelpers');
|
||||
$self->plugin('Pear::LocalLoop::Plugin::Minion');
|
||||
|
||||
$self->plugin('Authentication' => {
|
||||
'load_user' => sub {
|
||||
|
@ -143,19 +153,32 @@ sub startup {
|
|||
});
|
||||
$api->post('/upload')->to('api-upload#post_upload');
|
||||
$api->post('/search')->to('api-upload#post_search');
|
||||
$api->post('/search/category')->to('api-upload#post_category');
|
||||
$api->post('/user')->to('api-user#post_account');
|
||||
$api->post('/user/account')->to('api-user#post_account_update');
|
||||
$api->post('/user-history')->to('api-user#post_user_history');
|
||||
$api->post('/stats')->to('api-stats#post_index');
|
||||
$api->post('/stats/category')->to('api-categories#post_category_list');
|
||||
$api->post('/stats/customer')->to('api-stats#post_customer');
|
||||
$api->post('/stats/organisation')->to('api-stats#post_organisation');
|
||||
$api->post('/stats/leaderboard')->to('api-stats#post_leaderboards');
|
||||
$api->post('/stats/leaderboard/paged')->to('api-stats#post_leaderboards_paged');
|
||||
$api->post('/outgoing-transactions')->to('api-transactions#post_transaction_list_purchases');
|
||||
$api->post('/recurring-transactions')->to('api-transactions#update_recurring');
|
||||
$api->post('/recurring-transactions/delete')->to('api-transactions#delete_recurring');
|
||||
|
||||
|
||||
my $api_v1 = $api->under('/v1');
|
||||
|
||||
my $api_v1_user = $api_v1->under('/user');
|
||||
|
||||
$api_v1_user->post('/medals')->to('api-v1-user-medals#index');
|
||||
$api_v1_user->post('/points')->to('api-v1-user-points#index');
|
||||
|
||||
my $api_v1_supplier = $api_v1->under('/supplier');
|
||||
|
||||
$api_v1_supplier->post('/location')->to('api-v1-supplier-location#index');
|
||||
$api_v1_supplier->post('/location/trail')->to('api-v1-supplier-location#trail_load');
|
||||
|
||||
my $api_v1_org = $api_v1->under('/organisation')->to('api-v1-organisation#auth');
|
||||
|
||||
|
@ -168,8 +191,29 @@ sub startup {
|
|||
$api_v1_org->post('/employee')->to('api-organisation#post_employee_read');
|
||||
$api_v1_org->post('/employee/add')->to('api-organisation#post_employee_add');
|
||||
|
||||
$api_v1_org->post('/external/transactions')->to('api-external#post_lcc_transactions');
|
||||
$api_v1_org->post('/external/suppliers')->to('api-external#post_lcc_suppliers');
|
||||
$api_v1_org->post('/external/year_spend')->to('api-external#post_year_spend');
|
||||
$api_v1_org->post('/external/supplier_count')->to('api-external#post_supplier_count');
|
||||
$api_v1_org->post('/external/supplier_history')->to('api-external#post_supplier_history');
|
||||
$api_v1_org->post('/external/lcc_tables')->to('api-external#post_lcc_table_summary');
|
||||
|
||||
$api_v1_org->post('/pies')->to('api-v1-organisation-pies#index');
|
||||
|
||||
my $api_v1_cust = $api_v1->under('/customer')->to('api-v1-customer#auth');
|
||||
|
||||
$api_v1_cust->post('/graphs')->to('api-v1-customer-graphs#index');
|
||||
$api_v1_cust->post('/snippets')->to('api-v1-customer-snippets#index');
|
||||
$api_v1_cust->post('/pies')->to('api-v1-customer-pies#index');
|
||||
|
||||
my $admin_routes = $r->under('/admin')->to('admin#under');
|
||||
|
||||
if ( defined $config->{minion} ) {
|
||||
$self->plugin( 'Minion::Admin' => {
|
||||
return_to => '/admin/home',
|
||||
route => $admin_routes->any('/minion'),
|
||||
} );
|
||||
}
|
||||
$admin_routes->get('/home')->to('admin#home');
|
||||
|
||||
$admin_routes->get('/tokens')->to('admin-tokens#index');
|
||||
|
@ -178,6 +222,12 @@ sub startup {
|
|||
$admin_routes->post('/tokens/:id')->to('admin-tokens#update');
|
||||
$admin_routes->post('/tokens/:id/delete')->to('admin-tokens#delete');
|
||||
|
||||
$admin_routes->get('/categories')->to('admin-categories#index');
|
||||
$admin_routes->post('/categories')->to('admin-categories#create');
|
||||
$admin_routes->get('/categories/:id')->to('admin-categories#read');
|
||||
$admin_routes->post('/categories/:id')->to('admin-categories#update');
|
||||
$admin_routes->post('/categories/:id/delete')->to('admin-categories#delete');
|
||||
|
||||
$admin_routes->get('/users')->to('admin-users#index');
|
||||
$admin_routes->get('/users/:id')->to('admin-users#read');
|
||||
$admin_routes->post('/users/:id')->to('admin-users#update');
|
||||
|
@ -188,6 +238,9 @@ sub startup {
|
|||
$admin_routes->post('/organisations/add')->to('admin-organisations#add_org_submit');
|
||||
$admin_routes->get('/organisations/:id')->to('admin-organisations#valid_read');
|
||||
$admin_routes->post('/organisations/:id')->to('admin-organisations#valid_edit');
|
||||
$admin_routes->get('/organisations/:id/merge')->to('admin-organisations#merge_list');
|
||||
$admin_routes->get('/organisations/:id/merge/:target_id')->to('admin-organisations#merge_detail');
|
||||
$admin_routes->post('/organisations/:id/merge/:target_id')->to('admin-organisations#merge_confirm');
|
||||
|
||||
$admin_routes->get('/feedback')->to('admin-feedback#index');
|
||||
$admin_routes->get('/feedback/:id')->to('admin-feedback#read');
|
||||
|
@ -206,10 +259,16 @@ sub startup {
|
|||
$admin_routes->get('/import/:set_id')->to('admin-import#list');
|
||||
$admin_routes->get('/import/:set_id/user')->to('admin-import#get_user');
|
||||
$admin_routes->get('/import/:set_id/org')->to('admin-import#get_org');
|
||||
$admin_routes->post('/import/:set_id/org')->to('admin-import#set_org');
|
||||
|
||||
$admin_routes->get('/import/:set_id/:value_id')->to('admin-import#get_value');
|
||||
$admin_routes->post('/import/:set_id/:value_id')->to('admin-import#post_value');
|
||||
$admin_routes->get('/import/:set_id/ignore/:value_id')->to('admin-import#ignore_value');
|
||||
$admin_routes->get('/import/:set_id/import')->to('admin-import#run_import');
|
||||
|
||||
$admin_routes->get('/import_from')->to('admin-import_from#index');
|
||||
$admin_routes->post('/import_from/suppliers')->to('admin-import_from#post_suppliers');
|
||||
$admin_routes->post('/import_from/transactions')->to('admin-import_from#post_transactions');
|
||||
$admin_routes->post('/import_from/postcodes')->to('admin-import_from#post_postcodes');
|
||||
$admin_routes->get('/import_from/org_search')->to('admin-import_from#org_search');
|
||||
|
||||
# my $user_routes = $r->under('/')->to('root#under');
|
||||
|
||||
# $user_routes->get('/home')->to('root#home');
|
||||
|
@ -220,9 +279,9 @@ sub startup {
|
|||
# $portal_api->post('/search')->to('api-upload#post_search');
|
||||
|
||||
$self->hook( before_dispatch => sub {
|
||||
my $self = shift;
|
||||
my $c = shift;
|
||||
|
||||
$self->res->headers->header('Access-Control-Allow-Origin' => '*') if $self->app->mode eq 'development';
|
||||
$c->res->headers->header('Access-Control-Allow-Origin' => '*') if $c->app->mode eq 'development';
|
||||
});
|
||||
|
||||
$self->helper( copy_transactions_and_delete => sub {
|
||||
|
|
|
@ -22,7 +22,12 @@ sub run {
|
|||
|
||||
unless ( -d $output_dir ) {
|
||||
print "Unzipping code-point-open data\n" unless $quiet_mode;
|
||||
system( 'unzip', '-q', $zip_file, '-d', $output_dir );
|
||||
eval { system( 'unzip', '-q', $zip_file, '-d', $output_dir ) };
|
||||
if ( my $err = $@ ) {
|
||||
print "Error extracting zip: " . $err . "\n";
|
||||
print "Manually create etc/code-point-open/codepo_gb directory and extract zip into it";
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
my $cpo = Geo::UK::Postcode::CodePointOpen->new( path => $output_dir );
|
||||
|
|
|
@ -28,45 +28,57 @@ sub run {
|
|||
$schema->resultset('User')->create({
|
||||
email => 'test@example.com',
|
||||
password => 'abc123',
|
||||
customer => {
|
||||
full_name => 'Test User',
|
||||
display_name => 'Test User',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
entity => {
|
||||
type => 'customer',
|
||||
customer => {
|
||||
full_name => 'Test User',
|
||||
display_name => 'Test User',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
}
|
||||
},
|
||||
administrator => {},
|
||||
is_admin => 1,
|
||||
});
|
||||
|
||||
$schema->resultset('User')->create({
|
||||
email => 'test2@example.com',
|
||||
password => 'abc123',
|
||||
customer => {
|
||||
full_name => 'Test User 2',
|
||||
display_name => 'Test User 2',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
entity => {
|
||||
type => 'customer',
|
||||
customer => {
|
||||
full_name => 'Test User 2',
|
||||
display_name => 'Test User 2',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
$schema->resultset('User')->create({
|
||||
email => 'test3@example.com',
|
||||
password => 'abc123',
|
||||
customer => {
|
||||
full_name => 'Test User 3',
|
||||
display_name => 'Test User 3',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
entity => {
|
||||
type => 'customer',
|
||||
customer => {
|
||||
full_name => 'Test User 3',
|
||||
display_name => 'Test User 3',
|
||||
year_of_birth => 2006,
|
||||
postcode => 'LA1 1AA',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
$schema->resultset('User')->create({
|
||||
email => 'testorg@example.com',
|
||||
password => 'abc123',
|
||||
organisation => {
|
||||
name => 'Test Org',
|
||||
street_name => 'Test Street',
|
||||
town => 'Lancaster',
|
||||
postcode => 'LA1 1AA',
|
||||
entity => {
|
||||
type => 'organisation',
|
||||
organisation => {
|
||||
name => 'Test Org',
|
||||
street_name => 'Test Street',
|
||||
town => 'Lancaster',
|
||||
postcode => 'LA1 1AA',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
140
lib/Pear/LocalLoop/Command/recur_transactions.pm
Normal file
140
lib/Pear/LocalLoop/Command/recur_transactions.pm
Normal file
|
@ -0,0 +1,140 @@
|
|||
package Pear::LocalLoop::Command::recur_transactions;
|
||||
use Mojo::Base 'Mojolicious::Command';
|
||||
|
||||
use Mojo::Util 'getopt';
|
||||
|
||||
use DateTime;
|
||||
use DateTime::Format::Strptime;
|
||||
|
||||
has description => 'Recur Transactions';
|
||||
|
||||
has usage => sub { shift->extract_usage };
|
||||
|
||||
sub run {
|
||||
my ( $self, @args ) = @_;
|
||||
|
||||
my $app = $self->app;
|
||||
|
||||
getopt \@args,
|
||||
'f|force' => \my $force,
|
||||
'd|date=s' => \my $date;
|
||||
|
||||
unless ( defined $force ) {
|
||||
say "Will not do anything without force option";
|
||||
return;
|
||||
}
|
||||
|
||||
my $date_formatter = DateTime::Format::Strptime->new(
|
||||
pattern => '%Y-%m-%d'
|
||||
);
|
||||
|
||||
my $datetime;
|
||||
|
||||
if ( defined $date ) {
|
||||
|
||||
$datetime = $date_formatter->parse_datetime($date);
|
||||
|
||||
unless ( defined $datetime ) {
|
||||
say "Unrecognised date format, please use 'YYYY-MM-DD' Format";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$datetime = DateTime->today;
|
||||
}
|
||||
|
||||
my $match_date_day = $app->format_iso_date($datetime->clone->subtract( days => 1 ));
|
||||
my $match_date_week = $app->format_iso_date($datetime->clone->subtract( weeks => 1 ));
|
||||
my $match_date_fortnight = $app->format_iso_date($datetime->clone->subtract( weeks => 2 ));
|
||||
my $match_date_month = $app->format_iso_date($datetime->clone->subtract( months => 1 ));
|
||||
my $match_date_quarter = $app->format_iso_date($datetime->clone->subtract( months => 3));
|
||||
my $match_date_year = $app->format_iso_date($datetime->clone->subtract( years => 1 ));
|
||||
|
||||
my $schema = $app->schema;
|
||||
my $dtf = $schema->storage->datetime_parser;
|
||||
my $recur_rs = $schema->resultset('TransactionRecurring');
|
||||
|
||||
for my $recur_result ( $recur_rs->all ) {
|
||||
|
||||
my $start_time_dt;
|
||||
if ( defined $recur_result->last_updated ) {
|
||||
$start_time_dt = $recur_result->last_updated;
|
||||
} else {
|
||||
$start_time_dt = $recur_result->start_time;
|
||||
}
|
||||
my $start_time = $app->format_iso_date($start_time_dt);
|
||||
my $recurring_period = $recur_result->recurring_period;
|
||||
|
||||
if ( $recurring_period eq 'daily' ) {
|
||||
next unless $start_time eq $match_date_day;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to daily";
|
||||
} elsif ( $recurring_period eq 'weekly' ) {
|
||||
next unless $start_time eq $match_date_week;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to weekly";
|
||||
} elsif ( $recurring_period eq 'fortnightly' ) {
|
||||
next unless $start_time eq $match_date_fortnight;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to fortnightly";
|
||||
} elsif ( $recurring_period eq 'monthly' ) {
|
||||
next unless $start_time eq $match_date_month;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to monthly";
|
||||
} elsif ( $recurring_period eq 'quarterly' ) {
|
||||
next unless $start_time eq $match_date_quarter;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to quarterly";
|
||||
} elsif ( $recurring_period eq 'yearly' ) {
|
||||
next unless $start_time eq $match_date_year;
|
||||
say "matched recurring transaction ID " . $recur_result->id . " to yearly";
|
||||
} else {
|
||||
say "Invalid recurring time period given";
|
||||
return;
|
||||
}
|
||||
|
||||
my $purchase_time = DateTime->new(
|
||||
year => $datetime->year,
|
||||
month => $datetime->month,
|
||||
day => $datetime->day,
|
||||
hour => $start_time_dt->hour,
|
||||
minute => $start_time_dt->minute,
|
||||
second => $start_time_dt->second,
|
||||
time_zone => 'UTC',
|
||||
);
|
||||
my $category = $recur_result->category_id;
|
||||
my $essential = $recur_result->essential;
|
||||
my $distance = $recur_result->distance;
|
||||
|
||||
my $new_transaction = $schema->resultset('Transaction')->create({
|
||||
buyer_id => $recur_result->buyer_id,
|
||||
seller_id => $recur_result->seller_id,
|
||||
value => $recur_result->value,
|
||||
purchase_time => $app->format_db_datetime($purchase_time),
|
||||
distance => $distance,
|
||||
essential => ( defined $essential ? $essential : 0 ),
|
||||
});
|
||||
|
||||
unless ( defined $new_transaction ) {
|
||||
say "Error Adding Transaction";
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined $category ) {
|
||||
$schema->resultset('TransactionCategory')->create({
|
||||
category_id => $category,
|
||||
transaction_id => $new_transaction->id,
|
||||
});
|
||||
}
|
||||
|
||||
$recur_result->update({ last_updated => $purchase_time });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Usage: APPLICATION recur_transactions [OPTIONS]
|
||||
|
||||
Options:
|
||||
|
||||
-f, --force Actually insert the data
|
||||
-d, --date Date to recur the transactions on
|
||||
|
||||
=cut
|
||||
|
||||
1;
|
|
@ -38,9 +38,12 @@ sub home {
|
|||
sub auth_login {
|
||||
my $c = shift;
|
||||
|
||||
$c->app->log->debug( __PACKAGE__ . " admin login attempt for [" . $c->param('email') . "]" );
|
||||
|
||||
if ( $c->authenticate($c->param('email'), $c->param('password')) ) {
|
||||
$c->redirect_to('/admin/home');
|
||||
} else {
|
||||
$c->app->log->info( __PACKAGE__ . " failed admin login for [" . $c->param('email') . "]" );
|
||||
$c->redirect_to('/admin');
|
||||
}
|
||||
}
|
||||
|
|
100
lib/Pear/LocalLoop/Controller/Admin/Categories.pm
Normal file
100
lib/Pear/LocalLoop/Controller/Admin/Categories.pm
Normal file
|
@ -0,0 +1,100 @@
|
|||
package Pear::LocalLoop::Controller::Admin::Categories;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
has result_set => sub {
|
||||
my $c = shift;
|
||||
return $c->schema->resultset('Category');
|
||||
};
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $category_rs = $c->result_set;
|
||||
$category_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
|
||||
$c->stash( categories => [ $category_rs->all ] );
|
||||
}
|
||||
|
||||
# POST
|
||||
sub create {
|
||||
my $c = shift;
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->required('category', 'trim')->not_in_resultset('name', $c->result_set);
|
||||
|
||||
my $category_name = $validation->param('category');
|
||||
|
||||
if ( $validation->has_error ) {
|
||||
my $check = shift @{ $c->validation->error('category') };
|
||||
if ( $check eq 'required' ) {
|
||||
$c->flash( error => 'Category name is required' );
|
||||
} elsif ( $check eq 'like' ) {
|
||||
$c->flash( error => 'Category name not valid - Alphanumeric characters and Underscore only' );
|
||||
} elsif ( $check eq 'not_in_resultset' ) {
|
||||
$c->flash( error => 'Category Already Exists' );
|
||||
}
|
||||
} else {
|
||||
$c->flash( success => 'Category Created' );
|
||||
$c->result_set->create({ name => $category_name });
|
||||
}
|
||||
$c->redirect_to( '/admin/categories' );
|
||||
}
|
||||
|
||||
# GET
|
||||
sub read {
|
||||
my $c = shift;
|
||||
|
||||
my $id = $c->param('id');
|
||||
|
||||
if ( my $category = $c->result_set->find($id) ) {
|
||||
$c->stash( category => $category );
|
||||
} else {
|
||||
$c->flash( error => 'No Category found' );
|
||||
$c->redirect_to( '/admin/categories' );
|
||||
}
|
||||
}
|
||||
|
||||
# POST
|
||||
sub update {
|
||||
my $c = shift;
|
||||
my $validation = $c->validation;
|
||||
$validation->required('id');
|
||||
$validation->required('category', 'trim')->like(qr/^[\w]*$/);
|
||||
$validation->optional('line_icon');
|
||||
|
||||
my $id = $c->param('id');
|
||||
|
||||
if ( $validation->has_error ) {
|
||||
my $names = $validation->failed;
|
||||
$c->flash( error => 'Error in submitted data: ' . join(', ', @$names) );
|
||||
$c->redirect_to( '/admin/categories/' . $id );
|
||||
} elsif ( my $category = $c->result_set->find($id) ) {
|
||||
$category->update({
|
||||
id => $validation->param('id'),
|
||||
name => $validation->param('category'),
|
||||
line_icon => (defined $validation->param('line_icon') ? $validation->param('line_icon') : undef ),
|
||||
});
|
||||
$c->flash( success => 'Category Updated' );
|
||||
$c->redirect_to( '/admin/categories/' . $validation->param('id') );
|
||||
} else {
|
||||
$c->flash( error => 'No Category found' );
|
||||
$c->redirect_to( '/admin/categories' );
|
||||
}
|
||||
}
|
||||
|
||||
# DELETE
|
||||
sub delete {
|
||||
my $c = shift;
|
||||
|
||||
my $id = $c->param('id');
|
||||
|
||||
if ( my $category = $c->result_set->find($id) ) {
|
||||
$category->transaction_category->delete;
|
||||
$category->delete;
|
||||
$c->flash( success => 'Category Deleted' );
|
||||
} else {
|
||||
$c->flash( error => 'No Category found' );
|
||||
}
|
||||
$c->redirect_to( '/admin/categories' );
|
||||
}
|
||||
|
||||
1;
|
|
@ -27,10 +27,13 @@ sub list {
|
|||
my $c = shift;
|
||||
my $set_id = $c->param('set_id');
|
||||
|
||||
my $include_ignored = $c->param('ignored');
|
||||
my $include_imported = $c->param('imported');
|
||||
|
||||
my $import_set = $c->result_set->find($set_id);
|
||||
my $import_value_rs = $c->result_set->get_values($set_id);
|
||||
my $import_users_rs = $c->result_set->get_users($set_id);
|
||||
my $import_org_rs = $c->result_set->get_orgs($set_id);
|
||||
my $import_value_rs = $c->result_set->get_values($set_id, $include_ignored, $include_imported);
|
||||
my $import_users_rs = $c->result_set->get_users($set_id, $include_ignored, $include_imported);
|
||||
my $import_org_rs = $c->result_set->get_orgs($set_id, $include_ignored, $include_imported);
|
||||
my $import_lookup_rs = $c->result_set->get_lookups($set_id);
|
||||
|
||||
$c->stash(
|
||||
|
@ -69,7 +72,7 @@ sub post_add {
|
|||
};
|
||||
|
||||
if ( defined $error ) {
|
||||
$c->flash( error => $error, csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( $error );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -78,7 +81,7 @@ sub post_add {
|
|||
my @required = grep {/^user$|^value$|^date$|^organisation$/} @csv_headers;
|
||||
|
||||
unless ( scalar( @required ) == 4 ) {
|
||||
$c->flash( error => 'Required columns not available', csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( 'Required columns not available' );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -86,7 +89,7 @@ sub post_add {
|
|||
my $csv_output = $csv->getline_hr_all( $fh );
|
||||
|
||||
unless ( scalar( @$csv_output ) ) {
|
||||
$c->flash( error => "No data found", csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( "No data found" );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -94,7 +97,7 @@ sub post_add {
|
|||
for my $data ( @$csv_output ) {
|
||||
for my $key ( qw/ user value organisation / ) {
|
||||
unless ( defined $data->{$key} ) {
|
||||
$c->flash( error => "Undefined [$key] data found", csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( "Undefined [$key] data found" );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -103,7 +106,7 @@ sub post_add {
|
|||
my $dtp = DateTime::Format::Strptime->new( pattern => $date_format );
|
||||
my $dt_obj = $dtp->parse_datetime($data->{date});
|
||||
unless ( defined $dt_obj ) {
|
||||
$c->flash( error => "Undefined or incorrect format for [date] data found", csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( "Undefined or incorrect format for [date] data found" );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -126,7 +129,7 @@ sub post_add {
|
|||
);
|
||||
|
||||
unless ( defined $value_set ) {
|
||||
$c->flash( error => 'Error creating new Value Set', csv_data => $csv_data, date_format => $date_format );
|
||||
$c->_csv_flash_error( 'Error creating new Value Set' );
|
||||
$c->redirect_to( '/admin/import/add' );
|
||||
return;
|
||||
}
|
||||
|
@ -135,6 +138,18 @@ sub post_add {
|
|||
$c->redirect_to( '/admin/import/' . $value_set->id );
|
||||
}
|
||||
|
||||
sub _csv_flash_error {
|
||||
my ( $c, $error ) = @_;
|
||||
$error //= "An error occurred";
|
||||
|
||||
$c->flash(
|
||||
error => $error,
|
||||
# If csv info is huge, this fails epically
|
||||
#csv_data => $c->param('csv'),
|
||||
date_format => $c->param('date_format'),
|
||||
);
|
||||
}
|
||||
|
||||
sub get_user {
|
||||
my $c = shift;
|
||||
my $set_id = $c->param('set_id');
|
||||
|
@ -184,22 +199,128 @@ sub get_user {
|
|||
|
||||
sub get_org {
|
||||
my $c = shift;
|
||||
my $set_id = $c->param('set_id');
|
||||
my $org_name = $c->param('org');
|
||||
|
||||
my $values_rs = $c->result_set->find($set_id)->values->search(
|
||||
{
|
||||
org_name => $org_name,
|
||||
ignore_value => 0,
|
||||
}
|
||||
);
|
||||
|
||||
unless ( $values_rs->count > 0 ) {
|
||||
$c->flash( error => 'Organisation not found or all values are ignored' );
|
||||
return $c->redirect_to( '/admin/import/' . $set_id );
|
||||
}
|
||||
|
||||
my $lookup_result = $c->result_set->find($set_id)->lookups->find(
|
||||
{ name => $org_name },
|
||||
);
|
||||
|
||||
my $entity_id = $c->param('entity');
|
||||
|
||||
my $orgs_rs = $c->schema->resultset('Organisation');
|
||||
|
||||
if ( defined $entity_id && $orgs_rs->find({ entity_id => $entity_id }) ) {
|
||||
if ( defined $lookup_result ) {
|
||||
$lookup_result->update({ entity_id => $entity_id });
|
||||
} else {
|
||||
$lookup_result = $c->result_set->find($set_id)->lookups->create(
|
||||
{
|
||||
name => $org_name,
|
||||
entity_id => $entity_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
} elsif ( defined $entity_id ) {
|
||||
$c->stash( error => "Organisation does not exist" );
|
||||
}
|
||||
|
||||
$c->stash(
|
||||
orgs_rs => $orgs_rs,
|
||||
lookup => $lookup_result,
|
||||
org_name => $org_name,
|
||||
);
|
||||
}
|
||||
|
||||
sub set_org {
|
||||
my $c = shift;
|
||||
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
sub ignore_value {
|
||||
my $c = shift;
|
||||
my $set_id = $c->param('set_id');
|
||||
my $value_id = $c->param('value_id');
|
||||
|
||||
my $set_result = $c->result_set->find($set_id);
|
||||
unless ( defined $set_result ) {
|
||||
$c->flash( error => "Set does not exist" );
|
||||
return $c->redirect_to( '/admin/import' );
|
||||
}
|
||||
|
||||
my $value_result = $set_result->values->find($value_id);
|
||||
unless ( defined $value_result ) {
|
||||
$c->flash( error => "Value does not exist" );
|
||||
return $c->redirect_to( '/admin/import/' . $set_id );
|
||||
}
|
||||
|
||||
$value_result->update({ ignore_value => $value_result->ignore_value ? 0 : 1 });
|
||||
|
||||
$c->flash( success => "Updated value" );
|
||||
my $referer = $c->req->headers->header('Referer');
|
||||
return $c->redirect_to(
|
||||
defined $referer
|
||||
? $c->url_for($referer)->path_query
|
||||
: '/admin/import/' . $set_id
|
||||
);
|
||||
}
|
||||
|
||||
sub post_value {
|
||||
sub run_import {
|
||||
my $c = shift;
|
||||
my $set_id = $c->param('set_id');
|
||||
|
||||
my $set_result = $c->result_set->find($set_id);
|
||||
unless ( defined $set_result ) {
|
||||
$c->flash( error => "Set does not exist" );
|
||||
return $c->redirect_to( '/admin/import' );
|
||||
}
|
||||
|
||||
my $import_value_rs = $c->result_set->get_values($set_id, undef, undef);
|
||||
my $import_lookup = $c->result_set->get_lookups($set_id);
|
||||
my $entity_rs = $c->schema->resultset('Entity');
|
||||
|
||||
$c->schema->txn_do(
|
||||
sub {
|
||||
for my $value_result ( $import_value_rs->all ) {
|
||||
my $user_lookup = $import_lookup->{ $value_result->user_name };
|
||||
my $org_lookup = $import_lookup->{ $value_result->org_name };
|
||||
my $value_lookup = $c->parse_currency( $value_result->purchase_value );
|
||||
|
||||
if ( defined $user_lookup && defined $org_lookup && $value_lookup ) {
|
||||
my $user_entity = $entity_rs->find($user_lookup->{entity_id});
|
||||
my $org_entity = $entity_rs->find($org_lookup->{entity_id});
|
||||
my $distance = $c->get_distance_from_coords( $user_entity->type_object, $org_entity->type_object );
|
||||
my $transaction = $c->schema->resultset('Transaction')->create(
|
||||
{
|
||||
buyer => $user_entity,
|
||||
seller => $org_entity,
|
||||
value => $value_lookup * 100000,
|
||||
purchase_time => $value_result->purchase_date,
|
||||
distance => $distance,
|
||||
}
|
||||
);
|
||||
$value_result->update({transaction_id => $transaction->id });
|
||||
} else {
|
||||
$c->app->log->warn("Failed value import for value id [" . $value_result->id . "], ignoring");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$c->flash( success => "Import completed for ready values" );
|
||||
my $referer = $c->req->headers->header('Referer');
|
||||
return $c->redirect_to(
|
||||
defined $referer
|
||||
? $c->url_for($referer)->path_query
|
||||
: '/admin/import/' . $set_id
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
127
lib/Pear/LocalLoop/Controller/Admin/ImportFrom.pm
Normal file
127
lib/Pear/LocalLoop/Controller/Admin/ImportFrom.pm
Normal file
|
@ -0,0 +1,127 @@
|
|||
package Pear::LocalLoop::Controller::Admin::ImportFrom;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
use Moo;
|
||||
use Try::Tiny;
|
||||
use Mojo::File qw/path/;
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
$c->stash->{org_entities} = [
|
||||
map {
|
||||
{ id => $_->entity_id, name => $_->name }
|
||||
} $c->schema->resultset('Organisation')->search({ name => { like => '%lancashire%' }}, { columns => [qw/ entity_id name / ]})
|
||||
];
|
||||
|
||||
$c->app->max_request_size(104857600);
|
||||
}
|
||||
|
||||
sub post_suppliers {
|
||||
my $c = shift;
|
||||
|
||||
unless ($c->param('suppliers_csv')) {
|
||||
$c->flash(error => "No CSV file given");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
# Check file size
|
||||
if ($c->req->is_limit_exceeded) {
|
||||
$c->flash(error => "CSV file size is too large");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
my $file = $c->param('suppliers_csv');
|
||||
|
||||
my $filename = path($c->app->config->{upload_path}, time . 'suppliers.csv');
|
||||
|
||||
$file->move_to($filename);
|
||||
|
||||
my $job_id = $c->minion->enqueue('csv_supplier_import' => [ $filename ]);
|
||||
|
||||
my $job_url = $c->url_for("/admin/minion/jobs?id=$job_id")->to_abs;
|
||||
|
||||
$c->flash(success => "CSV import started, see status of minion job at: $job_url");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
sub post_postcodes {
|
||||
my $c = shift;
|
||||
|
||||
unless ($c->param('postcodes_csv')) {
|
||||
$c->flash(error => "No CSV file given");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
# Check file size
|
||||
if ($c->req->is_limit_exceeded) {
|
||||
$c->flash(error => "CSV file size is too large");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
my $file = $c->param('postcodes_csv');
|
||||
|
||||
my $filename = path($c->app->config->{upload_path}, time . 'postcodes.csv');
|
||||
|
||||
$file->move_to($filename);
|
||||
|
||||
my $job_id = $c->minion->enqueue('csv_postcode_import' => [ $filename ]);
|
||||
|
||||
my $job_url = $c->url_for("/admin/minion/jobs?id=$job_id")->to_abs;
|
||||
|
||||
$c->flash(success => "CSV import started, see status of minion job at: $job_url");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
sub post_transactions {
|
||||
my $c = shift;
|
||||
|
||||
unless ($c->param('entity_id') ne '') {
|
||||
$c->flash(error => "Please Choose an organisation");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
unless ($c->param('transactions_csv')) {
|
||||
$c->flash(error => "No CSV file given");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
# Check file size
|
||||
if ($c->req->is_limit_exceeded) {
|
||||
$c->flash(error => "CSV file size is too large");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
my $file = $c->param('transactions_csv');
|
||||
|
||||
my $filename = path($c->app->config->{upload_path}, time . 'transactions.csv');
|
||||
|
||||
$file->move_to($filename);
|
||||
|
||||
my $job_id = $c->minion->enqueue('csv_transaction_import' => [ $filename, $c->param('entity_id') ]);
|
||||
|
||||
my $job_url = $c->url_for("/admin/minion/jobs?id=$job_id")->to_abs;
|
||||
|
||||
$c->flash(success => "CSV import started, see status of minion job at: $job_url");
|
||||
return $c->redirect_to('/admin/import_from');
|
||||
}
|
||||
|
||||
sub org_search {
|
||||
my $c = shift;
|
||||
my $term = $c->param('term');
|
||||
|
||||
my $rs = $c->schema->resultset('Organisation')->search(
|
||||
{ name => { like => $term . '%' } },
|
||||
{
|
||||
join => 'entity',
|
||||
columns => [ qw/ me.name entity.id / ]
|
||||
},
|
||||
);
|
||||
|
||||
my @results = ( map { {
|
||||
label => $_->name,
|
||||
value => $_->entity->id,
|
||||
} } $rs->all);
|
||||
|
||||
$c->render( json => \@results );
|
||||
}
|
||||
|
||||
1;
|
|
@ -3,6 +3,11 @@ use Mojo::Base 'Mojolicious::Controller';
|
|||
|
||||
use Try::Tiny;
|
||||
|
||||
has result_set => sub {
|
||||
my $c = shift;
|
||||
return $c->schema->resultset('Organisation');
|
||||
};
|
||||
|
||||
sub list {
|
||||
my $c = shift;
|
||||
|
||||
|
@ -36,6 +41,7 @@ sub add_org_submit {
|
|||
$validation->optional('postcode')->postcode;
|
||||
$validation->optional('pending');
|
||||
$validation->optional('is_local');
|
||||
$validation->optional('is_fair');
|
||||
|
||||
if ( $validation->has_error ) {
|
||||
$c->flash( error => 'The validation has failed' );
|
||||
|
@ -44,6 +50,11 @@ sub add_org_submit {
|
|||
|
||||
my $organisation;
|
||||
|
||||
my $location = $c->get_location_from_postcode(
|
||||
$validation->param('postcode'),
|
||||
'organisation',
|
||||
);
|
||||
|
||||
try {
|
||||
my $entity = $c->schema->resultset('Entity')->create({
|
||||
organisation => {
|
||||
|
@ -52,9 +63,11 @@ sub add_org_submit {
|
|||
town => $validation->param('town'),
|
||||
sector => $validation->param('sector'),
|
||||
postcode => $validation->param('postcode'),
|
||||
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
|
||||
submitted_by_id => $c->current_user->id,
|
||||
pending => defined $validation->param('pending') ? 0 : 1,
|
||||
is_local => $validation->param('is_local'),
|
||||
is_fair => $validation->param('is_fair'),
|
||||
},
|
||||
type => 'organisation',
|
||||
});
|
||||
|
@ -73,16 +86,23 @@ sub add_org_submit {
|
|||
sub valid_read {
|
||||
my $c = shift;
|
||||
my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') );
|
||||
my $transactions = $valid_org->entity->sales->search(
|
||||
my $transactions = $valid_org->entity->purchases->search(
|
||||
undef, {
|
||||
page => $c->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => { -desc => 'submitted_at' },
|
||||
},
|
||||
);
|
||||
my $associations = $valid_org->entity->associations;
|
||||
my $assoc = {
|
||||
lis => defined $associations ? $associations->lis : 0,
|
||||
esta => defined $associations ? $associations->esta : 0,
|
||||
};
|
||||
|
||||
$c->stash(
|
||||
valid_org => $valid_org,
|
||||
transactions => $transactions,
|
||||
associations => $assoc,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -91,12 +111,15 @@ sub valid_edit {
|
|||
|
||||
my $validation = $c->validation;
|
||||
$validation->required('name');
|
||||
$validation->required('street_name');
|
||||
$validation->optional('street_name');
|
||||
$validation->required('town');
|
||||
$validation->optional('sector');
|
||||
$validation->required('postcode')->postcode;
|
||||
$validation->optional('pending');
|
||||
$validation->optional('is_local');
|
||||
$validation->optional('is_fair');
|
||||
$validation->optional('is_lis');
|
||||
$validation->optional('is_esta');
|
||||
|
||||
if ( $validation->has_error ) {
|
||||
$c->flash( error => 'The validation has failed' );
|
||||
|
@ -105,6 +128,11 @@ sub valid_edit {
|
|||
|
||||
my $valid_org = $c->schema->resultset('Organisation')->find( $c->param('id') );
|
||||
|
||||
my $location = $c->get_location_from_postcode(
|
||||
$validation->param('postcode'),
|
||||
'organisation',
|
||||
);
|
||||
|
||||
try {
|
||||
$c->schema->storage->txn_do( sub {
|
||||
$valid_org->update({
|
||||
|
@ -113,18 +141,124 @@ sub valid_edit {
|
|||
town => $validation->param('town'),
|
||||
sector => $validation->param('sector'),
|
||||
postcode => $validation->param('postcode'),
|
||||
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
|
||||
pending => defined $validation->param('pending') ? 0 : 1,
|
||||
is_local => $validation->param('is_local'),
|
||||
is_fair => $validation->param('is_fair'),
|
||||
});
|
||||
$valid_org->entity->update_or_create_related( 'associations', {
|
||||
lis => $validation->param('is_lis'),
|
||||
esta => $validation->param('is_esta')
|
||||
});
|
||||
} );
|
||||
} finally {
|
||||
if ( @_ ) {
|
||||
if ( @_ ) {use Devel::Dwarn; Dwarn \@_;
|
||||
$c->flash( error => 'Something went wrong Updating the Organisation' );
|
||||
} else {
|
||||
$c->flash( success => 'Updated Organisation' );
|
||||
}
|
||||
};
|
||||
$c->redirect_to( '/admin/organisations/');
|
||||
$c->redirect_to( '/admin/organisations/' . $c->param('id') );
|
||||
}
|
||||
|
||||
sub merge_list {
|
||||
my $c = shift;
|
||||
|
||||
my $org_id = $c->param('id');
|
||||
my $org_result = $c->result_set->find($org_id);
|
||||
|
||||
if ( defined $org_result->entity->user ) {
|
||||
$c->flash( error => 'Cannot merge from user-owned organisation!' );
|
||||
$c->redirect_to( '/admin/organisations/' . $org_id );
|
||||
return;
|
||||
}
|
||||
|
||||
my $org_rs = $c->result_set->search(
|
||||
{
|
||||
id => { '!=' => $org_id },
|
||||
},
|
||||
{
|
||||
page => $c->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => { '-asc' => 'name' },
|
||||
}
|
||||
);
|
||||
|
||||
$c->stash(
|
||||
org_result => $org_result,
|
||||
org_rs => $org_rs,
|
||||
);
|
||||
}
|
||||
|
||||
sub merge_detail {
|
||||
my $c = shift;
|
||||
|
||||
my $org_id = $c->param('id');
|
||||
my $org_result = $c->result_set->find($org_id);
|
||||
|
||||
if ( defined $org_result->entity->user ) {
|
||||
$c->flash( error => 'Cannot merge from user-owned organisation!' );
|
||||
$c->redirect_to( '/admin/organisations/' . $org_id );
|
||||
return;
|
||||
}
|
||||
|
||||
my $target_id = $c->param('target_id');
|
||||
my $target_result = $c->result_set->find($target_id);
|
||||
|
||||
unless ( defined $target_result ) {
|
||||
$c->flash( error => 'Unknown target organisation' );
|
||||
$c->redirect_to( '/admin/organisations/' . $org_id . '/merge' );
|
||||
return;
|
||||
}
|
||||
|
||||
$c->stash(
|
||||
org_result => $org_result,
|
||||
target_result => $target_result,
|
||||
);
|
||||
}
|
||||
|
||||
sub merge_confirm {
|
||||
my $c = shift;
|
||||
|
||||
my $org_id = $c->param('id');
|
||||
my $org_result = $c->result_set->find($org_id);
|
||||
|
||||
if ( defined $org_result->entity->user ) {
|
||||
$c->flash( error => 'Cannot merge from user-owned organisation!' );
|
||||
$c->redirect_to( '/admin/organisations/' . $org_id );
|
||||
return;
|
||||
}
|
||||
|
||||
my $target_id = $c->param('target_id');
|
||||
my $target_result = $c->result_set->find($target_id);
|
||||
my $confirm = $c->param('confirm');
|
||||
|
||||
if ( $confirm eq 'checked' && defined $org_result && defined $target_result ) {
|
||||
try {
|
||||
$c->schema->txn_do( sub {
|
||||
# Done as an update, not update_all, so its damn fast - we're only
|
||||
# editing an id which is guaranteed to be an integer here, and this
|
||||
# makes it only one update statement.
|
||||
$org_result->entity->sales->update(
|
||||
{ seller_id => $target_result->entity->id }
|
||||
);
|
||||
my $count = $org_result->entity->sales->count;
|
||||
die "Failed to migrate all sales" if $count;
|
||||
$org_result->entity->delete;
|
||||
$c->schema->resultset('ImportLookup')->search({ entity_id => $org_result->entity->id })->delete;
|
||||
my $org_count = $c->result_set->search({id => $org_result->id })->count;
|
||||
my $entity_count = $c->schema->resultset('Entity')->search({id => $org_result->entity->id })->count;
|
||||
die "Failed to remove org" if $org_count;
|
||||
die "Failed to remove entity" if $entity_count;
|
||||
});
|
||||
} catch {
|
||||
$c->app->log->warn($_);
|
||||
};
|
||||
$c->flash( error => 'Engage' );
|
||||
} else {
|
||||
$c->flash( error => 'You must tick the confirmation box to proceed' );
|
||||
}
|
||||
$c->redirect_to( '/admin/organisations/' . $org_id . '/merge/' . $target_id );
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package Pear::LocalLoop::Controller::Admin::Transactions;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use List::Util qw/ max sum /;
|
||||
|
||||
has result_set => sub {
|
||||
my $c = shift;
|
||||
return $c->schema->resultset('Transaction');
|
||||
|
@ -9,15 +11,49 @@ has result_set => sub {
|
|||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $transactions = $c->result_set->search(
|
||||
undef, {
|
||||
page => $c->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => { -desc => 'submitted_at' },
|
||||
},
|
||||
my $pending_transaction_rs = $c->schema->resultset('Organisation')->search({ pending => 1 })->entity->sales;
|
||||
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $week_transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{},
|
||||
{
|
||||
select => [
|
||||
{ count => 'me.value', '-as' => 'count' },
|
||||
{ sum => 'me.value', '-as' => 'sum_value' },
|
||||
'quantised_weeks',
|
||||
],
|
||||
group_by => 'quantised_weeks',
|
||||
order_by => { '-asc' => 'quantised_weeks' },
|
||||
}
|
||||
);
|
||||
|
||||
my @all_weeks = $week_transaction_rs->all;
|
||||
my $first_week_count = defined $all_weeks[0] ? $all_weeks[0]->get_column('count') || 0 : 0;
|
||||
my $first_week_value = defined $all_weeks[0] ? $all_weeks[0]->get_column('sum_value') / 100000 || 0 : 0;
|
||||
my $second_week_count = defined $all_weeks[1] ? $all_weeks[1]->get_column('count') || 0 : 0;
|
||||
my $second_week_value = defined $all_weeks[1] ? $all_weeks[1]->get_column('sum_value') / 100000 || 0 : 0;
|
||||
|
||||
my $transaction_rs = $c->schema->resultset('Transaction');
|
||||
my $value_rs_col = $transaction_rs->get_column('value');
|
||||
my $max_value = $value_rs_col->max / 100000 || 0;
|
||||
my $avg_value = sprintf( '%.2f', $value_rs_col->func('AVG') / 100000) || 0;
|
||||
my $sum_value = $value_rs_col->sum / 100000 || 0;
|
||||
my $count = $transaction_rs->count || 0;
|
||||
|
||||
my $placeholder = 'Placeholder';
|
||||
$c->stash(
|
||||
transactions => $transactions,
|
||||
placeholder => $placeholder,
|
||||
pending_trans => $pending_transaction_rs->count,
|
||||
weeks => {
|
||||
first_count => $first_week_count,
|
||||
second_count => $second_week_count,
|
||||
first_value => $first_week_value,
|
||||
second_value => $second_week_value,
|
||||
max => $max_value,
|
||||
avg => $avg_value,
|
||||
sum => $sum_value,
|
||||
count => $count,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -54,6 +90,9 @@ sub delete {
|
|||
my $id = $c->param('id');
|
||||
|
||||
if ( my $transaction = $c->result_set->find($id) ) {
|
||||
if (defined $transaction->category) {
|
||||
$transaction->category->delete;
|
||||
}
|
||||
$transaction->delete;
|
||||
$c->flash( success => 'Successfully deleted transaction' );
|
||||
$c->redirect_to( '/admin/transactions' );
|
||||
|
@ -63,4 +102,19 @@ if ( my $transaction = $c->result_set->find($id) ) {
|
|||
}
|
||||
}
|
||||
|
||||
sub pg_or_sqlite {
|
||||
my ( $c, $pg_sql, $sqlite_sql ) = @_;
|
||||
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
|
||||
if ( $driver eq 'Pg' ) {
|
||||
return \$pg_sql;
|
||||
} elsif ( $driver eq 'SQLite' ) {
|
||||
return \$sqlite_sql;
|
||||
} else {
|
||||
$c->app->log->warn('Unknown Driver Used');
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -22,9 +22,15 @@ has organisation_result_set => sub {
|
|||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $user_rs = $c->user_result_set;
|
||||
$user_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
|
||||
$c->stash( users => [ $user_rs->all ] );
|
||||
my $user_rs = $c->user_result_set->search(
|
||||
undef, {
|
||||
prefech => { entity => [ qw/ customer organisation / ] },
|
||||
page => $c->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => { -asc => 'email' },
|
||||
}
|
||||
);
|
||||
$c->stash( user_rs => $user_rs );
|
||||
}
|
||||
|
||||
sub read {
|
||||
|
@ -86,6 +92,11 @@ sub update {
|
|||
return $c->redirect_to( '/admin/users/' . $id );
|
||||
}
|
||||
|
||||
my $location = $c->get_location_from_postcode(
|
||||
$validation->param('postcode'),
|
||||
$user->type,
|
||||
);
|
||||
|
||||
if ( $user->type eq 'customer' ){
|
||||
|
||||
try {
|
||||
|
@ -94,6 +105,7 @@ sub update {
|
|||
full_name => $validation->param('full_name'),
|
||||
display_name => $validation->param('display_name'),
|
||||
postcode => $validation->param('postcode'),
|
||||
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
|
||||
});
|
||||
$user->update({
|
||||
email => $validation->param('email'),
|
||||
|
@ -119,6 +131,7 @@ sub update {
|
|||
town => $validation->param('town'),
|
||||
sector => $validation->param('sector'),
|
||||
postcode => $validation->param('postcode'),
|
||||
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
|
||||
});
|
||||
$user->update({
|
||||
email => $validation->param('email'),
|
||||
|
|
|
@ -74,6 +74,8 @@ sub post_login {
|
|||
my $email = $validation->param('email');
|
||||
my $password = $validation->param('password');
|
||||
|
||||
$c->app->log->debug( __PACKAGE__ . " login attempt for [" . $email . "]" );
|
||||
|
||||
my $user_result = $c->schema->resultset('User')->find({ email => $email });
|
||||
|
||||
if ( defined $user_result ) {
|
||||
|
@ -86,6 +88,8 @@ sub post_login {
|
|||
display_name => $user_result->name,
|
||||
user_type => $user_result->type,
|
||||
});
|
||||
} else {
|
||||
$c->app->log->info( __PACKAGE__ . " failed login for [" . $email . "]" );
|
||||
}
|
||||
}
|
||||
return $c->render(
|
||||
|
|
86
lib/Pear/LocalLoop/Controller/Api/Categories.pm
Normal file
86
lib/Pear/LocalLoop/Controller/Api/Categories.pm
Normal file
|
@ -0,0 +1,86 @@
|
|||
package Pear::LocalLoop::Controller::Api::Categories;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use List::Util qw/ max /;
|
||||
|
||||
sub post_category_list {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => 28 );
|
||||
my $end = DateTime->today;
|
||||
my $start = $end->clone->subtract_duration( $duration );
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $month_transaction_category_rs = $c->schema->resultset('ViewQuantisedTransactionCategory' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_weeks',
|
||||
value => { sum => 'value' },
|
||||
category_id => 'category_id',
|
||||
essential => 'essential',
|
||||
},
|
||||
],
|
||||
group_by => [ qw/ category_id quantised_weeks essential / ],
|
||||
}
|
||||
);
|
||||
|
||||
my $data = { categories => {}, essentials => {} };
|
||||
|
||||
my $category_list = $c->schema->resultset('Category')->as_hash;
|
||||
|
||||
for my $cat_trans ( $month_transaction_category_rs->all ) {
|
||||
my $quantised = $c->db_datetime_parser->parse_datetime($cat_trans->get_column('quantised'));
|
||||
my $days = $c->format_iso_date( $quantised ) || 0;
|
||||
my $category = $cat_trans->get_column('category_id') || 0;
|
||||
my $value = ($cat_trans->get_column('value') || 0) / 100000;
|
||||
$data->{categories}->{$days}->{$category_list->{$category}} += $value;
|
||||
next unless $cat_trans->get_column('essential');
|
||||
$data->{essentials}->{$days}->{value} += $value;
|
||||
}
|
||||
|
||||
for my $day ( keys %{ $data->{categories} } ) {
|
||||
my @days = ( map{ {
|
||||
days => $day,
|
||||
value => $data->{categories}->{$day}->{$_},
|
||||
category => $_,
|
||||
} } keys %{ $data->{categories}->{$day} } );
|
||||
$data->{categories}->{$day} = [ sort { $b->{value} <=> $a->{value} } @days ];
|
||||
}
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => $data,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub pg_or_sqlite {
|
||||
my ( $c, $pg_sql, $sqlite_sql ) = @_;
|
||||
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
|
||||
if ( $driver eq 'Pg' ) {
|
||||
return \$pg_sql;
|
||||
} elsif ( $driver eq 'SQLite' ) {
|
||||
return \$sqlite_sql;
|
||||
} else {
|
||||
$c->app->log->warn('Unknown Driver Used');
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
489
lib/Pear/LocalLoop/Controller/Api/External.pm
Normal file
489
lib/Pear/LocalLoop/Controller/Api/External.pm
Normal file
|
@ -0,0 +1,489 @@
|
|||
package Pear::LocalLoop::Controller::Api::External;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
use Mojo::JSON;
|
||||
|
||||
sub post_lcc_transactions {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
# TODO Check the user is lancaster city council
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input($c->stash->{api_json});
|
||||
$validation->optional('page')->number;
|
||||
$validation->optional('per_page')->number;
|
||||
$validation->optional('search');
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $search_ref = { 'me.buyer_id' => $user->entity->id };
|
||||
if ($validation->param('search')) {
|
||||
$search_ref->{"organisation.name"} = { '-like' => join('', '%', $validation->param('search'), '%') };
|
||||
}
|
||||
|
||||
my $lcc_transactions = $c->schema->resultset('Transaction')->search(
|
||||
$search_ref,
|
||||
{
|
||||
page => $validation->param('page') || 1,
|
||||
rows => $validation->param('per_page') || 10,
|
||||
join => [ 'transaction', 'organisation' ],
|
||||
order_by => { -desc => 'transaction.purchase_time' },
|
||||
});
|
||||
|
||||
# purchase_time needs timezone attached to it
|
||||
my @transaction_list = (
|
||||
map {{
|
||||
transaction_external_id => $_->external_id,
|
||||
seller => $_->transaction->seller->name,
|
||||
net_value => $_->transaction->meta->net_value,
|
||||
gross_value => $_->transaction->meta->gross_value,
|
||||
sales_tax_value => $_->transaction->meta->sales_tax_value,
|
||||
purchase_time => $c->format_iso_datetime($_->transaction->purchase_time),
|
||||
}} $lcc_transactions->all
|
||||
);
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
transactions => \@transaction_list,
|
||||
page_no => $lcc_transactions->pager->total_entries,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_lcc_suppliers {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
# TODO give an error if user is not of Lancashire County Council
|
||||
|
||||
# my $is_lcc = $user->entity->organisation->count({ name => "Lancashire County Council" });
|
||||
|
||||
my $v = $c->validation;
|
||||
$v->input($c->stash->{api_json});
|
||||
$v->optional('page')->number;
|
||||
$v->optional('sort_by');
|
||||
$v->optional('sort_dir');
|
||||
$v->optional('search');
|
||||
|
||||
my $order_by = [
|
||||
{ -asc => 'organisation.name' },
|
||||
];
|
||||
if ($v->param('sort_by')) {
|
||||
my %dirs = ('asc' => '-asc', 'desc' => '-desc');
|
||||
my $dir = $dirs{$v->param('sort_dir')} // '-asc';
|
||||
my %sorts = (
|
||||
'name' => 'organisation.name',
|
||||
'postcode' => 'organisation.postcode',
|
||||
'spend' => 'total_spend',
|
||||
);
|
||||
my $sort = $sorts{$v->param('sort_by')} || 'organisation.name';
|
||||
$order_by->[0] = { $dir => $sort };
|
||||
}
|
||||
|
||||
return $c->api_validation_error if $v->has_error;
|
||||
|
||||
my $lcc_suppliers = $c->schema->resultset('Entity')->search(
|
||||
{
|
||||
'sales.buyer_id' => $user->entity->id,
|
||||
($v->param('search') ? (
|
||||
'-or' => [
|
||||
{ 'organisation.name' => { 'like' => $v->param('search') . '%' } },
|
||||
{ 'organisation.postcode' => { 'like' => $v->param('search') . '%' } },
|
||||
]
|
||||
) : ()),
|
||||
},
|
||||
{
|
||||
join => [ 'sales', 'organisation' ],
|
||||
group_by => [ 'me.id', 'organisation.id' ],
|
||||
'+select' => [
|
||||
{
|
||||
'sum' => 'sales.value',
|
||||
'-as' => 'total_spend',
|
||||
}
|
||||
],
|
||||
'+as' => [ 'total_spend' ],
|
||||
page => $v->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => $order_by,
|
||||
}
|
||||
);
|
||||
|
||||
my @supplier_list = (
|
||||
map {{
|
||||
entity_id => $_->id,
|
||||
name => $_->name,
|
||||
street => $_->organisation->street_name,
|
||||
town => $_->organisation->town,
|
||||
postcode => $_->organisation->postcode,
|
||||
country => $_->organisation->country,
|
||||
spend => ($_->get_column('total_spend') / 100000) // 0,
|
||||
}} $lcc_suppliers->all
|
||||
);
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
suppliers => \@supplier_list,
|
||||
page_no => $lcc_suppliers->pager->total_entries,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_year_spend {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
my $v = $c->validation;
|
||||
$v->input($c->stash->{api_json});
|
||||
$v->required('from');
|
||||
$v->required('to');
|
||||
|
||||
return $c->api_validation_error if $v->has_error;
|
||||
|
||||
my $last = $c->parse_iso_date($v->param('to'));
|
||||
my $first = $c->parse_iso_date($v->param('from'));
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $spend_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($first),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
buyer_id => $user->entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_days',
|
||||
count => \"COUNT(*)",
|
||||
total_spend => { sum => 'value' },
|
||||
}
|
||||
],
|
||||
group_by => 'quantised_days',
|
||||
order_by => { '-asc' => 'quantised_days' },
|
||||
}
|
||||
);
|
||||
|
||||
my @graph_data = (
|
||||
map {{
|
||||
count => $_->get_column('count'),
|
||||
value => ($_->get_column('total_spend') / 100000) // 0,
|
||||
date => $_->get_column('quantised'),
|
||||
}} $spend_rs->all,
|
||||
);
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => \@graph_data,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_supplier_count {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
my $v = $c->validation;
|
||||
$v->input($c->stash->{api_json});
|
||||
$v->required('from');
|
||||
$v->required('to');
|
||||
|
||||
return $c->api_validation_error if $v->has_error;
|
||||
|
||||
my $last = $c->parse_iso_date($v->param('to'));
|
||||
my $first = $c->parse_iso_date($v->param('from'));
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $spend_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($first),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
buyer_id => $user->entity->id,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
select => [
|
||||
{ count => 'me.value', '-as' => 'count' },
|
||||
{ sum => 'me.value', '-as' => 'total_spend' },
|
||||
'organisation.name',
|
||||
'me.quantised_days',
|
||||
],
|
||||
as => [ qw/count total_spend name quantised_days/ ],
|
||||
group_by => [ qw/me.quantised_days seller.id organisation.id/ ],
|
||||
order_by => { '-asc' => 'me.quantised_days' },
|
||||
}
|
||||
);
|
||||
|
||||
my @graph_data = (
|
||||
map {{
|
||||
count => $_->get_column('count'),
|
||||
value => ($_->get_column('total_spend') / 100000) // 0,
|
||||
date => $_->get_column('quantised_days'),
|
||||
seller => $_->get_column('name'),
|
||||
}} $spend_rs->all,
|
||||
);
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => \@graph_data,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_supplier_history {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
# Temporary date lock for dev data
|
||||
my $last = DateTime->new(
|
||||
year => 2019,
|
||||
month => 4,
|
||||
day => 1
|
||||
);
|
||||
my $first = $last->clone->subtract(years => 1);
|
||||
my $second = $last->clone->subtract(months => 6);
|
||||
my $third = $last->clone->subtract(months => 3);
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $year_rs = $c->schema->resultset('Entity')->search(
|
||||
{
|
||||
'sales.purchase_time' => {
|
||||
-between => [
|
||||
$dtf->format_datetime($first),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
'sales.buyer_id' => $user->entity->id,
|
||||
},
|
||||
{
|
||||
join => [ 'sales', 'organisation' ],
|
||||
columns => [
|
||||
{
|
||||
id => 'me.id',
|
||||
name => 'organisation.name',
|
||||
count => \"COUNT(*)",
|
||||
total_spend => { sum => 'sales.value' },
|
||||
}
|
||||
],
|
||||
group_by => [ 'me.id', 'organisation.id' ],
|
||||
order_by => { '-asc' => 'organisation.name' },
|
||||
}
|
||||
);
|
||||
my $half_year_rs = $c->schema->resultset('Entity')->search(
|
||||
{
|
||||
'sales.purchase_time' => {
|
||||
-between => [
|
||||
$dtf->format_datetime($second),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
'sales.buyer_id' => $user->entity->id,
|
||||
},
|
||||
{
|
||||
join => [ 'sales', 'organisation' ],
|
||||
columns => [
|
||||
{
|
||||
id => 'me.id',
|
||||
name => 'organisation.name',
|
||||
count => \"COUNT(*)",
|
||||
total_spend => { sum => 'sales.value' },
|
||||
}
|
||||
],
|
||||
group_by => [ 'me.id', 'organisation.id' ],
|
||||
order_by => { '-asc' => 'organisation.name' },
|
||||
}
|
||||
);
|
||||
my $quarter_year_rs = $c->schema->resultset('Entity')->search(
|
||||
{
|
||||
'sales.purchase_time' => {
|
||||
-between => [
|
||||
$dtf->format_datetime($third),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
'sales.buyer_id' => $user->entity->id,
|
||||
},
|
||||
{
|
||||
join => [ 'sales', 'organisation' ],
|
||||
columns => [
|
||||
{
|
||||
id => 'me.id',
|
||||
name => 'organisation.name',
|
||||
count => \"COUNT(*)",
|
||||
total_spend => { sum => 'sales.value' },
|
||||
}
|
||||
],
|
||||
group_by => [ 'me.id', 'organisation.id' ],
|
||||
order_by => { '-asc' => 'organisation.name' },
|
||||
}
|
||||
);
|
||||
|
||||
my %data;
|
||||
for my $row ($year_rs->all) {
|
||||
$data{$row->get_column('id')} = {
|
||||
id => $row->get_column('id'),
|
||||
name => $row->get_column('name'),
|
||||
quarter_count => 0,
|
||||
quarter_total => 0,
|
||||
half_count => 0,
|
||||
half_total => 0,
|
||||
year_count => $row->get_column('count'),
|
||||
year_total => $row->get_column('total_spend') / 100000,
|
||||
};
|
||||
}
|
||||
|
||||
for my $row ($half_year_rs->all) {
|
||||
$data{$row->get_column('id')} = {
|
||||
id => $row->get_column('id'),
|
||||
name => $row->get_column('name'),
|
||||
quarter_count => 0,
|
||||
quarter_total => 0,
|
||||
half_count => $row->get_column('count'),
|
||||
half_total => $row->get_column('total_spend') / 100000,
|
||||
year_count => 0,
|
||||
year_total => 0,
|
||||
%{$data{$row->get_column('id')}},
|
||||
};
|
||||
}
|
||||
|
||||
for my $row ($quarter_year_rs->all) {
|
||||
$data{$row->get_column('id')} = {
|
||||
id => $row->get_column('id'),
|
||||
name => $row->get_column('name'),
|
||||
quarter_count => $row->get_column('count'),
|
||||
quarter_total => $row->get_column('total_spend') / 100000,
|
||||
half_count => 0,
|
||||
half_total => 0,
|
||||
year_count => 0,
|
||||
year_total => 0,
|
||||
%{$data{$row->get_column('id')}},
|
||||
};
|
||||
}
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => [ values %data ],
|
||||
});
|
||||
}
|
||||
|
||||
sub post_lcc_table_summary {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
my $v = $c->validation;
|
||||
$v->input($c->stash->{api_json});
|
||||
$v->required('from');
|
||||
$v->required('to');
|
||||
|
||||
return $c->api_validation_error if $v->has_error;
|
||||
|
||||
my $last = $c->parse_iso_date($v->param('to'));
|
||||
my $first = $c->parse_iso_date($v->param('from'));
|
||||
|
||||
my $transaction_rs = $c->schema->resultset('Transaction');
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $ward_transactions_rs = $transaction_rs->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($first),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
buyer_id => $user->entity->id,
|
||||
},
|
||||
{
|
||||
join => { seller => { postcode => { gb_postcode => 'ward' } } },
|
||||
group_by => 'ward.id',
|
||||
select => [
|
||||
{ count => 'me.id', '-as' => 'count' },
|
||||
{ sum => 'me.value', '-as' => 'sum' },
|
||||
'ward.ward'
|
||||
],
|
||||
as => [ qw/count sum ward_name/ ],
|
||||
}
|
||||
);
|
||||
|
||||
my $transaction_type_data = {};
|
||||
|
||||
my %meta_names = (
|
||||
local_service => "Local Services",
|
||||
regional_service => "Regional Services",
|
||||
national_service => "National Services",
|
||||
private_household_rebate => "Private Household Rebates etc",
|
||||
business_tax_and_rebate => "Business Tax & Service Rebates",
|
||||
stat_loc_gov => "Statutory Loc Gov",
|
||||
central_loc_gov => "Central Gov HMRC",
|
||||
);
|
||||
|
||||
for my $meta (qw/
|
||||
local_service
|
||||
regional_service
|
||||
national_service
|
||||
private_household_rebate
|
||||
business_tax_and_rebate
|
||||
stat_loc_gov
|
||||
central_loc_gov
|
||||
/) {
|
||||
my $transaction_type_rs = $transaction_rs->search(
|
||||
{
|
||||
'me.purchase_time' => {
|
||||
-between => [
|
||||
$dtf->format_datetime($first),
|
||||
$dtf->format_datetime($last),
|
||||
],
|
||||
},
|
||||
'me.buyer_id' => $user->entity->id,
|
||||
'meta.' . $meta => 1,
|
||||
},
|
||||
{
|
||||
join => 'meta',
|
||||
group_by => 'meta.' . $meta,
|
||||
select => [
|
||||
{ count => 'me.id', '-as' => 'count' },
|
||||
{ sum => 'me.value', '-as' => 'sum' },
|
||||
],
|
||||
as => [ qw/count sum/ ],
|
||||
}
|
||||
)->first;
|
||||
|
||||
$transaction_type_data->{$meta} = {
|
||||
($transaction_type_rs ? (
|
||||
count => $transaction_type_rs->get_column('count'),
|
||||
sum => $transaction_type_rs->get_column('sum'),
|
||||
type => $meta_names{$meta},
|
||||
) : (
|
||||
count => 0,
|
||||
sum => 0,
|
||||
type => $meta_names{$meta},
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
my @ward_transaction_list = (
|
||||
map {{
|
||||
ward => $_->get_column('ward_name') || "N/A",
|
||||
sum => $_->get_column('sum') / 100000,
|
||||
count => $_->get_column('count'),
|
||||
}} $ward_transactions_rs->all
|
||||
);
|
||||
|
||||
return $c->render(json => {
|
||||
success => Mojo::JSON->true,
|
||||
wards => \@ward_transaction_list,
|
||||
types => $transaction_type_data,
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,7 +1,7 @@
|
|||
package Pear::LocalLoop::Controller::Api::Stats;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
use List::Util qw/ first /;
|
||||
use List::Util qw/ max sum /;
|
||||
|
||||
has error_messages => sub {
|
||||
return {
|
||||
|
@ -58,6 +58,320 @@ sub post_index {
|
|||
});
|
||||
}
|
||||
|
||||
sub post_customer {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $purchase_rs = $entity->purchases;
|
||||
|
||||
my $duration_weeks = DateTime::Duration->new( weeks => 7 );
|
||||
my $end = DateTime->today;
|
||||
my $start_weeks = $end->clone->subtract_duration( $duration_weeks );
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $week_transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start_weeks),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_weeks',
|
||||
count => \"COUNT(*)",
|
||||
}
|
||||
],
|
||||
group_by => 'quantised_weeks',
|
||||
order_by => { '-asc' => 'quantised_weeks' },
|
||||
}
|
||||
);
|
||||
|
||||
my @all_weeks = $week_transaction_rs->all;
|
||||
my $first = defined $all_weeks[0] ? $all_weeks[0]->get_column('count') || 0 : 0;
|
||||
my $second = defined $all_weeks[1] ? $all_weeks[1]->get_column('count') || 0 : 0;
|
||||
my $max = max( map { $_->get_column('count') } @all_weeks );
|
||||
my $sum = sum( map { $_->get_column('count') } @all_weeks );
|
||||
my $count = $week_transaction_rs->count;
|
||||
|
||||
my $weeks = {
|
||||
first => $first,
|
||||
second => $second,
|
||||
max => $max,
|
||||
sum => $sum,
|
||||
count => $count,
|
||||
};
|
||||
|
||||
my $data = { cat_total => {}, categories => {}, essentials => {}, cat_list => {} };
|
||||
|
||||
my $category_list = $c->schema->resultset('Category')->as_hash;
|
||||
|
||||
my $category_purchase_rs = $purchase_rs->search({},
|
||||
{
|
||||
join => 'category',
|
||||
columns => {
|
||||
category_id => "category.category_id",
|
||||
value => { sum => 'value' },
|
||||
},
|
||||
group_by => "category.category_id",
|
||||
}
|
||||
);
|
||||
|
||||
my %cat_total_list;
|
||||
|
||||
for ( $category_purchase_rs->all ) {
|
||||
my $category = $_->get_column('category_id') || 0;
|
||||
my $value = ($_->get_column('value') || 0) / 100000;
|
||||
|
||||
$cat_total_list{$category_list->{$category}} += $value;
|
||||
}
|
||||
|
||||
my @cat_lists = map { { category => $_, value => $cat_total_list{$_},
|
||||
icon => $c->schema->resultset('Category')->as_hash_name_icon->{$_} || 'question'} } sort keys %cat_total_list;
|
||||
$data->{cat_list} = [ sort { $b->{value} <=> $a->{value} } @cat_lists ];
|
||||
|
||||
my $purchase_no_essential_rs = $purchase_rs->search({
|
||||
"me.essential" => 1,
|
||||
});
|
||||
|
||||
$data->{essentials} = {
|
||||
purchase_no_total => $purchase_rs->count,
|
||||
purchase_no_essential_total => $purchase_no_essential_rs->count,
|
||||
};
|
||||
|
||||
my $duration_month = DateTime::Duration->new( days => 28 );
|
||||
my $start_month = $end->clone->subtract_duration( $duration_month );
|
||||
my $month_transaction_category_rs = $c->schema->resultset('ViewQuantisedTransactionCategory' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start_month),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_weeks',
|
||||
value => { sum => 'value' },
|
||||
category_id => 'category_id',
|
||||
essential => 'essential',
|
||||
},
|
||||
],
|
||||
group_by => [ qw/ category_id quantised_weeks essential / ],
|
||||
}
|
||||
);
|
||||
|
||||
for my $cat_trans ( $month_transaction_category_rs->all ) {
|
||||
my $quantised = $c->db_datetime_parser->parse_datetime($cat_trans->get_column('quantised'));
|
||||
my $days = $c->format_iso_date( $quantised ) || 0;
|
||||
my $category = $cat_trans->get_column('category_id') || 0;
|
||||
my $value = ($cat_trans->get_column('value') || 0) / 100000;
|
||||
$data->{cat_total}->{$category_list->{$category}} += $value;
|
||||
$data->{categories}->{$days}->{$category_list->{$category}} += $value;
|
||||
next unless $cat_trans->get_column('essential');
|
||||
$data->{essentials}->{$days}->{value} += $value;
|
||||
}
|
||||
|
||||
for my $day ( keys %{ $data->{categories} } ) {
|
||||
my @days = ( map{ {
|
||||
days => $day,
|
||||
value => $data->{categories}->{$day}->{$_},
|
||||
category => $_,
|
||||
} } keys %{ $data->{categories}->{$day} } );
|
||||
$data->{categories}->{$day} = [ sort { $b->{value} <=> $a->{value} } @days ];
|
||||
}
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => $data,
|
||||
weeks => $weeks,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_organisation {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $purchase_rs = $entity->purchases;
|
||||
|
||||
my $duration_weeks = DateTime::Duration->new( weeks => 7 );
|
||||
my $end = DateTime->today;
|
||||
my $start_weeks = $end->clone->subtract_duration( $duration_weeks );
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $week_transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start_weeks),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_weeks',
|
||||
count => \"COUNT(*)",
|
||||
}
|
||||
],
|
||||
group_by => 'quantised_weeks',
|
||||
order_by => { '-asc' => 'quantised_weeks' },
|
||||
}
|
||||
);
|
||||
|
||||
my @all_weeks = $week_transaction_rs->all;
|
||||
my $first = defined $all_weeks[0] ? $all_weeks[0]->get_column('count') || 0 : 0;
|
||||
my $second = defined $all_weeks[1] ? $all_weeks[1]->get_column('count') || 0 : 0;
|
||||
my $max = max( map { $_->get_column('count') } @all_weeks );
|
||||
my $sum = sum( map { $_->get_column('count') } @all_weeks );
|
||||
my $count = $week_transaction_rs->count;
|
||||
|
||||
my $weeks = {
|
||||
first => $first,
|
||||
second => $second,
|
||||
max => $max,
|
||||
sum => $sum,
|
||||
count => $count,
|
||||
};
|
||||
|
||||
my $data = {
|
||||
cat_total => {},
|
||||
categories => {},
|
||||
essentials => {},
|
||||
cat_list => {},
|
||||
sector_monthly => {}
|
||||
};
|
||||
|
||||
my $category_list = $c->schema->resultset('Category')->as_hash;
|
||||
|
||||
my $category_purchase_rs = $purchase_rs->search({},
|
||||
{
|
||||
join => 'category',
|
||||
columns => {
|
||||
category_id => "category.category_id",
|
||||
value => { sum => 'value' },
|
||||
},
|
||||
group_by => "category.category_id",
|
||||
}
|
||||
);
|
||||
|
||||
my %cat_total_list;
|
||||
|
||||
for ( $category_purchase_rs->all ) {
|
||||
my $category = $_->get_column('category_id') || 0;
|
||||
my $value = ($_->get_column('value') || 0) / 100000;
|
||||
|
||||
$cat_total_list{$category_list->{$category}} += $value;
|
||||
}
|
||||
|
||||
my @cat_lists = map { { category => $_, value => $cat_total_list{$_},
|
||||
icon => $c->schema->resultset('Category')->as_hash_name_icon->{$_} || 'question'} } sort keys %cat_total_list;
|
||||
$data->{cat_list} = [ sort { $b->{value} <=> $a->{value} } @cat_lists ];
|
||||
|
||||
my $purchase_no_essential_rs = $purchase_rs->search({
|
||||
"me.essential" => 1,
|
||||
});
|
||||
|
||||
$data->{essentials} = {
|
||||
purchase_no_total => $purchase_rs->count,
|
||||
purchase_no_essential_total => $purchase_no_essential_rs->count,
|
||||
};
|
||||
|
||||
my $duration_month = DateTime::Duration->new( days => 28 );
|
||||
my $start_month = $end->clone->subtract_duration( $duration_month );
|
||||
my $month_transaction_category_rs = $c->schema->resultset('ViewQuantisedTransactionCategory' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start_month),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_weeks',
|
||||
value => { sum => 'value' },
|
||||
category_id => 'category_id',
|
||||
essential => 'essential',
|
||||
},
|
||||
],
|
||||
group_by => [ qw/ category_id quantised_weeks essential / ],
|
||||
}
|
||||
);
|
||||
|
||||
for my $cat_trans ( $month_transaction_category_rs->all ) {
|
||||
my $quantised = $c->db_datetime_parser->parse_datetime($cat_trans->get_column('quantised'));
|
||||
my $days = $c->format_iso_date( $quantised ) || 0;
|
||||
my $category = $cat_trans->get_column('category_id') || 0;
|
||||
my $value = ($cat_trans->get_column('value') || 0) / 100000;
|
||||
$data->{cat_total}->{$category_list->{$category}} += $value;
|
||||
$data->{categories}->{$days}->{$category_list->{$category}} += $value;
|
||||
next unless $cat_trans->get_column('essential');
|
||||
$data->{essentials}->{$days}->{value} += $value;
|
||||
}
|
||||
|
||||
for my $day ( keys %{ $data->{categories} } ) {
|
||||
my @days = ( map{ {
|
||||
days => $day,
|
||||
value => $data->{categories}->{$day}->{$_},
|
||||
category => $_,
|
||||
} } keys %{ $data->{categories}->{$day} } );
|
||||
$data->{categories}->{$day} = [ sort { $b->{value} <=> $a->{value} } @days ];
|
||||
}
|
||||
|
||||
# my $start_year_monthly = DateTime->now->truncate( to => 'year' );
|
||||
# my $current_year_monthly = DateTime->now->add( months => 1, end_of_month => 'limit' );
|
||||
# my $monthly_sector_transactions_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
# {
|
||||
# purchase_time => {
|
||||
# -between => [
|
||||
# $dtf->format_datetime($start_year_monthly),
|
||||
# $dtf->format_datetime($current_year_monthly),
|
||||
# ],
|
||||
# },
|
||||
# buyer_id => $entity->id,
|
||||
# },
|
||||
# {
|
||||
# columns => [
|
||||
# {
|
||||
# quantised => 'quantised_months',
|
||||
# value => { sum => 'value' },
|
||||
# },
|
||||
# ],
|
||||
# group_by => [ qw/ quantised_months / ],
|
||||
# }
|
||||
# );
|
||||
#
|
||||
# for my $sector_transaction ( $monthly_sector_transactions_rs->all ) {
|
||||
# my $quantised = $c->db_datetime_parser->parse_datetime($cat_trans->get_column('quantised'));
|
||||
# my $months = $c->format_iso_date( $quantised ) || 0;
|
||||
# my $category = $cat_trans->get_column('category_id') || 0;
|
||||
# my $value = ($cat_trans->get_column('value') || 0) / 100000;
|
||||
# }
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
data => $data,
|
||||
weeks => $weeks,
|
||||
});
|
||||
}
|
||||
|
||||
sub post_leaderboards {
|
||||
my $c = shift;
|
||||
|
||||
|
@ -109,4 +423,90 @@ sub post_leaderboards {
|
|||
});
|
||||
}
|
||||
|
||||
sub post_leaderboards_paged {
|
||||
my $c = shift;
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
|
||||
my $leaderboard_rs = $c->schema->resultset('Leaderboard');
|
||||
|
||||
$validation->required('type')->in_resultset( 'type', $leaderboard_rs );
|
||||
$validation->optional('page')->number;
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $page = 1;
|
||||
|
||||
my $today_board = $leaderboard_rs->get_latest( $validation->param('type') );
|
||||
my @leaderboard_array;
|
||||
my $current_user_position;
|
||||
my $values_count = 0;
|
||||
if ( defined $today_board ) {
|
||||
|
||||
if ( !defined $validation->param('page') || $validation->param('page') < 1 ) {
|
||||
my $user_position = $today_board->values->find({ entity_id => $c->stash->{api_user}->entity->id });
|
||||
$page = int(defined $user_position ? $user_position->{position} : 0 / 10) + 1;
|
||||
} else {
|
||||
$page = $validation->param('page');
|
||||
}
|
||||
|
||||
my $today_values = $today_board->values->search(
|
||||
{},
|
||||
{
|
||||
page => $page,
|
||||
rows => 10,
|
||||
order_by => { -asc => 'me.position' },
|
||||
columns => [
|
||||
qw/
|
||||
me.value
|
||||
me.trend
|
||||
me.position
|
||||
/,
|
||||
{ display_name => 'customer.display_name' },
|
||||
],
|
||||
join => { entity => 'customer' },
|
||||
},
|
||||
);
|
||||
$today_values->result_class( 'DBIx::Class::ResultClass::HashRefInflator' );
|
||||
|
||||
@leaderboard_array = $today_values->all;
|
||||
|
||||
$values_count = $today_values->pager->total_entries;
|
||||
|
||||
if ( $validation->param('type') =~ /total$/ ) {
|
||||
@leaderboard_array = (map {
|
||||
{
|
||||
%$_,
|
||||
value => $_->{value} / 100000,
|
||||
}
|
||||
} @leaderboard_array);
|
||||
}
|
||||
|
||||
$current_user_position = $today_values->find({ entity_id => $c->stash->{api_user}->entity->id });
|
||||
}
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
leaderboard => [ @leaderboard_array ],
|
||||
user_position => defined $current_user_position ? $current_user_position->{position} : 0,
|
||||
page => $page,
|
||||
count => $values_count,
|
||||
});
|
||||
}
|
||||
|
||||
sub pg_or_sqlite {
|
||||
my ( $c, $pg_sql, $sqlite_sql ) = @_;
|
||||
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
|
||||
if ( $driver eq 'Pg' ) {
|
||||
return \$pg_sql;
|
||||
} elsif ( $driver eq 'SQLite' ) {
|
||||
return \$sqlite_sql;
|
||||
} else {
|
||||
$c->app->log->warn('Unknown Driver Used');
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -8,6 +8,21 @@ has error_messages => sub {
|
|||
required => { message => 'No email sent.', status => 400 },
|
||||
email => { message => 'Email is invalid.', status => 400 },
|
||||
},
|
||||
value => {
|
||||
required => { message => 'transaction amount is missing', status => 400 },
|
||||
number => { message => 'transaction amount does not look like a number', status => 400 },
|
||||
gt_num => { message => 'transaction amount cannot be equal to or less than zero', status => 400 },
|
||||
},
|
||||
apply_time => {
|
||||
required => { message => 'purchase time is missing', status => 400 },
|
||||
is_full_iso_datetime => { message => 'time is in incorrect format', status => 400 },
|
||||
},
|
||||
id => {
|
||||
required => { message => 'Recurring Transaction not found', status => 400 },
|
||||
},
|
||||
category => {
|
||||
in_resultset => { message => 'Category is invalid', status => 400 },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -30,20 +45,135 @@ sub post_transaction_list_purchases {
|
|||
},
|
||||
);
|
||||
|
||||
# purchase_time needs timezone attached to it
|
||||
my $recurring_transactions = $c->schema->resultset('TransactionRecurring')->search({
|
||||
buyer_id => $user->id,
|
||||
});
|
||||
|
||||
# purchase_time needs timezone attached to it
|
||||
my @transaction_list = (
|
||||
map {{
|
||||
seller => $_->seller->name,
|
||||
value => $_->value / 100000,
|
||||
purchase_time => $c->format_iso_datetime($_->purchase_time),
|
||||
( $_->meta ? (
|
||||
net_value => $_->meta->net_value / 100000,
|
||||
sales_tax_value => $_->meta->sales_tax_value / 100000,
|
||||
gross_value => $_->meta->gross_value / 100000,
|
||||
) : (
|
||||
net_value => undef,
|
||||
sales_tax_value => undef,
|
||||
gross_value => undef,
|
||||
)),
|
||||
|
||||
}} $transactions->all
|
||||
);
|
||||
|
||||
my @recurring_transaction_list = (
|
||||
map {{
|
||||
id => $_->id,
|
||||
seller => $_->seller->name,
|
||||
value => $_->value / 100000,
|
||||
start_time => $c->format_iso_datetime($_->start_time),
|
||||
last_updated => $c->format_iso_datetime($_->last_updated) || undef,
|
||||
essential => $_->essential,
|
||||
category => $_->category_id || 0,
|
||||
recurring_period => $_->recurring_period,
|
||||
}} $recurring_transactions->all
|
||||
);
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
transactions => \@transaction_list,
|
||||
recurring_transactions => \@recurring_transaction_list,
|
||||
page_no => $transactions->pager->total_entries,
|
||||
});
|
||||
}
|
||||
|
||||
sub update_recurring {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
$validation->required('id');
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $id = $validation->param('id');
|
||||
|
||||
my $recur_transaction = $c->schema->resultset('TransactionRecurring')->find($id);
|
||||
unless ( $recur_transaction ) {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
message => 'Error Finding Recurring Transaction',
|
||||
error => 'recurring_error',
|
||||
},
|
||||
status => 400,
|
||||
);
|
||||
}
|
||||
|
||||
$validation->required('recurring_period');
|
||||
$validation->required('apply_time')->is_full_iso_datetime;
|
||||
$validation->optional('category')->in_resultset( 'id', $c->schema->resultset('Category'));
|
||||
$validation->optional('essential');
|
||||
$validation->required('value');
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $apply_time = $c->parse_iso_datetime($validation->param('apply_time'));
|
||||
|
||||
$c->schema->storage->txn_do( sub {
|
||||
$recur_transaction->update({
|
||||
start_time => $c->format_db_datetime($apply_time),
|
||||
last_updated => undef,
|
||||
category_id => $validation->param('category'),
|
||||
essential => $validation->param('essential'),
|
||||
value => $validation->param('value') * 100000,
|
||||
recurring_period => $validation->param('recurring_period'),
|
||||
});
|
||||
});
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
message => 'Recurring Transaction Updated Successfully',
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
sub delete_recurring {
|
||||
my $c = shift;
|
||||
|
||||
my $user = $c->stash->{api_user};
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
$validation->required('id');
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $id = $validation->param('id');
|
||||
|
||||
my $recur_transaction = $c->schema->resultset('TransactionRecurring')->find($id);
|
||||
unless ( $recur_transaction ) {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
message => 'Error Finding Recurring Transaction',
|
||||
error => 'recurring_error',
|
||||
},
|
||||
status => 400,
|
||||
);
|
||||
}
|
||||
|
||||
$recur_transaction->delete;
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
message => 'Recurring Transaction Deleted Successfully',
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -75,6 +75,9 @@ has error_messages => sub {
|
|||
organisation_name => {
|
||||
required => { message => 'organisation name is missing', status => 400 },
|
||||
},
|
||||
category => {
|
||||
in_resultset => { message => 'Category is invalid', status => 400 },
|
||||
},
|
||||
town => {
|
||||
required => { message => 'town/city is missing', status => 400 },
|
||||
},
|
||||
|
@ -104,6 +107,9 @@ sub post_upload {
|
|||
|
||||
#Check a proper purchase time was submitted
|
||||
$validation->optional('purchase_time')->is_full_iso_datetime;
|
||||
$validation->optional('category')->in_resultset( 'id', $c->schema->resultset('Category'));
|
||||
$validation->optional('essential');
|
||||
$validation->optional('recurring');
|
||||
|
||||
# First pass of required items
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
@ -117,7 +123,7 @@ sub post_upload {
|
|||
my $valid_org_rs = $c->schema->resultset('Organisation')->search({
|
||||
pending => 0,
|
||||
entity_id => { "!=" => $user->entity_id },
|
||||
});
|
||||
});
|
||||
$validation->required('organisation_id')->number->in_resultset( 'id', $valid_org_rs );
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
@ -141,17 +147,23 @@ sub post_upload {
|
|||
# Unknown Organisation
|
||||
$validation->required('organisation_name');
|
||||
$validation->optional('street_name');
|
||||
$validation->required('town');
|
||||
$validation->optional('town');
|
||||
$validation->optional('postcode')->postcode;
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $location = $c->get_location_from_postcode(
|
||||
$validation->param('postcode'),
|
||||
'organisation',
|
||||
);
|
||||
|
||||
my $entity = $c->schema->resultset('Entity')->create_org({
|
||||
submitted_by_id => $user->id,
|
||||
name => $validation->param('organisation_name'),
|
||||
street_name => $validation->param('street_name'),
|
||||
town => $validation->param('town'),
|
||||
postcode => $validation->param('postcode'),
|
||||
( defined $location ? ( %$location ) : ( latitude => undef, longitude => undef ) ),
|
||||
pending => 1,
|
||||
});
|
||||
$organisation = $entity->organisation;
|
||||
|
@ -173,6 +185,9 @@ sub post_upload {
|
|||
my $purchase_time = $c->parse_iso_datetime($validation->param('purchase_time') || '');
|
||||
$purchase_time ||= DateTime->now();
|
||||
my $file = defined $upload ? $c->store_file_from_upload( $upload ) : undef;
|
||||
my $category = $validation->param('category');
|
||||
my $essential = $validation->param('essential');
|
||||
my $recurring_period = $validation->param('recurring');
|
||||
my $distance = $c->get_distance_from_coords( $user->entity->type_object, $organisation );
|
||||
|
||||
my $new_transaction = $organisation->entity->create_related(
|
||||
|
@ -182,6 +197,7 @@ sub post_upload {
|
|||
value => $transaction_value * 100000,
|
||||
( defined $file ? ( proof_image => $file ) : () ),
|
||||
purchase_time => $c->format_db_datetime($purchase_time),
|
||||
essential => ( defined $essential ? $essential : 0 ),
|
||||
distance => $distance,
|
||||
}
|
||||
);
|
||||
|
@ -197,12 +213,45 @@ sub post_upload {
|
|||
);
|
||||
}
|
||||
|
||||
if ( defined $category ) {
|
||||
$c->schema->resultset('TransactionCategory')->create({
|
||||
category_id => $category,
|
||||
transaction_id => $new_transaction->id,
|
||||
});
|
||||
}
|
||||
|
||||
if ( defined $recurring_period ) {
|
||||
$c->schema->resultset('TransactionRecurring')->create({
|
||||
buyer => $user->entity,
|
||||
seller => $organisation->entity,
|
||||
value => $transaction_value * 100000,
|
||||
start_time => $c->format_db_datetime($purchase_time),
|
||||
essential => ( defined $essential ? $essential : 0 ),
|
||||
distance => $distance,
|
||||
category_id => ( defined $category ? $category : undef ),
|
||||
recurring_period => $recurring_period,
|
||||
});
|
||||
}
|
||||
|
||||
return $c->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
message => 'Upload Successful',
|
||||
});
|
||||
}
|
||||
|
||||
sub post_category {
|
||||
my $c = shift;
|
||||
my $self = $c;
|
||||
|
||||
my $category_list = $c->schema->resultset('Category')->as_hash;
|
||||
delete $category_list->{0};
|
||||
|
||||
return $self->render( json => {
|
||||
success => Mojo::JSON->true,
|
||||
categories => $category_list,
|
||||
});
|
||||
}
|
||||
|
||||
# TODO Limit search results, possibly paginate them?
|
||||
# TODO Search by location as well
|
||||
sub post_search {
|
||||
|
@ -216,6 +265,7 @@ sub post_search {
|
|||
$validation->input( $c->stash->{api_json} );
|
||||
|
||||
$validation->required('search_name');
|
||||
$validation->optional('page')->number;
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
|
@ -227,6 +277,11 @@ sub post_search {
|
|||
my $valid_orgs_rs = $org_rs->search({
|
||||
pending => 0,
|
||||
entity_id => { "!=" => $user->entity_id },
|
||||
},
|
||||
{
|
||||
page => $validation->param('page') || 1,
|
||||
rows => 10,
|
||||
order_by => { -desc => 'name' },
|
||||
})->search(
|
||||
\$search_stmt,
|
||||
);
|
||||
|
|
21
lib/Pear/LocalLoop/Controller/Api/V1/Customer.pm
Normal file
21
lib/Pear/LocalLoop/Controller/Api/V1/Customer.pm
Normal file
|
@ -0,0 +1,21 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::Customer;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
sub auth {
|
||||
my $c = shift;
|
||||
|
||||
return 1 if $c->stash->{api_user}->type eq 'customer';
|
||||
|
||||
$c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
message => 'Not an Customer',
|
||||
error => 'user_not_cust',
|
||||
},
|
||||
status => 403,
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
167
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Graphs.pm
Normal file
167
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Graphs.pm
Normal file
|
@ -0,0 +1,167 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::Customer::Graphs;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
has error_messages => sub {
|
||||
return {
|
||||
graph => {
|
||||
required => { message => 'Must request graph type', status => 400 },
|
||||
in => { message => 'Unrecognised graph type', status => 400 },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
$validation->required('graph')->in( qw/
|
||||
total_last_week
|
||||
avg_spend_last_week
|
||||
total_last_month
|
||||
avg_spend_last_month
|
||||
/ );
|
||||
|
||||
return $c->api_validation_error if $validation->has_error;
|
||||
|
||||
my $graph_sub = "graph_" . $validation->param('graph');
|
||||
|
||||
unless ( $c->can($graph_sub) ) {
|
||||
# Secondary catch in case a mistake has been made
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
message => $c->error_messages->{graph}->{in}->{message},
|
||||
error => 'in',
|
||||
},
|
||||
status => $c->error_messages->{graph}->{in}->{status},
|
||||
);
|
||||
}
|
||||
|
||||
return $c->$graph_sub;
|
||||
}
|
||||
|
||||
sub graph_total_last_week { return shift->_purchases_total_duration( 7 ) }
|
||||
sub graph_total_last_month { return shift->_purchases_total_duration( 30 ) }
|
||||
|
||||
sub _purchases_total_duration {
|
||||
my ( $c, $day_duration ) = @_;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => $day_duration );
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $data = { labels => [], data => [] };
|
||||
|
||||
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
|
||||
|
||||
$data->{bounds} = {
|
||||
min => $c->format_iso_datetime( $start ),
|
||||
max => $c->format_iso_datetime( $end ),
|
||||
};
|
||||
|
||||
while ( $start < $end ) {
|
||||
my $next_end = $start->clone->add( days => 1 );
|
||||
my $transactions = $entity->purchases
|
||||
->search_between( $start, $next_end )
|
||||
->get_column('value')
|
||||
->sum || 0 * 1;
|
||||
push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
|
||||
push @{ $data->{ data } }, $transactions / 100000;
|
||||
$start->add( days => 1 );
|
||||
}
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
graph => $data,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub graph_avg_spend_last_week { return shift->_purchases_avg_spend_duration( 7 ) }
|
||||
sub graph_avg_spend_last_month { return shift->_purchases_avg_spend_duration( 30 ) }
|
||||
|
||||
sub _purchases_avg_spend_duration {
|
||||
my ( $c, $day_duration ) = @_;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => $day_duration );
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $data = { labels => [], data => [] };
|
||||
|
||||
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
|
||||
|
||||
$data->{bounds} = {
|
||||
min => $c->format_iso_datetime( $start ),
|
||||
max => $c->format_iso_datetime( $end ),
|
||||
};
|
||||
|
||||
my $dtf = $c->schema->storage->datetime_parser;
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
my $transaction_rs = $c->schema->resultset('ViewQuantisedTransaction' . $driver)->search(
|
||||
{
|
||||
purchase_time => {
|
||||
-between => [
|
||||
$dtf->format_datetime($start),
|
||||
$dtf->format_datetime($end),
|
||||
],
|
||||
},
|
||||
buyer_id => $entity->id,
|
||||
},
|
||||
{
|
||||
columns => [
|
||||
{
|
||||
quantised => 'quantised_days',
|
||||
count => \"COUNT(*)",
|
||||
sum_value => $c->pg_or_sqlite(
|
||||
'SUM("me"."value")',
|
||||
'SUM("me"."value")',
|
||||
),
|
||||
average_value => $c->pg_or_sqlite(
|
||||
'AVG("me"."value")',
|
||||
'AVG("me"."value")',
|
||||
),
|
||||
}
|
||||
],
|
||||
group_by => 'quantised_days',
|
||||
order_by => { '-asc' => 'quantised_days' },
|
||||
}
|
||||
);
|
||||
|
||||
for ( $transaction_rs->all ) {
|
||||
my $quantised = $c->db_datetime_parser->parse_datetime($_->get_column('quantised'));
|
||||
push @{ $data->{ labels } }, $c->format_iso_datetime( $quantised );
|
||||
push @{ $data->{ data } }, ($_->get_column('average_value') || 0) / 100000;
|
||||
}
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
graph => $data,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub _get_start_end_duration {
|
||||
my ( $c, $duration ) = @_;
|
||||
my $end = DateTime->today;
|
||||
my $start = $end->clone->subtract_duration( $duration );
|
||||
return ( $start, $end );
|
||||
}
|
||||
|
||||
sub pg_or_sqlite {
|
||||
my ( $c, $pg_sql, $sqlite_sql ) = @_;
|
||||
|
||||
my $driver = $c->schema->storage->dbh->{Driver}->{Name};
|
||||
|
||||
if ( $driver eq 'Pg' ) {
|
||||
return \$pg_sql;
|
||||
} elsif ( $driver eq 'SQLite' ) {
|
||||
return \$sqlite_sql;
|
||||
} else {
|
||||
$c->app->log->warn('Unknown Driver Used');
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
63
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Pies.pm
Normal file
63
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Pies.pm
Normal file
|
@ -0,0 +1,63 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::Customer::Pies;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $purchase_rs = $entity->purchases;
|
||||
|
||||
my $local_org_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '<', 20000 },
|
||||
'organisation.is_local' => 1,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $local_org_non_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '>=', 20000 },
|
||||
'organisation.is_local' => 1,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $non_local_org_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '<', 20000 },
|
||||
'organisation.is_local' => 0,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $non_local_org_non_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '>=', 20000 },
|
||||
'organisation.is_local' => 0,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $local_all = {
|
||||
'Local shop local purchaser' => $local_org_local_purchase->count,
|
||||
'Local shop non-local purchaser' => $local_org_non_local_purchase->count,
|
||||
'Non-local shop local purchaser' => $non_local_org_local_purchase->count,
|
||||
'Non-local shop non-local purchaser' => $non_local_org_non_local_purchase->count,
|
||||
};
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
local_all => $local_all,
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
1;
|
32
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Snippets.pm
Normal file
32
lib/Pear/LocalLoop/Controller/Api/V1/Customer/Snippets.pm
Normal file
|
@ -0,0 +1,32 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::Customer::Snippets;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
my $data = {
|
||||
user_sum => 0,
|
||||
user_position => 0,
|
||||
};
|
||||
|
||||
my $user_rs = $entity->purchases;
|
||||
$data->{ user_sum } = $user_rs->get_column('value')->sum || 0;
|
||||
$data->{ user_sum } /= 100000;
|
||||
|
||||
my $leaderboard_rs = $c->schema->resultset('Leaderboard');
|
||||
my $monthly_board = $leaderboard_rs->get_latest( 'monthly_total' );
|
||||
if (defined $monthly_board) {
|
||||
my $monthly_values = $monthly_board->values;
|
||||
$data->{ user_position } = $monthly_values ? $monthly_values->find({ entity_id => $entity->id })->position : 0;
|
||||
}
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
snippets => $data,
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
1;
|
|
@ -78,35 +78,30 @@ sub graph_customers_range {
|
|||
);
|
||||
}
|
||||
|
||||
sub graph_customers_last_7_days {
|
||||
my $c = shift;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => 7 );
|
||||
return $c->_customers_last_duration( $duration );
|
||||
}
|
||||
|
||||
sub graph_customers_last_30_days {
|
||||
my $c = shift;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => 30 );
|
||||
return $c->_customers_last_duration( $duration );
|
||||
}
|
||||
sub graph_customers_last_7_days { return shift->_customers_last_duration( 7 ) }
|
||||
sub graph_customers_last_30_days { return shift->_customers_last_duration( 30 ) }
|
||||
|
||||
sub _customers_last_duration {
|
||||
my ( $c, $duration ) = @_;
|
||||
my ( $c, $day_duration ) = @_;
|
||||
|
||||
my $duration = DateTime::Duration->new( days => $day_duration );
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $data = { labels => [], data => [] };
|
||||
|
||||
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
|
||||
|
||||
$data->{bounds} = {
|
||||
min => $c->format_iso_datetime( $start ),
|
||||
max => $c->format_iso_datetime( $end ),
|
||||
};
|
||||
|
||||
while ( $start < $end ) {
|
||||
my $next_end = $start->clone->add( days => 1 );
|
||||
my $transactions = $entity->sales
|
||||
->search_between( $start, $next_end )
|
||||
->count;
|
||||
push @{ $data->{ labels } }, $start->day_name;
|
||||
push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
|
||||
push @{ $data->{ data } }, $transactions;
|
||||
$start->add( days => 1 );
|
||||
}
|
||||
|
@ -132,13 +127,18 @@ sub _sales_last_duration {
|
|||
|
||||
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
|
||||
|
||||
$data->{bounds} = {
|
||||
min => $c->format_iso_datetime( $start ),
|
||||
max => $c->format_iso_datetime( $end ),
|
||||
};
|
||||
|
||||
while ( $start < $end ) {
|
||||
my $next_end = $start->clone->add( days => 1 );
|
||||
my $transactions = $entity->sales
|
||||
->search_between( $start, $next_end )
|
||||
->get_column('value')
|
||||
->sum || 0 + 0;
|
||||
push @{ $data->{ labels } }, $start->day_name;
|
||||
push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
|
||||
push @{ $data->{ data } }, $transactions / 100000;
|
||||
$start->add( days => 1 );
|
||||
}
|
||||
|
@ -164,13 +164,18 @@ sub _purchases_last_duration {
|
|||
|
||||
my ( $start, $end ) = $c->_get_start_end_duration( $duration );
|
||||
|
||||
$data->{bounds} = {
|
||||
min => $c->format_iso_datetime( $start ),
|
||||
max => $c->format_iso_datetime( $end ),
|
||||
};
|
||||
|
||||
while ( $start < $end ) {
|
||||
my $next_end = $start->clone->add( days => 1 );
|
||||
my $transactions = $entity->purchases
|
||||
->search_between( $start, $next_end )
|
||||
->get_column('value')
|
||||
->sum || 0 + 0;
|
||||
push @{ $data->{ labels } }, $start->day_name;
|
||||
push @{ $data->{ labels } }, $c->format_iso_datetime( $start );
|
||||
push @{ $data->{ data } }, $transactions / 100000;
|
||||
$start->add( days => 1 );
|
||||
}
|
||||
|
|
63
lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Pies.pm
Normal file
63
lib/Pear/LocalLoop/Controller/Api/V1/Organisation/Pies.pm
Normal file
|
@ -0,0 +1,63 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::Organisation::Pies;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
|
||||
my $purchase_rs = $entity->purchases;
|
||||
|
||||
my $local_org_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '<', 20000 },
|
||||
'organisation.is_local' => 1,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $local_org_non_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '>=', 20000 },
|
||||
'organisation.is_local' => 1,
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $non_local_org_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '<', 20000 },
|
||||
'organisation.is_local' => [0, undef],
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $non_local_org_non_local_purchase = $purchase_rs->search({
|
||||
"me.distance" => { '>=', 20000 },
|
||||
'organisation.is_local' => [0, undef],
|
||||
},
|
||||
{
|
||||
join => { 'seller' => 'organisation' },
|
||||
}
|
||||
);
|
||||
|
||||
my $local_all = {
|
||||
'Local shop local purchaser' => $local_org_local_purchase->count,
|
||||
'Local shop non-local purchaser' => $local_org_non_local_purchase->count,
|
||||
'Non-local shop local purchaser' => $non_local_org_local_purchase->count,
|
||||
'Non-local shop non-local purchaser' => $non_local_org_non_local_purchase->count,
|
||||
};
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
local_all => $local_all,
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
1;
|
|
@ -6,6 +6,10 @@ sub index {
|
|||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
my $data = {
|
||||
all_sales_count => 0,
|
||||
all_sales_total => 0,
|
||||
all_purchases_count => 0,
|
||||
all_purchases_total => 0,
|
||||
this_month_sales_count => 0,
|
||||
this_month_sales_total => 0,
|
||||
this_month_purchases_count => 0,
|
||||
|
@ -25,6 +29,12 @@ sub index {
|
|||
my $week_ago = $today->clone->subtract( days => 7 );
|
||||
my $month_ago = $today->clone->subtract( days => 30 );
|
||||
|
||||
# TODO check that sales is doing the right thing here
|
||||
my $all_sales = $entity->sales;
|
||||
$data->{ all_sales_count } = $all_sales->count;
|
||||
$data->{ all_sales_total } = $all_sales->get_column('value')->sum || 0;
|
||||
$data->{ all_sales_total } /= 100000;
|
||||
|
||||
my $today_sales = $entity->sales->search_between( $today, $now );
|
||||
$data->{ today_sales_count } = $today_sales->count;
|
||||
$data->{ today_sales_total } = $today_sales->get_column('value')->sum || 0;
|
||||
|
@ -40,6 +50,11 @@ sub index {
|
|||
$data->{ this_month_sales_total } = $month_sales->get_column('value')->sum || 0;
|
||||
$data->{ this_month_sales_total } /= 100000;
|
||||
|
||||
my $all_purchases = $entity->purchases;
|
||||
$data->{ all_purchases_count } = $all_purchases->count;
|
||||
$data->{ all_purchases_total } = $all_purchases->get_column('value')->sum || 0;
|
||||
$data->{ all_purchases_total } /= 100000;
|
||||
|
||||
my $today_purchases = $entity->purchases->search_between( $today, $now );
|
||||
$data->{ today_purchases_count } = $today_purchases->count;
|
||||
$data->{ today_purchases_total } = $today_purchases->get_column('value')->sum || 0;
|
||||
|
|
|
@ -79,6 +79,9 @@ sub index {
|
|||
'organisation.name',
|
||||
'organisation.latitude',
|
||||
'organisation.longitude',
|
||||
'organisation.street_name',
|
||||
'organisation.town',
|
||||
'organisation.postcode',
|
||||
],
|
||||
group_by => [ qw/ organisation.id / ],
|
||||
},
|
||||
|
@ -86,11 +89,14 @@ sub index {
|
|||
|
||||
$org_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
|
||||
|
||||
my $suppliers = [ map {
|
||||
my $suppliers = [ map {
|
||||
{
|
||||
latitude => $_->{organisation}->{latitude} * 1,
|
||||
longitude => $_->{organisation}->{longitude} * 1,
|
||||
name => $_->{organisation}->{name},
|
||||
street_name => $_->{organisation}->{street_name},
|
||||
town => $_->{organisation}->{town},
|
||||
postcode => $_->{organisation}->{postcode},
|
||||
}
|
||||
} $org_rs->all ];
|
||||
|
||||
|
@ -106,4 +112,82 @@ sub index {
|
|||
);
|
||||
}
|
||||
|
||||
sub trail_load {
|
||||
my $c = shift;
|
||||
|
||||
return if $c->validation_error('index');
|
||||
|
||||
my $json = $c->stash->{api_json};
|
||||
|
||||
# Extra custom error, because its funny
|
||||
if ( $json->{north_east}->{latitude} < $json->{south_west}->{latitude} ) {
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->false,
|
||||
errors => [ 'upside_down' ],
|
||||
},
|
||||
status => 400,
|
||||
);
|
||||
}
|
||||
|
||||
my $entity = $c->stash->{api_user}->entity;
|
||||
my $entity_type_object = $entity->type_object;
|
||||
my $orgs_lis = $c->schema->resultset('EntityAssociation')->search(
|
||||
{
|
||||
$json->{association} => 1,
|
||||
},
|
||||
);
|
||||
|
||||
# need: organisations only, with name, latitude, and longitude
|
||||
my $org_rs = $orgs_lis->search_related('entity',
|
||||
{
|
||||
'entity.type' => 'organisation',
|
||||
'organisation.latitude' => { -between => [
|
||||
$json->{south_west}->{latitude},
|
||||
$json->{north_east}->{latitude},
|
||||
] },
|
||||
'organisation.longitude' => { -between => [
|
||||
$json->{south_west}->{longitude},
|
||||
$json->{north_east}->{longitude},
|
||||
] },
|
||||
},
|
||||
{
|
||||
join => [ qw/ organisation / ],
|
||||
columns => [
|
||||
'organisation.name',
|
||||
'organisation.latitude',
|
||||
'organisation.longitude',
|
||||
'organisation.street_name',
|
||||
'organisation.town',
|
||||
'organisation.postcode',
|
||||
],
|
||||
group_by => [ qw/ organisation.id / ],
|
||||
},
|
||||
);
|
||||
|
||||
$org_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
|
||||
|
||||
my $locations = [ map {
|
||||
{
|
||||
latitude => $_->{organisation}->{latitude} * 1,
|
||||
longitude => $_->{organisation}->{longitude} * 1,
|
||||
name => $_->{organisation}->{name},
|
||||
street_name => $_->{organisation}->{street_name},
|
||||
town => $_->{organisation}->{town},
|
||||
postcode => $_->{organisation}->{postcode},
|
||||
}
|
||||
} $org_rs->all ];
|
||||
|
||||
$c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
locations => $locations,
|
||||
self => {
|
||||
latitude => $entity_type_object->latitude,
|
||||
longitude => $entity_type_object->longitude,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
48
lib/Pear/LocalLoop/Controller/Api/V1/User/Medals.pm
Normal file
48
lib/Pear/LocalLoop/Controller/Api/V1/User/Medals.pm
Normal file
|
@ -0,0 +1,48 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::User::Medals;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
use Mojo::JSON qw/true false/;
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
|
||||
# Placeholder data
|
||||
my $global_placeholder = {
|
||||
group_name => {
|
||||
threshold => {
|
||||
awarded => true,
|
||||
awarded_at => '2017-01-02T01:00:00Z',
|
||||
threshold => 1,
|
||||
points => 1,
|
||||
},
|
||||
total => 1,
|
||||
},
|
||||
};
|
||||
my $organisation_placeholder = {
|
||||
org_id => {
|
||||
group_name => {
|
||||
threshold => {
|
||||
awarded => true,
|
||||
awarded_at => '2017-01-02T01:00:00Z',
|
||||
threshold => 1,
|
||||
points => 1,
|
||||
multiplier => 1,
|
||||
},
|
||||
total => 1,
|
||||
},
|
||||
name => 'Placeholder',
|
||||
},
|
||||
};
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
global => $global_placeholder,
|
||||
organisation => $organisation_placeholder,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
39
lib/Pear/LocalLoop/Controller/Api/V1/User/Points.pm
Normal file
39
lib/Pear/LocalLoop/Controller/Api/V1/User/Points.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Controller::Api::V1::User::Points;
|
||||
use Mojo::Base 'Mojolicious::Controller';
|
||||
use Mojo::JSON qw/true false/;
|
||||
|
||||
sub index {
|
||||
my $c = shift;
|
||||
|
||||
my $validation = $c->validation;
|
||||
$validation->input( $c->stash->{api_json} );
|
||||
|
||||
# Placeholder data
|
||||
my $snippets_placeholder = {
|
||||
points_total => 1,
|
||||
point_last => 1,
|
||||
trans_count => 1,
|
||||
avg_multi => 1,
|
||||
};
|
||||
|
||||
my $widget_line_placeholder = { labels => [], data => [] };
|
||||
|
||||
my $widget_progress_placeholder = {
|
||||
this_week => 1,
|
||||
last_week => 1,
|
||||
max => 1,
|
||||
sum => 1,
|
||||
count => 1,
|
||||
};
|
||||
|
||||
return $c->render(
|
||||
json => {
|
||||
success => Mojo::JSON->true,
|
||||
snippets => $snippets_placeholder,
|
||||
widget_line => $widget_line_placeholder,
|
||||
widget_progress => $widget_progress_placeholder,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
10
lib/Pear/LocalLoop/Error.pm
Normal file
10
lib/Pear/LocalLoop/Error.pm
Normal file
|
@ -0,0 +1,10 @@
|
|||
package Pear::LocalLoop::Error;
|
||||
use Moo;
|
||||
extends 'Throwable::Error';
|
||||
|
||||
package Pear::LocalLoop::ImplementationError;
|
||||
use Moo;
|
||||
use namespace::clean;
|
||||
extends 'Pear::LocalLoop::Error';
|
||||
|
||||
1;
|
23
lib/Pear/LocalLoop/Import/LCCCsv.pm
Normal file
23
lib/Pear/LocalLoop/Import/LCCCsv.pm
Normal file
|
@ -0,0 +1,23 @@
|
|||
package Pear::LocalLoop::Import::LCCCsv;
|
||||
use Moo;
|
||||
use Pear::LocalLoop::Error;
|
||||
|
||||
has external_name => (
|
||||
is => 'ro',
|
||||
default => 'LCC CSV',
|
||||
);
|
||||
|
||||
has csv_required_columns => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
Pear::LocalLoop::ImplementationError->throw("Must be implemented by child class");
|
||||
},
|
||||
);
|
||||
|
||||
with qw/
|
||||
Pear::LocalLoop::Import::Role::ExternalName
|
||||
Pear::LocalLoop::Import::Role::Schema
|
||||
Pear::LocalLoop::Import::Role::CSV
|
||||
/;
|
||||
|
||||
1;
|
43
lib/Pear/LocalLoop/Import/LCCCsv/Postcodes.pm
Normal file
43
lib/Pear/LocalLoop/Import/LCCCsv/Postcodes.pm
Normal file
|
@ -0,0 +1,43 @@
|
|||
package Pear::LocalLoop::Import::LCCCsv::Postcodes;
|
||||
use Moo;
|
||||
|
||||
use Geo::UK::Postcode::Regex;
|
||||
|
||||
extends qw/Pear::LocalLoop::Import::LCCCsv/;
|
||||
|
||||
has '+csv_required_columns' => (
|
||||
builder => sub { return [ qw/
|
||||
postcode
|
||||
ward
|
||||
/ ]},
|
||||
);
|
||||
|
||||
sub import_csv {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->check_headers;
|
||||
|
||||
while ( my $row = $self->get_csv_line ) {
|
||||
$self->_row_to_result($row);
|
||||
}
|
||||
}
|
||||
|
||||
sub _row_to_result {
|
||||
my ( $self, $row ) = @_;
|
||||
|
||||
my $postcode_obj = Geo::UK::Postcode::Regex->parse( $row->{postcode} );
|
||||
|
||||
my $ward = $self->schema->resultset('GbWard')->find_or_create(ward => $row->{ward});
|
||||
|
||||
my $postcode_r = $self->schema->resultset('GbPostcode')->find({
|
||||
outcode => $postcode_obj->{outcode},
|
||||
incode => $postcode_obj->{incode},
|
||||
});
|
||||
|
||||
return unless $postcode_r;
|
||||
return if $postcode_r->ward;
|
||||
|
||||
$postcode_r->update({ ward_id => $ward->id });
|
||||
}
|
||||
|
||||
1;
|
48
lib/Pear/LocalLoop/Import/LCCCsv/Suppliers.pm
Normal file
48
lib/Pear/LocalLoop/Import/LCCCsv/Suppliers.pm
Normal file
|
@ -0,0 +1,48 @@
|
|||
package Pear::LocalLoop::Import::LCCCsv::Suppliers;
|
||||
use Moo;
|
||||
|
||||
extends qw/Pear::LocalLoop::Import::LCCCsv/;
|
||||
|
||||
has '+csv_required_columns' => (
|
||||
builder => sub { return [ qw/
|
||||
supplier_id
|
||||
name
|
||||
/ ]},
|
||||
);
|
||||
|
||||
sub import_csv {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->check_headers;
|
||||
|
||||
while ( my $row = $self->get_csv_line ) {
|
||||
$self->_row_to_result($row);
|
||||
}
|
||||
}
|
||||
|
||||
sub _row_to_result {
|
||||
my ( $self, $row ) = @_;
|
||||
|
||||
my $addr2 = $row->{post_town};
|
||||
|
||||
my $address = ( defined $addr2 ? ( $row->{"address line 2"} . ' ' . $addr2) : $row->{"address line 2"} );
|
||||
|
||||
return if $self->external_result->organisations->find({external_id => $row->{supplier_id}});
|
||||
|
||||
$self->schema->resultset('Entity')->create({
|
||||
type => 'organisation',
|
||||
organisation => {
|
||||
name => $row->{name},
|
||||
street_name => $row->{"address line 1"},
|
||||
town => $address,
|
||||
postcode => $row->{post_code},
|
||||
country => $row->{country_code},
|
||||
external_reference => [ {
|
||||
external_reference => $self->external_result,
|
||||
external_id => $row->{supplier_id},
|
||||
} ],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
128
lib/Pear/LocalLoop/Import/LCCCsv/Transactions.pm
Normal file
128
lib/Pear/LocalLoop/Import/LCCCsv/Transactions.pm
Normal file
|
@ -0,0 +1,128 @@
|
|||
package Pear::LocalLoop::Import::LCCCsv::Transactions;
|
||||
use Moo;
|
||||
use DateTime;
|
||||
use DateTime::Format::Strptime;
|
||||
|
||||
use Geo::UK::Postcode::Regex;
|
||||
|
||||
extends qw/Pear::LocalLoop::Import::LCCCsv/;
|
||||
|
||||
has target_entity_id => (
|
||||
is => 'ro',
|
||||
required => 1,
|
||||
);
|
||||
|
||||
has target_entity => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
my $self = shift;
|
||||
my $entity = $self->schema->resultset('Entity')->find($self->target_entity_id);
|
||||
Pear::LocalLoop::Error->throw("Cannot find LCC Entity, did you pass the right id?") unless $entity;
|
||||
return $entity;
|
||||
},
|
||||
);
|
||||
|
||||
has '+csv_required_columns' => (
|
||||
builder => sub {return [ (
|
||||
'transaction_id',
|
||||
'supplier_id',
|
||||
'net_amount',
|
||||
'vat amount',
|
||||
'gross_amount',
|
||||
) ]},
|
||||
);
|
||||
|
||||
sub import_csv {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->check_headers;
|
||||
my $lcc_org = $self->target_entity;
|
||||
|
||||
while ( my $row = $self->get_csv_line ) {
|
||||
$self->_row_to_result($row, $lcc_org);
|
||||
}
|
||||
}
|
||||
|
||||
sub _row_to_result {
|
||||
my ($self, $row, $lcc_org) = @_;
|
||||
|
||||
my $supplier_id = $row->{supplier_id};
|
||||
|
||||
my $organisation = $self->schema->resultset('Organisation')->find({
|
||||
'external_reference.external_id' => $supplier_id
|
||||
}, { join => 'external_reference' });
|
||||
|
||||
unless ($organisation) {
|
||||
# Pear::LocalLoop::Error->throw("Cannot find an organisation with supplier_id $supplier_id");
|
||||
|
||||
return unless $row->{'Company Name (WHO)'};
|
||||
|
||||
my $town = $row->{post_town};
|
||||
|
||||
unless ($town) {
|
||||
my $postcode_obj = Geo::UK::Postcode::Regex->parse( $row->{post_code} );
|
||||
$town = Geo::UK::Postcode::Regex->outcode_to_posttowns($postcode_obj->{outcode});
|
||||
$town = $town->[0];
|
||||
}
|
||||
|
||||
return if $self->external_result->organisations->find({external_id => $row->{supplier_id}});
|
||||
|
||||
$organisation = $self->schema->resultset('Entity')->create({
|
||||
type => 'organisation',
|
||||
organisation => {
|
||||
name => $row->{'Company Name (WHO)'},
|
||||
street_name => $row->{"address line 1"},
|
||||
town => $town,
|
||||
postcode => $row->{post_code},
|
||||
country => $row->{country_code},
|
||||
external_reference => [ {
|
||||
external_reference => $self->external_result,
|
||||
external_id => $row->{supplier_id},
|
||||
} ],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
my $date_formatter = DateTime::Format::Strptime->new(
|
||||
pattern => '%m/%d/%Y',
|
||||
time_zone => 'Europe/London'
|
||||
);
|
||||
|
||||
my $paid_date = ( $row->{paid_date} ?
|
||||
$date_formatter->parse_datetime($row->{paid_date}) :
|
||||
$date_formatter->parse_datetime($row->{invoice_date}) );
|
||||
|
||||
my $gross_value = $row->{gross_amount};
|
||||
$gross_value =~ s/,//g;
|
||||
my $sales_tax_value = $row->{"vat amount"};
|
||||
$sales_tax_value =~ s/,//g;
|
||||
my $net_value = $row->{net_amount};
|
||||
$net_value =~ s/,//g;
|
||||
|
||||
# TODO negative values are sometimes present
|
||||
my $external_transaction = $self->external_result->update_or_create_related('transactions', { # This is a TransactionExternal result
|
||||
external_id => $row->{transaction_id},
|
||||
});
|
||||
|
||||
my $transaction_result = $external_transaction->update_or_create_related( 'transaction', {
|
||||
seller => $organisation->entity,
|
||||
buyer => $lcc_org,
|
||||
purchase_time => $paid_date,
|
||||
value => $gross_value * 100000,
|
||||
});
|
||||
|
||||
my $meta_result = $transaction_result->update_or_create_related('meta', {
|
||||
gross_value => $gross_value * 100000,
|
||||
sales_tax_value => $sales_tax_value * 100000,
|
||||
net_value => $net_value * 100000,
|
||||
($row->{"local service"} ? (local_service => $row->{"local service"}) : ()),
|
||||
($row->{"regional service"} ? (regional_service => $row->{"regional service"}) : ()),
|
||||
($row->{"national service"} ? (national_service => $row->{"national service"}) : ()),
|
||||
($row->{"private household rebate"} ? (private_household_rebate => $row->{"private household rebate"}) : ()),
|
||||
($row->{"business tax and rebate"} ? (business_tax_and_rebate => $row->{"business tax and rebate"}) : ()),
|
||||
($row->{"stat loc gov"} ? (stat_loc_gov => $row->{"stat loc gov"}) : ()),
|
||||
($row->{"central loc gov"} ? (central_loc_gov => $row->{"central loc gov"}) : ()),
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
88
lib/Pear/LocalLoop/Import/Role/CSV.pm
Normal file
88
lib/Pear/LocalLoop/Import/Role/CSV.pm
Normal file
|
@ -0,0 +1,88 @@
|
|||
package Pear::LocalLoop::Import::Role::CSV;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Moo::Role;
|
||||
use Text::CSV;
|
||||
use Try::Tiny;
|
||||
use Pear::LocalLoop::Error;
|
||||
|
||||
requires 'csv_required_columns';
|
||||
|
||||
has csv_file => (
|
||||
is => 'ro',
|
||||
predicate => 1,
|
||||
);
|
||||
|
||||
has csv_string => (
|
||||
is => 'ro',
|
||||
predicate => 1,
|
||||
);
|
||||
|
||||
has csv_error => (
|
||||
is => 'ro',
|
||||
predicate => 1,
|
||||
);
|
||||
|
||||
has _csv_filehandle => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
my $self = shift;
|
||||
my $fh;
|
||||
if ( $self->has_csv_file ) {
|
||||
open $fh, '<', $self->csv_file;
|
||||
} elsif ( $self->has_csv_string ) {
|
||||
my $string = $self->csv_string;
|
||||
open $fh, '<', \$string;
|
||||
} else {
|
||||
die "Must provide csv_file or csv_string"
|
||||
}
|
||||
return $fh;
|
||||
}
|
||||
);
|
||||
|
||||
has text_csv_options => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
return {
|
||||
binary => 1,
|
||||
allow_whitespace => 1,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
has _text_csv => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
return Text::CSV->new(shift->text_csv_options);
|
||||
}
|
||||
);
|
||||
|
||||
has csv_data => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
my $self = shift;
|
||||
my $header_check = $self->check_headers;
|
||||
return 0 unless $header_check;
|
||||
return $self->_text_csv->getline_hr_all( $self->_csv_filehandle );
|
||||
}
|
||||
);
|
||||
|
||||
sub get_csv_line {
|
||||
my $self = shift;
|
||||
return $self->_text_csv->getline_hr( $self->_csv_filehandle );
|
||||
}
|
||||
|
||||
sub check_headers {
|
||||
my $self = shift;
|
||||
my $req_headers = $self->csv_required_columns;
|
||||
my @headers;
|
||||
@headers = $self->_text_csv->header( $self->_csv_filehandle );
|
||||
my %header_map = ( map { $_ => 1 } @headers );
|
||||
for my $req_header ( @$req_headers ) {
|
||||
next if $header_map{$req_header};
|
||||
die "Require header [" . $req_header . "]";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
19
lib/Pear/LocalLoop/Import/Role/ExternalName.pm
Normal file
19
lib/Pear/LocalLoop/Import/Role/ExternalName.pm
Normal file
|
@ -0,0 +1,19 @@
|
|||
package Pear::LocalLoop::Import::Role::ExternalName;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Moo::Role;
|
||||
|
||||
requires qw/
|
||||
external_name
|
||||
schema
|
||||
/;
|
||||
|
||||
has external_result => (
|
||||
is => 'lazy',
|
||||
builder => sub {
|
||||
my $self = shift;
|
||||
return $self->schema->resultset('ExternalReference')->find_or_create({ name => $self->external_name });
|
||||
}
|
||||
);
|
||||
|
||||
1;
|
11
lib/Pear/LocalLoop/Import/Role/Schema.pm
Normal file
11
lib/Pear/LocalLoop/Import/Role/Schema.pm
Normal file
|
@ -0,0 +1,11 @@
|
|||
package Pear::LocalLoop::Import::Role::Schema;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Moo::Role;
|
||||
|
||||
has schema => (
|
||||
is => 'ro',
|
||||
required => 1,
|
||||
);
|
||||
|
||||
1;
|
24
lib/Pear/LocalLoop/Plugin/Currency.pm
Normal file
24
lib/Pear/LocalLoop/Plugin/Currency.pm
Normal file
|
@ -0,0 +1,24 @@
|
|||
package Pear::LocalLoop::Plugin::Currency;
|
||||
use Mojo::Base 'Mojolicious::Plugin';
|
||||
|
||||
sub register {
|
||||
my ( $plugin, $app, $cong ) = @_;
|
||||
|
||||
$app->helper( parse_currency => sub {
|
||||
my ( $c, $currency_string ) = @_;
|
||||
my $value;
|
||||
if ( $currency_string =~ /^£([\d.]+)/ ) {
|
||||
$value = $1 * 1;
|
||||
} elsif ( $currency_string =~ /^([\d.]+)/ ) {
|
||||
$value = $1 * 1;
|
||||
}
|
||||
return $value;
|
||||
});
|
||||
|
||||
$app->helper( format_currency_from_db => sub {
|
||||
my ( $c, $value ) = @_;
|
||||
return sprintf( '£%.2f', $value / 100000 );
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
|
@ -66,6 +66,7 @@ sub register {
|
|||
|
||||
$app->helper( format_iso_datetime => sub {
|
||||
my ( $c, $datetime_obj ) = @_;
|
||||
return unless defined $datetime_obj;
|
||||
return $c->iso_datetime_parser->format_datetime(
|
||||
$datetime_obj,
|
||||
);
|
||||
|
|
40
lib/Pear/LocalLoop/Plugin/Minion.pm
Normal file
40
lib/Pear/LocalLoop/Plugin/Minion.pm
Normal file
|
@ -0,0 +1,40 @@
|
|||
package Pear::LocalLoop::Plugin::Minion;
|
||||
use Mojo::Base 'Mojolicious::Plugin';
|
||||
|
||||
use Mojo::Loader qw/ find_modules load_class /;
|
||||
|
||||
sub register {
|
||||
my ( $plugin, $app, $cong ) = @_;
|
||||
|
||||
if ( defined $app->config->{minion} ) {
|
||||
$app->log->debug('Setting up Minion tasks');
|
||||
$app->plugin('Minion' => $app->config->{minion} );
|
||||
|
||||
$app->log->debug('Loaded Minion Job packages:');
|
||||
|
||||
my $job_namespace = __PACKAGE__ . '::Job';
|
||||
my @modules = find_modules $job_namespace;
|
||||
for my $package ( @modules ) {
|
||||
my ( $job_name ) = $package =~ /${job_namespace}::(.*)$/;
|
||||
$app->log->debug( $package );
|
||||
if (my $e = load_class $package) {
|
||||
die ref $e ? "Exception: $e" : "$package not found";
|
||||
}
|
||||
$app->minion->add_task(
|
||||
$job_name => sub {
|
||||
my ( $job, @args ) = @_;
|
||||
my $job_runner = $package->new(
|
||||
job => $job,
|
||||
);
|
||||
$job_runner->run( @args );
|
||||
}
|
||||
);
|
||||
}
|
||||
# $app->minion->enqueue('test' => [ 'test arg 1', 'test_arg 2' ] );
|
||||
} else {
|
||||
$app->log->debug('No Minion Config');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1;
|
12
lib/Pear/LocalLoop/Plugin/Minion/Job.pm
Normal file
12
lib/Pear/LocalLoop/Plugin/Minion/Job.pm
Normal file
|
@ -0,0 +1,12 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job;
|
||||
use Mojo::Base -base;
|
||||
|
||||
has [ qw/ job / ];
|
||||
|
||||
has app => sub { shift->job->app };
|
||||
|
||||
sub run {
|
||||
die ( __PACKAGE__ . " must implement run sub" );
|
||||
}
|
||||
|
||||
1;
|
15
lib/Pear/LocalLoop/Plugin/Minion/Job/csv_postcode_import.pm
Normal file
15
lib/Pear/LocalLoop/Plugin/Minion/Job/csv_postcode_import.pm
Normal file
|
@ -0,0 +1,15 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::csv_postcode_import;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
use Pear::LocalLoop::Import::LCCCsv::Postcodes;
|
||||
|
||||
sub run {
|
||||
my ( $self, $filename ) = @_;
|
||||
|
||||
my $csv_import = Pear::LocalLoop::Import::LCCCsv::Postcodes->new(
|
||||
csv_file => $filename,
|
||||
schema => $self->app->schema
|
||||
)->import_csv;
|
||||
}
|
||||
|
||||
1;
|
15
lib/Pear/LocalLoop/Plugin/Minion/Job/csv_supplier_import.pm
Normal file
15
lib/Pear/LocalLoop/Plugin/Minion/Job/csv_supplier_import.pm
Normal file
|
@ -0,0 +1,15 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::csv_supplier_import;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
use Pear::LocalLoop::Import::LCCCsv::Suppliers;
|
||||
|
||||
sub run {
|
||||
my ( $self, $filename ) = @_;
|
||||
|
||||
my $csv_import = Pear::LocalLoop::Import::LCCCsv::Suppliers->new(
|
||||
csv_file => $filename,
|
||||
schema => $self->app->schema
|
||||
)->import_csv;
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,16 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::csv_transaction_import;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
use Pear::LocalLoop::Import::LCCCsv::Transactions;
|
||||
|
||||
sub run {
|
||||
my ($self, $filename, $entity_id) = @_;
|
||||
|
||||
Pear::LocalLoop::Import::LCCCsv::Transactions->new(
|
||||
csv_file => $filename,
|
||||
schema => $self->app->schema,
|
||||
target_entity_id => $entity_id,
|
||||
)->import_csv;
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,30 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::entity_postcode_lookup;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
sub run {
|
||||
my ( $self, $entity_id ) = @_;
|
||||
|
||||
my $entity_rs = $self->app->schema->resultset('Entity');
|
||||
$entity_rs = $entity_rs->search({id => $entity_id }) if $entity_id;
|
||||
|
||||
while ( my $entity = $entity_rs->next ) {
|
||||
my $obj = $entity->type_object;
|
||||
next unless $obj;
|
||||
|
||||
my $postcode_obj = Geo::UK::Postcode::Regex->parse( $obj->postcode );
|
||||
|
||||
unless ( defined $postcode_obj && $postcode_obj->{non_geographical} ) {
|
||||
my $pc_result = $self->app->schema->resultset('GbPostcode')->find({
|
||||
incode => $postcode_obj->{incode},
|
||||
outcode => $postcode_obj->{outcode},
|
||||
});
|
||||
if ( defined $pc_result ) {
|
||||
$entity->update_or_create_related('postcode', {
|
||||
gb_postcode => $pc_result,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
12
lib/Pear/LocalLoop/Plugin/Minion/Job/leaderboards_recalc.pm
Normal file
12
lib/Pear/LocalLoop/Plugin/Minion/Job/leaderboards_recalc.pm
Normal file
|
@ -0,0 +1,12 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::leaderboards_recalc;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
sub run {
|
||||
my ( $self, @args ) = @_;
|
||||
|
||||
my $leaderboard_rs = $self->app->schema->resultset('Leaderboard');
|
||||
|
||||
$leaderboard_rs->recalculate_all;
|
||||
}
|
||||
|
||||
1;
|
13
lib/Pear/LocalLoop/Plugin/Minion/Job/test.pm
Normal file
13
lib/Pear/LocalLoop/Plugin/Minion/Job/test.pm
Normal file
|
@ -0,0 +1,13 @@
|
|||
package Pear::LocalLoop::Plugin::Minion::Job::test;
|
||||
use Mojo::Base 'Pear::LocalLoop::Plugin::Minion::Job';
|
||||
|
||||
sub run {
|
||||
my ( $self, @args ) = @_;
|
||||
|
||||
$self->job->app->log->debug( 'Testing Job' );
|
||||
for my $arg ( @args ) {
|
||||
$self->job->app->log->debug( $arg );
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -6,7 +6,7 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Schema';
|
||||
|
||||
our $VERSION = 16;
|
||||
our $VERSION = 30;
|
||||
|
||||
__PACKAGE__->load_namespaces;
|
||||
|
||||
|
|
46
lib/Pear/LocalLoop/Schema/Result/Category.pm
Normal file
46
lib/Pear/LocalLoop/Schema/Result/Category.pm
Normal file
|
@ -0,0 +1,46 @@
|
|||
package Pear::LocalLoop::Schema::Result::Category;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("category");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
# See here for all possible options http://simplelineicons.com/
|
||||
"line_icon" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 1,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint(["name"]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"transaction_category",
|
||||
"Pear::LocalLoop::Schema::Result::TransactionCategory",
|
||||
{ "foreign.category_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 1 },
|
||||
);
|
||||
|
||||
__PACKAGE__->many_to_many(
|
||||
"transactions",
|
||||
"transaction_category",
|
||||
"transaction",
|
||||
);
|
||||
|
||||
1;
|
|
@ -37,6 +37,16 @@ __PACKAGE__->might_have(
|
|||
"Pear::LocalLoop::Schema::Result::User" => "entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->might_have(
|
||||
"associations",
|
||||
"Pear::LocalLoop::Schema::Result::EntityAssociation" => "entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->might_have(
|
||||
"postcode",
|
||||
"Pear::LocalLoop::Schema::Result::EntityPostcode" => "entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"purchases",
|
||||
"Pear::LocalLoop::Schema::Result::Transaction",
|
||||
|
@ -51,6 +61,34 @@ __PACKAGE__->has_many(
|
|||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"global_user_medals",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalUserMedals",
|
||||
{ "foreign.entity_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"global_user_medal_progress",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalUserMedalProgress",
|
||||
{ "foreign.entity_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"org_user_medals",
|
||||
"Pear::LocalLoop::Schema::Result::OrgUserMedals",
|
||||
{ "foreign.entity_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"org_user_medal_progress",
|
||||
"Pear::LocalLoop::Schema::Result::OrgUserMedalProgress",
|
||||
{ "foreign.entity_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
sub name {
|
||||
my $self = shift;
|
||||
|
||||
|
|
39
lib/Pear/LocalLoop/Schema/Result/EntityAssociation.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/EntityAssociation.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::EntityAssociation;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("entity_association");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"entity_id" => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
"lis" => {
|
||||
data_type => 'boolean',
|
||||
default_value => undef,
|
||||
is_nullable => 1,
|
||||
},
|
||||
"esta" => {
|
||||
data_type => 'boolean',
|
||||
default_value => undef,
|
||||
is_nullable => 1,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
44
lib/Pear/LocalLoop/Schema/Result/EntityPostcode.pm
Normal file
44
lib/Pear/LocalLoop/Schema/Result/EntityPostcode.pm
Normal file
|
@ -0,0 +1,44 @@
|
|||
package Pear::LocalLoop::Schema::Result::EntityPostcode;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table('entities_postcodes');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
outcode => {
|
||||
data_type => 'char',
|
||||
size => 4,
|
||||
is_nullable => 0,
|
||||
},
|
||||
incode => {
|
||||
data_type => 'char',
|
||||
size => 3,
|
||||
is_nullable => 0,
|
||||
},
|
||||
entity_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key(qw/ outcode incode entity_id /);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"gb_postcode",
|
||||
"Pear::LocalLoop::Schema::Result::GbPostcode",
|
||||
{
|
||||
"foreign.outcode" => "self.outcode",
|
||||
"foreign.incode" => "self.incode",
|
||||
},
|
||||
);
|
||||
1;
|
39
lib/Pear/LocalLoop/Schema/Result/ExternalReference.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/ExternalReference.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::ExternalReference;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("external_references");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint([ qw/name/ ]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
'transactions',
|
||||
"Pear::LocalLoop::Schema::Result::TransactionExternal",
|
||||
{ 'foreign.external_reference_id' => 'self.id' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
'organisations',
|
||||
"Pear::LocalLoop::Schema::Result::OrganisationExternal",
|
||||
{ 'foreign.external_reference_id' => 'self.id' },
|
||||
);
|
||||
|
||||
1;
|
|
@ -31,8 +31,19 @@ __PACKAGE__->add_columns(
|
|||
is_nullable => 1,
|
||||
default_value => undef,
|
||||
},
|
||||
ward_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 1,
|
||||
default_value => undef,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key(qw/ outcode incode /);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"ward",
|
||||
"Pear::LocalLoop::Schema::Result::GbWard",
|
||||
"ward_id",
|
||||
);
|
||||
|
||||
1;
|
||||
|
|
32
lib/Pear/LocalLoop/Schema/Result/GbWard.pm
Normal file
32
lib/Pear/LocalLoop/Schema/Result/GbWard.pm
Normal file
|
@ -0,0 +1,32 @@
|
|||
package Pear::LocalLoop::Schema::Result::GbWard;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table('gb_wards');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
ward => {
|
||||
data_type => 'varchar',
|
||||
size => 100,
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key(qw/ id /);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"postcodes",
|
||||
"Pear::LocalLoop::Schema::Result::GbPostcode",
|
||||
{ "foreign.ward_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
1;
|
34
lib/Pear/LocalLoop/Schema/Result/GlobalMedalGroup.pm
Normal file
34
lib/Pear/LocalLoop/Schema/Result/GlobalMedalGroup.pm
Normal file
|
@ -0,0 +1,34 @@
|
|||
package Pear::LocalLoop::Schema::Result::GlobalMedalGroup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("global_medal_group");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint(["group_name"]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"medals",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalMedals",
|
||||
{ "foreign.group_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
1;
|
39
lib/Pear/LocalLoop/Schema/Result/GlobalMedals.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/GlobalMedals.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::GlobalMedals;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("global_medals");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"threshold" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"points" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
45
lib/Pear/LocalLoop/Schema/Result/GlobalUserMedalProgress.pm
Normal file
45
lib/Pear/LocalLoop/Schema/Result/GlobalUserMedalProgress.pm
Normal file
|
@ -0,0 +1,45 @@
|
|||
package Pear::LocalLoop::Schema::Result::GlobalUserMedalProgress;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("global_user_medal_progress");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"entity_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"total" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
59
lib/Pear/LocalLoop/Schema/Result/GlobalUserMedals.pm
Normal file
59
lib/Pear/LocalLoop/Schema/Result/GlobalUserMedals.pm
Normal file
|
@ -0,0 +1,59 @@
|
|||
package Pear::LocalLoop::Schema::Result::GlobalUserMedals;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
InflateColumn::DateTime
|
||||
TimeStamp
|
||||
/);
|
||||
|
||||
__PACKAGE__->table("global_user_medals");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"entity_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"points" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"awarded_at" => {
|
||||
data_type => "datetime",
|
||||
is_nullable => 0,
|
||||
set_on_create => 1,
|
||||
},
|
||||
"threshold" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::GlobalMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
|
@ -39,7 +39,7 @@ __PACKAGE__->add_columns(
|
|||
size => 255,
|
||||
},
|
||||
transaction_id => {
|
||||
data_type => 'varchar',
|
||||
data_type => 'integer',
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 1,
|
||||
},
|
||||
|
|
34
lib/Pear/LocalLoop/Schema/Result/OrgMedalGroup.pm
Normal file
34
lib/Pear/LocalLoop/Schema/Result/OrgMedalGroup.pm
Normal file
|
@ -0,0 +1,34 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrgMedalGroup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("org_medal_group");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint(["group_name"]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"medals",
|
||||
"Pear::LocalLoop::Schema::Result::OrgMedals",
|
||||
{ "foreign.group_id" => "self.id" },
|
||||
{ cascade_copy => 0, cascade_delete => 0 },
|
||||
);
|
||||
|
||||
1;
|
39
lib/Pear/LocalLoop/Schema/Result/OrgMedals.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/OrgMedals.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrgMedals;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("org_medals");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"threshold" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"points" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::OrgMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
45
lib/Pear/LocalLoop/Schema/Result/OrgUserMedalProgress.pm
Normal file
45
lib/Pear/LocalLoop/Schema/Result/OrgUserMedalProgress.pm
Normal file
|
@ -0,0 +1,45 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrgUserMedalProgress;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("org_user_medal_progress");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"entity_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"total" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::OrgMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
59
lib/Pear/LocalLoop/Schema/Result/OrgUserMedals.pm
Normal file
59
lib/Pear/LocalLoop/Schema/Result/OrgUserMedals.pm
Normal file
|
@ -0,0 +1,59 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrgUserMedals;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
InflateColumn::DateTime
|
||||
TimeStamp
|
||||
/);
|
||||
|
||||
__PACKAGE__->table("org_user_medals");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"entity_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"group_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"points" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"awarded_at" => {
|
||||
data_type => "datetime",
|
||||
is_nullable => 0,
|
||||
set_on_create => 1,
|
||||
},
|
||||
"threshold" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"entity",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"group",
|
||||
"Pear::LocalLoop::Schema::Result::OrgMedalGroup",
|
||||
{ id => "group_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
|
@ -10,71 +10,92 @@ __PACKAGE__->load_components("InflateColumn::DateTime", "FilterColumn");
|
|||
__PACKAGE__->table("organisations");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'integer',
|
||||
id => {
|
||||
data_type => 'integer',
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
is_nullable => 0,
|
||||
},
|
||||
entity_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
entity_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
name => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
name => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
street_name => {
|
||||
data_type => 'text',
|
||||
street_name => {
|
||||
data_type => 'text',
|
||||
is_nullable => 1,
|
||||
},
|
||||
town => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
town => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
postcode => {
|
||||
data_type => 'varchar',
|
||||
size => 16,
|
||||
postcode => {
|
||||
data_type => 'varchar',
|
||||
size => 16,
|
||||
is_nullable => 1,
|
||||
},
|
||||
country => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
country => {
|
||||
data_type => 'varchar',
|
||||
size => 255,
|
||||
is_nullable => 1,
|
||||
},
|
||||
sector => {
|
||||
data_type => 'varchar',
|
||||
size => 1,
|
||||
# Stores codes based on https://www.ons.gov.uk/methodology/classificationsandstandards/ukstandardindustrialclassificationofeconomicactivities/uksic2007
|
||||
sector => {
|
||||
data_type => 'varchar',
|
||||
size => 1,
|
||||
is_nullable => 1,
|
||||
},
|
||||
pending => {
|
||||
data_type => 'boolean',
|
||||
default => \"false",
|
||||
pending => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
is_local => {
|
||||
data_type => 'boolean',
|
||||
default => undef,
|
||||
is_local => {
|
||||
data_type => 'boolean',
|
||||
default_value => undef,
|
||||
is_nullable => 1,
|
||||
},
|
||||
is_fair => {
|
||||
data_type => 'boolean',
|
||||
default_value => undef,
|
||||
is_nullable => 1,
|
||||
},
|
||||
submitted_by_id => {
|
||||
data_type => 'integer',
|
||||
data_type => 'integer',
|
||||
is_nullable => 1,
|
||||
},
|
||||
latitude => {
|
||||
data_type => 'decimal',
|
||||
size => [8,5],
|
||||
is_nullable => 1,
|
||||
latitude => {
|
||||
data_type => 'decimal',
|
||||
size => [ 8, 5 ],
|
||||
is_nullable => 1,
|
||||
default_value => undef,
|
||||
},
|
||||
longitude => {
|
||||
data_type => 'decimal',
|
||||
size => [8,5],
|
||||
is_nullable => 1,
|
||||
longitude => {
|
||||
data_type => 'decimal',
|
||||
size => [ 8, 5 ],
|
||||
is_nullable => 1,
|
||||
default_value => undef,
|
||||
},
|
||||
type_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 1,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
social_type_id => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 1,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
is_anchor => {
|
||||
data_type => 'boolean',
|
||||
is_nullable => 0,
|
||||
default_value => \'FALSE',
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
|
@ -85,6 +106,24 @@ __PACKAGE__->belongs_to(
|
|||
"entity_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"organisation_type",
|
||||
"Pear::LocalLoop::Schema::Result::OrganisationType",
|
||||
"type_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"social_type",
|
||||
"Pear::LocalLoop::Schema::Result::OrganisationSocialType",
|
||||
"social_type_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"external_reference",
|
||||
"Pear::LocalLoop::Schema::Result::OrganisationExternal",
|
||||
{ 'foreign.org_id' => 'self.id' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"payroll",
|
||||
"Pear::LocalLoop::Schema::Result::OrganisationPayroll",
|
||||
|
@ -93,34 +132,45 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
__PACKAGE__->filter_column(
|
||||
pending => {
|
||||
pending => {
|
||||
filter_to_storage => 'to_bool',
|
||||
},
|
||||
is_local => {
|
||||
is_local => {
|
||||
filter_to_storage => 'to_bool',
|
||||
},
|
||||
is_anchor => {
|
||||
filter_to_storage => 'to_bool',
|
||||
}
|
||||
);
|
||||
|
||||
# Only works when calling ->deploy, but atleast helps for tests
|
||||
sub sqlt_deploy_hook {
|
||||
my ( $source_instance, $sqlt_table ) = @_;
|
||||
my ($source_instance, $sqlt_table) = @_;
|
||||
my $pending_field = $sqlt_table->get_field('pending');
|
||||
if ( $sqlt_table->schema->translator->producer_type =~ /SQLite$/ ) {
|
||||
if ($sqlt_table->schema->translator->producer_type =~ /SQLite$/) {
|
||||
$pending_field->{default_value} = 0;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$pending_field->{default_value} = \"false";
|
||||
}
|
||||
}
|
||||
|
||||
sub to_bool {
|
||||
my ( $self, $val ) = @_;
|
||||
return if ! defined $val;
|
||||
my ($self, $val) = @_;
|
||||
return if !defined $val;
|
||||
my $driver_name = $self->result_source->schema->storage->dbh->{Driver}->{Name};
|
||||
if ( $driver_name eq 'SQLite' ) {
|
||||
if ($driver_name eq 'SQLite') {
|
||||
return $val ? 1 : 0;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return $val ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
||||
sub user {
|
||||
my $self = shift;
|
||||
|
||||
return $self->entity->user;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
49
lib/Pear/LocalLoop/Schema/Result/OrganisationExternal.pm
Normal file
49
lib/Pear/LocalLoop/Schema/Result/OrganisationExternal.pm
Normal file
|
@ -0,0 +1,49 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrganisationExternal;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("organisations_external");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"org_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"external_reference_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"external_id" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint([ qw/external_reference_id external_id/ ]);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"organisation",
|
||||
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||
{ 'foreign.id' => 'self.org_id' },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"external_reference",
|
||||
"Pear::LocalLoop::Schema::Result::ExternalReference",
|
||||
{ 'foreign.id' => 'self.external_reference_id' },
|
||||
);
|
||||
|
||||
1;
|
39
lib/Pear/LocalLoop/Schema/Result/OrganisationSocialType.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/OrganisationSocialType.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrganisationSocialType;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("organisation_social_types");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"key" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint([ qw/key/ ]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"organisations",
|
||||
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||
{ 'foreign.social_type_id' => 'self.id' },
|
||||
);
|
||||
|
||||
1;
|
||||
1;
|
38
lib/Pear/LocalLoop/Schema/Result/OrganisationType.pm
Normal file
38
lib/Pear/LocalLoop/Schema/Result/OrganisationType.pm
Normal file
|
@ -0,0 +1,38 @@
|
|||
package Pear::LocalLoop::Schema::Result::OrganisationType;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("organisation_types");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"key" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"name" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint([ qw/key/ ]);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"organisations",
|
||||
"Pear::LocalLoop::Schema::Result::Organisation",
|
||||
{ 'foreign.type_id' => 'self.id' },
|
||||
);
|
||||
|
||||
1;
|
|
@ -48,6 +48,11 @@ __PACKAGE__->add_columns(
|
|||
is_nullable => 0,
|
||||
set_on_create => 1,
|
||||
},
|
||||
"essential" => {
|
||||
data_type => "boolean",
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
distance => {
|
||||
data_type => 'numeric',
|
||||
size => [15],
|
||||
|
@ -71,4 +76,31 @@ __PACKAGE__->belongs_to(
|
|||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
__PACKAGE__->might_have(
|
||||
"category",
|
||||
"Pear::LocalLoop::Schema::Result::TransactionCategory" => "transaction_id",
|
||||
);
|
||||
|
||||
__PACKAGE__->has_one(
|
||||
"meta",
|
||||
"Pear::LocalLoop::Schema::Result::TransactionMeta",
|
||||
{ 'foreign.transaction_id' => 'self.id' },
|
||||
);
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"external_reference",
|
||||
"Pear::LocalLoop::Schema::Result::TransactionExternal",
|
||||
{ 'foreign.transaction_id' => 'self.id' },
|
||||
);
|
||||
|
||||
sub sqlt_deploy_hook {
|
||||
my ( $source_instance, $sqlt_table ) = @_;
|
||||
my $pending_field = $sqlt_table->get_field('essential');
|
||||
if ( $sqlt_table->schema->translator->producer_type =~ /SQLite$/ ) {
|
||||
$pending_field->{default_value} = 0;
|
||||
} else {
|
||||
$pending_field->{default_value} = \"false";
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
39
lib/Pear/LocalLoop/Schema/Result/TransactionCategory.pm
Normal file
39
lib/Pear/LocalLoop/Schema/Result/TransactionCategory.pm
Normal file
|
@ -0,0 +1,39 @@
|
|||
package Pear::LocalLoop::Schema::Result::TransactionCategory;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("transaction_category");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"category_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
"transaction_id" => {
|
||||
data_type => 'integer',
|
||||
is_nullable => 0,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->add_unique_constraint(["transaction_id"]);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"category",
|
||||
"Pear::LocalLoop::Schema::Result::Category",
|
||||
"category_id",
|
||||
{ cascade_delete => 0 },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"transaction",
|
||||
"Pear::LocalLoop::Schema::Result::Transaction",
|
||||
"transaction_id",
|
||||
{ cascade_delete => 0 },
|
||||
);
|
||||
|
||||
1;
|
49
lib/Pear/LocalLoop/Schema/Result/TransactionExternal.pm
Normal file
49
lib/Pear/LocalLoop/Schema/Result/TransactionExternal.pm
Normal file
|
@ -0,0 +1,49 @@
|
|||
package Pear::LocalLoop::Schema::Result::TransactionExternal;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("transactions_external");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"transaction_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"external_reference_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"external_id" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
}
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->add_unique_constraint([ qw/external_reference_id external_id/ ]);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"transaction",
|
||||
"Pear::LocalLoop::Schema::Result::Transaction",
|
||||
{ 'foreign.id' => 'self.transaction_id' },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"external_reference",
|
||||
"Pear::LocalLoop::Schema::Result::ExternalReference",
|
||||
{ 'foreign.id' => 'self.external_reference_id' },
|
||||
);
|
||||
|
||||
1;
|
81
lib/Pear/LocalLoop/Schema/Result/TransactionMeta.pm
Normal file
81
lib/Pear/LocalLoop/Schema/Result/TransactionMeta.pm
Normal file
|
@ -0,0 +1,81 @@
|
|||
package Pear::LocalLoop::Schema::Result::TransactionMeta;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table("transactions_meta");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"transaction_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"net_value" => {
|
||||
data_type => "numeric",
|
||||
size => [ 100, 0 ],
|
||||
is_nullable => 0,
|
||||
},
|
||||
"sales_tax_value" => {
|
||||
data_type => "numeric",
|
||||
size => [ 100, 0 ],
|
||||
is_nullable => 0,
|
||||
},
|
||||
"gross_value" => {
|
||||
data_type => "numeric",
|
||||
size => [ 100, 0 ],
|
||||
is_nullable => 0,
|
||||
},
|
||||
"local_service" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"regional_service" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"national_service" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"private_household_rebate" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"business_tax_and_rebate" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"stat_loc_gov" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"central_loc_gov" => {
|
||||
data_type => 'boolean',
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"transaction",
|
||||
"Pear::LocalLoop::Schema::Result::Transaction",
|
||||
{ 'foreign.id' => 'self.transaction_id' },
|
||||
);
|
||||
|
||||
1;
|
92
lib/Pear/LocalLoop/Schema/Result/TransactionRecurring.pm
Normal file
92
lib/Pear/LocalLoop/Schema/Result/TransactionRecurring.pm
Normal file
|
@ -0,0 +1,92 @@
|
|||
package Pear::LocalLoop::Schema::Result::TransactionRecurring;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->load_components(qw/
|
||||
InflateColumn::DateTime
|
||||
TimeStamp
|
||||
/);
|
||||
|
||||
__PACKAGE__->table("transaction_recurring");
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
"id" => {
|
||||
data_type => "integer",
|
||||
is_auto_increment => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"buyer_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"seller_id" => {
|
||||
data_type => "integer",
|
||||
is_foreign_key => 1,
|
||||
is_nullable => 0,
|
||||
},
|
||||
"value" => {
|
||||
data_type => "numeric",
|
||||
size => [ 100, 0 ],
|
||||
is_nullable => 0,
|
||||
},
|
||||
"start_time" => {
|
||||
data_type => "datetime",
|
||||
timezone => "UTC",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"last_updated" => {
|
||||
data_type => "datetime",
|
||||
timezone => "UTC",
|
||||
is_nullable => 1,
|
||||
datetime_undef_if_invalid => 1,
|
||||
},
|
||||
"essential" => {
|
||||
data_type => "boolean",
|
||||
default_value => \"false",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"distance" => {
|
||||
data_type => 'numeric',
|
||||
size => [15],
|
||||
is_nullable => 1,
|
||||
},
|
||||
"category_id" => {
|
||||
data_type => "integer",
|
||||
is_nullable => 1,
|
||||
is_foreign_key => 1,
|
||||
},
|
||||
"recurring_period" => {
|
||||
data_type => "varchar",
|
||||
size => 255,
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("id");
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"buyer",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "buyer_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"seller",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "seller_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"category",
|
||||
"Pear::LocalLoop::Schema::Result::Category",
|
||||
"category_id",
|
||||
{ cascade_delete => 0 },
|
||||
);
|
||||
|
||||
1;
|
|
@ -0,0 +1,27 @@
|
|||
package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionCategoryPg;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table('view_quantised_transactions');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
|
||||
__PACKAGE__->result_source_instance->view_definition( qq/
|
||||
SELECT "transactions"."value",
|
||||
"transactions"."distance",
|
||||
"transactions"."purchase_time",
|
||||
"transactions"."buyer_id",
|
||||
"transactions"."seller_id",
|
||||
"transactions"."essential",
|
||||
"transaction_category"."category_id",
|
||||
DATE_TRUNC('hour', "transactions"."purchase_time") AS "quantised_hours",
|
||||
DATE_TRUNC('day', "transactions"."purchase_time") AS "quantised_days",
|
||||
DATE_TRUNC('week', "transactions"."purchase_time") AS "quantised_weeks"
|
||||
FROM "transactions"
|
||||
LEFT JOIN "transaction_category" ON "transactions"."id" = "transaction_category"."transaction_id"
|
||||
/);
|
||||
|
||||
1;
|
|
@ -0,0 +1,27 @@
|
|||
package Pear::LocalLoop::Schema::Result::ViewQuantisedTransactionCategorySQLite;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
|
||||
__PACKAGE__->table('view_quantised_transactions');
|
||||
__PACKAGE__->result_source_instance->is_virtual(1);
|
||||
|
||||
__PACKAGE__->result_source_instance->view_definition( qq/
|
||||
SELECT "transactions"."value",
|
||||
"transactions"."distance",
|
||||
"transactions"."purchase_time",
|
||||
"transactions"."buyer_id",
|
||||
"transactions"."seller_id",
|
||||
"transactions"."essential",
|
||||
"transaction_category"."category_id",
|
||||
DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"transactions"."purchase_time")) AS "quantised_hours",
|
||||
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time")) AS "quantised_days",
|
||||
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"transactions"."purchase_time", 'weekday 0','-6 days')) AS "quantised_weeks"
|
||||
FROM "transactions"
|
||||
LEFT JOIN "transaction_category" ON "transactions"."id" = "transaction_category"."transaction_id"
|
||||
/);
|
||||
|
||||
1;
|
|
@ -13,9 +13,27 @@ __PACKAGE__->result_source_instance->view_definition( qq/
|
|||
SELECT "value",
|
||||
"distance",
|
||||
"purchase_time",
|
||||
"buyer_id",
|
||||
"seller_id",
|
||||
DATE_TRUNC('hour', "purchase_time") AS "quantised_hours",
|
||||
DATE_TRUNC('day', "purchase_time") AS "quantised_days"
|
||||
DATE_TRUNC('day', "purchase_time") AS "quantised_days",
|
||||
DATE_TRUNC('week', "purchase_time") AS "quantised_weeks",
|
||||
DATE_TRUNC('month', "purchase_time") AS "quantised_months"
|
||||
FROM "transactions"
|
||||
/);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"buyer",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "buyer_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"seller",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "seller_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
||||
|
|
|
@ -13,9 +13,28 @@ __PACKAGE__->result_source_instance->view_definition( qq/
|
|||
SELECT "value",
|
||||
"distance",
|
||||
"purchase_time",
|
||||
"buyer_id",
|
||||
"seller_id",
|
||||
"sector",
|
||||
DATETIME(STRFTIME('%Y-%m-%d %H:00:00',"purchase_time")) AS "quantised_hours",
|
||||
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time")) AS "quantised_days"
|
||||
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time")) AS "quantised_days",
|
||||
DATETIME(STRFTIME('%Y-%m-%d 00:00:00',"purchase_time",'weekday 0','-6 days')) AS "quantised_weeks",
|
||||
DATETIME(STRFTIME('%Y-%m-00 00:00:00',"purchase_time")) AS "quantised_months"
|
||||
FROM "transactions"
|
||||
/);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"buyer",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "buyer_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
__PACKAGE__->belongs_to(
|
||||
"seller",
|
||||
"Pear::LocalLoop::Schema::Result::Entity",
|
||||
{ id => "seller_id" },
|
||||
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
|
||||
);
|
||||
|
||||
1;
|
||||
|
|
36
lib/Pear/LocalLoop/Schema/ResultSet/Category.pm
Normal file
36
lib/Pear/LocalLoop/Schema/ResultSet/Category.pm
Normal file
|
@ -0,0 +1,36 @@
|
|||
package Pear::LocalLoop::Schema::ResultSet::Category;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::ResultSet';
|
||||
|
||||
sub as_hash {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my %category_list = (
|
||||
(
|
||||
map {
|
||||
$_->id => $_->name,
|
||||
} $self->all
|
||||
),
|
||||
0 => 'Uncategorised',
|
||||
);
|
||||
return \%category_list;
|
||||
}
|
||||
|
||||
sub as_hash_name_icon {
|
||||
my ( $self ) = @_;
|
||||
|
||||
my %category_list = (
|
||||
(
|
||||
map {
|
||||
$_->name => $_->line_icon,
|
||||
} $self->all
|
||||
),
|
||||
0 => 'Uncategorised',
|
||||
);
|
||||
return \%category_list;
|
||||
}
|
||||
|
||||
1;
|
|
@ -8,34 +8,54 @@ use base 'DBIx::Class::ResultSet';
|
|||
sub get_values {
|
||||
my $self = shift;
|
||||
my $id = shift;
|
||||
my $include_ignored = shift;
|
||||
my $include_imported = shift;
|
||||
|
||||
return $self->find($id)->search_related(
|
||||
'values',
|
||||
undef,
|
||||
{
|
||||
( $include_ignored ? () : ( ignore_value => 0 ) ),
|
||||
( $include_imported ? () : ( transaction_id => undef ) ),
|
||||
},
|
||||
{
|
||||
order_by => { '-asc' => 'id' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
sub get_users {
|
||||
sub _unordered_get_values {
|
||||
my $self = shift;
|
||||
my $id = shift;
|
||||
my $include_ignored = shift;
|
||||
my $include_imported = shift;
|
||||
|
||||
return $self->get_values($id)->search({},
|
||||
return $self->find($id)->search_related(
|
||||
'values',
|
||||
{
|
||||
( $include_ignored ? () : ( ignore_value => 0 ) ),
|
||||
( $include_imported ? () : ( transaction_id => undef ) ),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
sub get_users {
|
||||
my $self = shift;
|
||||
|
||||
return $self->_unordered_get_values(@_)->search({},
|
||||
{
|
||||
group_by => 'user_name',
|
||||
columns => [ qw/ user_name / ],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
sub get_orgs {
|
||||
my $self = shift;
|
||||
my $id = shift;
|
||||
|
||||
return $self->get_values($id)->search({},
|
||||
return $self->_unordered_get_values(@_)->search({},
|
||||
{
|
||||
group_by => 'org_name',
|
||||
columns => [ qw/ org_name / ],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -44,13 +64,23 @@ sub get_lookups {
|
|||
my $self = shift;
|
||||
my $id = shift;
|
||||
|
||||
return $self->find($id)->search_related(
|
||||
my $lookup_rs = $self->find($id)->search_related(
|
||||
'lookups',
|
||||
undef,
|
||||
{
|
||||
order_by => { '-asc' => 'id' },
|
||||
prefetch => { entity => [ qw/ organisation customer / ] },
|
||||
order_by => { '-asc' => 'me.id' },
|
||||
},
|
||||
);
|
||||
my $lookup_map = {
|
||||
map {
|
||||
$_->name => {
|
||||
entity_id => $_->entity->id,
|
||||
name => $_->entity->name,
|
||||
},
|
||||
} $lookup_rs->all
|
||||
};
|
||||
return $lookup_map;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -114,6 +114,8 @@ sub dump_error {
|
|||
my $self = shift;
|
||||
if ( my $error = $self->tx->res->dom->at('pre[id="error"]') ) {
|
||||
diag $error->text;
|
||||
} elsif ( my $route_error = $self->tx->res->dom->at('div[id="routes"] > p') ) {
|
||||
diag $route_error->content;
|
||||
} else {
|
||||
diag $self->tx->res->to_string;
|
||||
}
|
||||
|
|
|
@ -3,4 +3,7 @@
|
|||
user => undef,
|
||||
pass => undef,
|
||||
key => "a",
|
||||
};
|
||||
minion => {
|
||||
SQLite => 'sqlite:minion.db',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,4 +4,7 @@
|
|||
user => undef,
|
||||
pass => undef,
|
||||
key => "a",
|
||||
minion => {
|
||||
SQLite => 'sqlite:minion.db',
|
||||
},
|
||||
};
|
||||
|
|
10
script/cron_daily
Normal file
10
script/cron_daily
Normal file
|
@ -0,0 +1,10 @@
|
|||
#! /bin/bash
|
||||
|
||||
# Scripts to run daily.
|
||||
# This will be run sometime between 2 & 3AM every morning.
|
||||
# If order matters, make sure they are in the right place.
|
||||
|
||||
eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
|
||||
|
||||
MOJO_MODE=production ./script/pear-local_loop recur_transactions --force
|
||||
MOJO_MODE=production ./script/pear-local_loop recalc_leaderboards
|
|
@ -5,7 +5,10 @@ use warnings;
|
|||
|
||||
use FindBin qw/ $Bin /;
|
||||
use lib "$Bin/../lib";
|
||||
use lib "$Bin/..";
|
||||
use Devel::Dwarn;
|
||||
|
||||
Dwarn $Bin;
|
||||
use Pear::LocalLoop::Schema::Script::DeploymentHandler;
|
||||
|
||||
Pear::LocalLoop::Schema::Script::DeploymentHandler->new_with_actions(schema_class => 'Pear::LocalLoop::Schema');
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#! /bin/bash
|
||||
|
||||
eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
|
||||
|
||||
MOJO_MODE=production ./script/pear-local_loop recalc_leaderboards
|
|
@ -206,7 +206,7 @@ CREATE TABLE "import_values" (
|
|||
"purchase_date" timestamp NOT NULL,
|
||||
"purchase_value" character varying(255) NOT NULL,
|
||||
"org_name" character varying(255) NOT NULL,
|
||||
"transaction_id" character varying,
|
||||
"transaction_id" integer,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "import_values_idx_set_id" on "import_values" ("set_id");
|
||||
|
|
|
@ -206,7 +206,7 @@ CREATE TABLE "import_values" (
|
|||
"purchase_date" timestamp NOT NULL,
|
||||
"purchase_value" character varying(255) NOT NULL,
|
||||
"org_name" character varying(255) NOT NULL,
|
||||
"transaction_id" character varying,
|
||||
"transaction_id" integer,
|
||||
"ignore_value" boolean DEFAULT false NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
|
|
@ -220,7 +220,7 @@ CREATE TABLE "import_values" (
|
|||
"purchase_date" timestamp NOT NULL,
|
||||
"purchase_value" character varying(255) NOT NULL,
|
||||
"org_name" character varying(255) NOT NULL,
|
||||
"transaction_id" character varying,
|
||||
"transaction_id" integer,
|
||||
"ignore_value" boolean DEFAULT false NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
|
18
share/ddl/PostgreSQL/deploy/17/001-auto-__VERSION.sql
Normal file
18
share/ddl/PostgreSQL/deploy/17/001-auto-__VERSION.sql
Normal file
|
@ -0,0 +1,18 @@
|
|||
--
|
||||
-- Created by SQL::Translator::Producer::PostgreSQL
|
||||
-- Created on Thu Nov 23 14:08:17 2017
|
||||
--
|
||||
;
|
||||
--
|
||||
-- Table: dbix_class_deploymenthandler_versions
|
||||
--
|
||||
CREATE TABLE "dbix_class_deploymenthandler_versions" (
|
||||
"id" serial NOT NULL,
|
||||
"version" character varying(50) NOT NULL,
|
||||
"ddl" text,
|
||||
"upgrade_sql" text,
|
||||
PRIMARY KEY ("id"),
|
||||
CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version")
|
||||
);
|
||||
|
||||
;
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue