Merge remote-tracking branch 'origin/feat_receipt_upload'

This commit is contained in:
Lunaresk 2023-07-16 23:53:30 +02:00
parent d311b6f8cf
commit 5e4a59e15f
109 changed files with 700 additions and 431 deletions

View File

@ -1,23 +1,26 @@
FROM python@sha256:075fe10ae13ea0f081540bead850eeb7b6c71d07ed4766d75f8529abd0101c44 FROM python@sha256:21c9f0b22213295a13bd678c5b45aa587ff6cb01cd99b6cf0e6928f4c777006b
# python:3.10.10-slim-bullseye # python:3.11.4-slim-bullseye (arm/v7)
RUN useradd costhive RUN useradd costhive
WORKDIR /home/costhive WORKDIR /home/costhive
RUN apt update && apt -y upgrade RUN apt update && apt -y upgrade; \
RUN apt install -y libpq-dev gcc g++ swig make apt install -y libpq-dev gcc g++ swig make; \
rm -rf /var/lib/apt/lists
COPY boot.sh requirements.txt ./ COPY boot.sh requirements.txt ./
RUN python -m venv venv RUN python -m venv venv; \
RUN venv/bin/pip install -r requirements.txt venv/bin/pip install --upgrade pip; \
RUN venv/bin/pip install gunicorn venv/bin/pip install wheel; \
RUN chmod +x boot.sh venv/bin/pip install gunicorn; \
venv/bin/pip install -r requirements.txt
COPY backend backend COPY backend backend
ENV FLASK_APP run.py ENV FLASK_APP run.py
RUN chown -R costhive:costhive ./ RUN chmod +x boot.sh; \
chown -R costhive:costhive ./
USER costhive USER costhive

12
Pipfile
View File

@ -1,12 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask-cors = "*"
[dev-packages]
[requires]
python_version = "3.10"

141
Pipfile.lock generated
View File

@ -1,141 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "2f46680952b8ccfaf093f8ef1315053afb3d2e15fc244d50f313d0961890dfc3"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "platform_system == 'Windows'",
"version": "==0.4.6"
},
"flask": {
"hashes": [
"sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d",
"sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d"
],
"markers": "python_version >= '3.7'",
"version": "==2.2.3"
},
"flask-cors": {
"hashes": [
"sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438",
"sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"
],
"index": "pypi",
"version": "==3.0.10"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"markupsafe": {
"hashes": [
"sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
"sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
"sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
"sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
"sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
"sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
"sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
"sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
"sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
"sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
"sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
"sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
"sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
"sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
"sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
"sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
"sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
"sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
"sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
"sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
"sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
"sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
"sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
"sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
"sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
"sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
"sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
"sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
"sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
"sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
"sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
"sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
"sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
"sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
"sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
"sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
"sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
"sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
"sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
"sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
"sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
"sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
"sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
"sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
"sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
"sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
"sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
"sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
"sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
"sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"werkzeug": {
"hashes": [
"sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe",
"sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"
],
"markers": "python_version >= '3.7'",
"version": "==2.2.3"
}
},
"develop": {}
}

View File

@ -7,22 +7,23 @@ name = "pypi"
email-validator = "*" email-validator = "*"
flask = "*" flask = "*"
flask-bootstrap = "*" flask-bootstrap = "*"
flask-cors = "*"
flask-login = "*" flask-login = "*"
flask-mail = "*" flask-mail = "*"
flask-marshmallow = "*"
flask-migrate = "*" flask-migrate = "*"
flask-sqlalchemy = "*" flask-sqlalchemy = "*"
flask-wtf = "*" flask-wtf = "*"
marshmallow-sqlalchemy = "*"
psycopg2-binary = "*" psycopg2-binary = "*"
pyjwt = "*" pyjwt = "*"
python-dotenv = "*" python-dotenv = "*"
pymupdf = "*" pymupdf = "*"
sqlalchemy-utils = "*"
flask-marshmallow = "*"
marshmallow-sqlalchemy = "*"
requests = "*" requests = "*"
sqlalchemy-utils = "*"
[dev-packages] [dev-packages]
[requires] [requires]
python_version = "3.10" python_version = "3.11"
python_full_version = "3.10.10" python_full_version = "3.11.4"

312
backend/Pipfile.lock generated
View File

@ -1,12 +1,12 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "d2a442d0df6bf1581b3b231990af6de00853e9a6c1381b756a8056ac9f2bead3" "sha256": "6aa4fc89c41e2a25283179a13d468214fdebcbd824a74a7f2f79482b920eed78"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
"python_full_version": "3.10.10", "python_full_version": "3.11.4",
"python_version": "3.10" "python_version": "3.11"
}, },
"sources": [ "sources": [
{ {
@ -25,6 +25,14 @@
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==1.11.1" "version": "==1.11.1"
}, },
"anyio": {
"hashes": [
"sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780",
"sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"
],
"markers": "python_version >= '3.7'",
"version": "==3.7.1"
},
"blinker": { "blinker": {
"hashes": [ "hashes": [
"sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213", "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213",
@ -43,92 +51,92 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96",
"sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c",
"sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710",
"sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706",
"sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020",
"sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252",
"sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
"sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329",
"sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a",
"sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f",
"sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6",
"sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4",
"sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a",
"sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46",
"sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2",
"sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23",
"sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace",
"sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd",
"sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982",
"sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10",
"sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2",
"sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea",
"sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09",
"sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5",
"sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149",
"sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489",
"sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9",
"sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80",
"sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592",
"sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3",
"sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6",
"sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed",
"sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c",
"sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200",
"sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a",
"sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e",
"sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d",
"sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6",
"sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623",
"sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669",
"sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3",
"sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa",
"sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9",
"sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2",
"sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f",
"sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1",
"sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4",
"sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a",
"sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8",
"sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3",
"sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029",
"sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f",
"sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959",
"sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22",
"sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7",
"sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952",
"sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346",
"sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e",
"sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d",
"sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299",
"sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd",
"sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a",
"sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3",
"sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037",
"sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94",
"sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c",
"sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858",
"sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a",
"sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449",
"sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c",
"sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918",
"sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1",
"sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c",
"sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac",
"sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.7.0'",
"version": "==3.1.0" "version": "==3.2.0"
}, },
"click": { "click": {
"hashes": [ "hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==8.1.3" "version": "==8.1.5"
}, },
"colorama": { "colorama": {
"hashes": [ "hashes": [
@ -140,11 +148,11 @@
}, },
"dnspython": { "dnspython": {
"hashes": [ "hashes": [
"sha256:224e32b03eb46be70e12ef6d64e0be123a64e621ab4c0822ff6d450d52a540b9", "sha256:46b4052a55b56beea3a3bdd7b30295c292bd6827dd442348bc116f2d35b17f0a",
"sha256:89141536394f909066cabd112e3e1a37e4e654db00a25308b0f130bc3152eb46" "sha256:758e691dbb454d5ccf4e1b154a19e52847f79e21a42fef17b969144af29a4e6c"
], ],
"markers": "python_version >= '3.7' and python_version < '4.0'", "markers": "python_version >= '3.8' and python_version < '4.0'",
"version": "==2.3.0" "version": "==2.4.0"
}, },
"dominate": { "dominate": {
"hashes": [ "hashes": [
@ -162,6 +170,14 @@
"index": "pypi", "index": "pypi",
"version": "==2.0.0.post2" "version": "==2.0.0.post2"
}, },
"exceptiongroup": {
"hashes": [
"sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5",
"sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"
],
"markers": "python_version < '3.11'",
"version": "==1.1.2"
},
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0", "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
@ -177,6 +193,14 @@
"index": "pypi", "index": "pypi",
"version": "==3.3.7.1" "version": "==3.3.7.1"
}, },
"flask-cors": {
"hashes": [
"sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783",
"sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"
],
"index": "pypi",
"version": "==4.0.0"
},
"flask-login": { "flask-login": {
"hashes": [ "hashes": [
"sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f", "sha256:1ef79843f5eddd0f143c2cd994c1b05ac83c0401dc6234c143495af9a939613f",
@ -290,6 +314,22 @@
"markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
"version": "==2.0.2" "version": "==2.0.2"
}, },
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"httpcore": {
"hashes": [
"sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888",
"sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"
],
"markers": "python_version >= '3.8'",
"version": "==0.17.3"
},
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
@ -530,52 +570,60 @@
"index": "pypi", "index": "pypi",
"version": "==2.31.0" "version": "==2.31.0"
}, },
"sqlalchemy": { "sniffio": {
"hashes": [ "hashes": [
"sha256:04383f1e3452f6739084184e427e9d5cb4e68ddc765d52157bf5ef30d5eca14f", "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:125f9f7e62ddf8b590c069729080ffe18b68a20d9882eb0947f72e06274601d7", "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
"sha256:1822620c89779b85f7c23d535c8e04b79c517739ae07aaed48c81e591ed5498e",
"sha256:21583808d37f126a647652c90332ac1d3a102edf3c94bcc3319edcc0ea2300cc",
"sha256:218fb20c01e95004f50a3062bf4c447dcb360cab8274232f31947e254f118298",
"sha256:2269b1f9b8be47e52b70936069a25a3771eff53367aa5cc59bb94f28a6412e13",
"sha256:234678ed6576531b8e4be255b980f20368bf07241a2e67b84e6b0fe679edb9c4",
"sha256:28da17059ecde53e2d10ba813d38db942b9f6344360b2958b25872d5cb729d35",
"sha256:2c6ff5767d954f6091113fedcaaf49cdec2197ae4c5301fe83d5ae4393c82f33",
"sha256:36a87e26fe8fa8c466fae461a8fcb780d0a1cbf8206900759fc6fe874475a3ce",
"sha256:394ac3adf3676fad76d4b8fcecddf747627f17f0738dc94bac15f303d05b03d4",
"sha256:40a3dc52b2b16f08b5c16b9ee7646329e4b3411e9280e5e8d57b19eaa51cbef4",
"sha256:48111d56afea5699bab72c38ec95561796b81befff9e13d1dd5ce251ab25f51d",
"sha256:48b40dc2895841ea89d89df9eb3ac69e2950a659db20a369acf4259f68e6dc1f",
"sha256:513411d73503a6fc5804f01fae3b3d44f267c1b3a06cfeac02e9286a7330e857",
"sha256:51736cfb607cf4e8fafb693906f9bc4e5ee55be0b096d44bd7f20cd8489b8571",
"sha256:5f40e3a7d0a464f1c8593f2991e5520b2f5b26da24e88000bbd4423f86103d4f",
"sha256:6150560fcffc6aee5ec9a97419ac768c7a9f56baf7a7eb59cb4b1b6a4d463ad9",
"sha256:724355973297bbe547f3eb98b46ade65a67a3d5a6303f17ab59a2dc6fb938943",
"sha256:74ddcafb6488f382854a7da851c404c394be3729bb3d91b02ad86c5458140eff",
"sha256:7830e01b02d440c27f2a5be68296e74ccb55e6a5b5962ffafd360b98930b2e5e",
"sha256:7f31d4e7ca1dd8ca5a27fd5eaa0f9e2732fe769ff7dd35bf7bba179597e4df07",
"sha256:8741d3d401383e54b2aada37cbd10f55c5d444b360eae3a82f74a2be568a7710",
"sha256:910d45bf3673f0e4ef13858674bd23cfdafdc8368b45b948bf511797dbbb401d",
"sha256:aa995b21f853864996e4056d9fde479bcecf8b7bff4beb3555eebbbba815f35d",
"sha256:af7e2ba75bf84b64adb331918188dda634689a2abb151bc1a583e488363fd2f8",
"sha256:b0eaf82cc844f6b46defe15ad243ea00d1e39ed3859df61130c263dc7204da6e",
"sha256:b114a16bc03dfe20b625062e456affd7b9938286e05a3f904a025b9aacc29dd4",
"sha256:b47be4c6281a86670ea5cfbbbe6c3a65366a8742f5bc8b986f790533c60b5ddb",
"sha256:ba03518e64d86f000dc24ab3d3a1aa876bcbaa8aa15662ac2df5e81537fa3394",
"sha256:cc9c2630c423ac4973492821b2969f5fe99d9736f3025da670095668fbfcd4d5",
"sha256:cf07ff9920cb3ca9d73525dfd4f36ddf9e1a83734ea8b4f724edfd9a2c6e82d9",
"sha256:cf175d26f6787cce30fe6c04303ca0aeeb0ad40eeb22e3391f24b32ec432a1e1",
"sha256:d0aeb3afaa19f187a70fa592fbe3c20a056b57662691fd3abf60f016aa5c1848",
"sha256:e186e9e95fb5d993b075c33fe4f38a22105f7ce11cecb5c17b5618181e356702",
"sha256:e2d5c3596254cf1a96474b98e7ce20041c74c008b0f101c1cb4f8261cb77c6d3",
"sha256:e3189432db2f5753b4fde1aa90a61c69976f4e7e31d1cf4611bfe3514ed07478",
"sha256:e3a6b2788f193756076061626679c5c5a6d600ddf8324f986bc72004c3e9d92e",
"sha256:ead58cae2a089eee1b0569060999cb5f2b2462109498a0937cc230a7556945a1",
"sha256:f2f389f77c68dc22cb51f026619291c4a38aeb4b7ecb5f998fd145b2d81ca513",
"sha256:f593170fc09c5abb1205a738290b39532f7380094dc151805009a07ae0e85330"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.0.17" "version": "==1.3.0"
},
"sqlalchemy": {
"hashes": [
"sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c",
"sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5",
"sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1",
"sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a",
"sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9",
"sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15",
"sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6",
"sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972",
"sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad",
"sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531",
"sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148",
"sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7",
"sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828",
"sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de",
"sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae",
"sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0",
"sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b",
"sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d",
"sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106",
"sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f",
"sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8",
"sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c",
"sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0",
"sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365",
"sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e",
"sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2",
"sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3",
"sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c",
"sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7",
"sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e",
"sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145",
"sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1",
"sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84",
"sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b",
"sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2",
"sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369",
"sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12",
"sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d",
"sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2",
"sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb",
"sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.19"
}, },
"sqlalchemy-utils": { "sqlalchemy-utils": {
"hashes": [ "hashes": [
@ -587,11 +635,11 @@
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771", "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
"sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82" "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==4.7.0" "version": "==4.7.1"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [

View File

@ -18,4 +18,4 @@ class Config(object):
MAIL_USERNAME = os.environ.get('MAIL_USERNAME') MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
ADMINS = ['postmaster@wpgcommunity.net'] ADMINS = ['postmaster@wpgcommunity.net']
POSTS_PER_PAGE = 10 POSTS_PER_PAGE = 15

View File

@ -0,0 +1,64 @@
"""receipt-id reorganized
Revision ID: f6f97ed9c053
Revises: 4fec22c35530
Create Date: 2023-07-02 14:16:06.260479
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f6f97ed9c053'
down_revision = '4fec22c35530'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.add_column(sa.Column('accepted', sa.Boolean(), nullable=False))
batch_op.drop_constraint("item_receipt_receipt_fkey")
batch_op.alter_column('receipt',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False)
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.add_column(sa.Column('bonid', sa.Numeric(precision=24, scale=0), nullable=True))
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.execute("UPDATE receipt SET bonid=id;")
batch_op.alter_column('id',
existing_type=sa.NUMERIC(precision=24, scale=0),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True)
batch_op.create_unique_constraint(None, ['bonid'])
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.create_foreign_key(None, 'receipt', ['receipt'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('receipt_item', schema=None) as batch_op:
batch_op.alter_column('receipt',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False)
batch_op.drop_column('accepted')
with op.batch_alter_table('receipt', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='unique')
batch_op.alter_column('id',
existing_type=sa.BigInteger(),
type_=sa.NUMERIC(precision=24, scale=0),
existing_nullable=False,
autoincrement=True)
batch_op.drop_column('bonid')
# ### end Alembic commands ###

View File

@ -2,7 +2,7 @@ from src import db
class Brand(db.Model): class Brand(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False) name = db.Column(db.String(32), nullable=False)
Item = db.relationship("Item", backref='Brand', lazy='dynamic') Item = db.relationship("Item", backref='Brand', lazy='dynamic')

View File

@ -1,9 +1,9 @@
from src import db from src import db
from src.models.item_category import item_category from .item_category import item_category
class Category(db.Model): class Category(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), nullable=False, unique=True) name = db.Column(db.String(32), nullable=False, unique=True)
Item = db.relationship("Item", secondary=item_category, Item = db.relationship("Item", secondary=item_category,

View File

@ -2,7 +2,7 @@ from src import db
class Establishment(db.Model): class Establishment(db.Model):
id = db.Column(db.BigInteger, primary_key=True) id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String(64), nullable=False) name = db.Column(db.String(64), nullable=False)
owner = db.Column(db.ForeignKey('user.id'), nullable=False) owner = db.Column(db.ForeignKey('user.id'), nullable=False)

View File

@ -1,5 +1,5 @@
from src import db, ma from src import db, ma
from src.models.item_category import item_category from .item_category import item_category
class Item(db.Model): class Item(db.Model):

View File

@ -2,9 +2,9 @@ from src import db
class Receipt(db.Model): class Receipt(db.Model):
id = db.Column(db.Numeric(precision=24, scale=0), id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
primary_key=True, autoincrement=False)
date = db.Column(db.Date, nullable=False) date = db.Column(db.Date, nullable=False)
bonid = db.Column(db.Numeric(precision=24, scale=0), unique=True)
from_user = db.Column(db.ForeignKey("login_token.token"), from_user = db.Column(db.ForeignKey("login_token.token"),
server_onupdate=db.FetchedValue()) server_onupdate=db.FetchedValue())
registered = db.Column(db.Boolean, nullable=False, registered = db.Column(db.Boolean, nullable=False,

View File

@ -1,6 +1,6 @@
from src import ma from src import ma
from src.models import AmountChange from ..amount_change import AmountChange
from src.models.schemas import ItemSchema from .item import ItemSchema
class AmountChangeSchema(ma.SQLAlchemySchema): class AmountChangeSchema(ma.SQLAlchemySchema):

View File

@ -1,6 +1,7 @@
from src import ma from src import ma
from src.models import Bought from ..bought import Bought
from src.models.schemas import LoginTokenSchema, ItemSchema from .login_token import LoginTokenSchema
from .item import ItemSchema
class BoughtSchema(ma.SQLAlchemySchema): class BoughtSchema(ma.SQLAlchemySchema):

View File

@ -1,5 +1,5 @@
from src import ma from src import ma
from src.models import Brand from ..brand import Brand
class BrandSchema(ma.SQLAlchemySchema): class BrandSchema(ma.SQLAlchemySchema):

View File

@ -1,6 +1,6 @@
# from marshmallow import Schema, fields # from marshmallow import Schema, fields
from src import ma from src import ma
from src.models import Category from ..category import Category
class CategorySchema(ma.SQLAlchemySchema): class CategorySchema(ma.SQLAlchemySchema):

View File

@ -1,6 +1,6 @@
from src import ma from src import ma
from src.models import Establishment from ..establishment import Establishment
from src.models.schemas import UserSchema from .user import UserSchema
class EstablishmentSchema(ma.SQLAlchemySchema): class EstablishmentSchema(ma.SQLAlchemySchema):

View File

@ -1,6 +1,7 @@
from src import ma from src import ma
from src.models import EstablishmentCandidate from ..establishment_candidate import EstablishmentCandidate
from src.models.schemas import EstablishmentSchema, UserSchema from .establishment import EstablishmentSchema
from .user import UserSchema
class EstablishmentCandidateSchema(ma.SQLAlchemySchema): class EstablishmentCandidateSchema(ma.SQLAlchemySchema):

View File

@ -0,0 +1,18 @@
from src import ma
from ..item import Item
from .brand import BrandSchema
class ItemSchema(ma.SQLAlchemySchema):
class Meta:
model = Item
include_fk = True
id = ma.auto_field()
name = ma.auto_field()
description = ma.auto_field()
Brand = ma.Nested(BrandSchema)
PriceChange = ma.List(ma.Nested(lambda: PriceChangeSchema(exclude=("Item",))))
AmountChange = ma.List(ma.Nested(lambda: AmountChangeSchema(exclude=("Item",))))
from .price_change import PriceChangeSchema
from .amount_change import AmountChangeSchema

View File

@ -1,6 +1,7 @@
from src import ma from src import ma
from src.models import LoginToken from ..login_token import LoginToken
from src.models.schemas import EstablishmentSchema, UserSchema from .establishment import EstablishmentSchema
from .user import UserSchema
class LoginTokenSchema(ma.SQLAlchemySchema): class LoginTokenSchema(ma.SQLAlchemySchema):

View File

@ -1,6 +1,6 @@
from src import ma from src import ma
from src.models import Payment from ..payment import Payment
from src.models.schemas import LoginTokenSchema from .login_token import LoginTokenSchema
class PaymentSchema(ma.SQLAlchemySchema): class PaymentSchema(ma.SQLAlchemySchema):

View File

@ -1,7 +1,6 @@
from src import ma from src import ma
from src.models import PriceChange from .item import ItemSchema
from src.models.schemas import ItemSchema from ..price_change import PriceChange
class PriceChangeSchema(ma.SQLAlchemySchema): class PriceChangeSchema(ma.SQLAlchemySchema):
class Meta: class Meta:

View File

@ -1,6 +1,6 @@
from src import ma from src import ma
from src.models import Receipt from ..receipt import Receipt
from src.models.schemas import LoginTokenSchema from .login_token import LoginTokenSchema
class ReceiptSchema(ma.SQLAlchemySchema): class ReceiptSchema(ma.SQLAlchemySchema):
@ -10,5 +10,6 @@ class ReceiptSchema(ma.SQLAlchemySchema):
id = ma.auto_field() id = ma.auto_field()
date = ma.auto_field() date = ma.auto_field()
LoginToken = ma.Nested(LoginTokenSchema) bonid = ma.auto_field()
registered = ma.auto_field() registered = ma.auto_field()
LoginToken = ma.Nested(LoginTokenSchema)

View File

@ -1,6 +1,7 @@
from src import ma from src import ma
from src.models import ReceiptItem from ..receipt_item import ReceiptItem
from src.models.schemas import ItemSchema, ReceiptSchema from .item import ItemSchema
from .receipt import ReceiptSchema
class ReceiptItemSchema(ma.SQLAlchemySchema): class ReceiptItemSchema(ma.SQLAlchemySchema):

View File

@ -1,5 +1,5 @@
from src import ma from src import ma
from src.models import User from ..user import User
class UserSchema(ma.SQLAlchemySchema): class UserSchema(ma.SQLAlchemySchema):

View File

@ -7,7 +7,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
id = db.Column(db.BigInteger, primary_key=True) id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
email = db.Column(db.String(255), nullable=False, unique=True) email = db.Column(db.String(255), nullable=False, unique=True)
password_hash = db.Column(db.String(128), nullable=False) password_hash = db.Column(db.String(128), nullable=False)

BIN
backend/requirements.txt Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
from src import create_app, db from src import create_app, db
from src.models import * from models import *
app = create_app() app = create_app()

View File

@ -38,7 +38,7 @@ migrate = Migrate()
def create_app(config_class=Config): def create_app(config_class=Config):
app = Flask(__name__) app = Flask(__name__, template_folder="../web/templates", static_folder="../web/static")
app.config.from_object(config_class) app.config.from_object(config_class)
bootstrap.init_app(app) bootstrap.init_app(app)
cors.init_app(app) cors.init_app(app)
@ -65,4 +65,4 @@ def create_app(config_class=Config):
return app return app
from src.models import * from models import *

View File

@ -1,6 +1,6 @@
from src import LOGGER from src import LOGGER
from src.api.v1 import bp from src.api.v1 import bp
from src.models.login_token import LoginToken from models.login_token import LoginToken
from src.utils import database_utils from src.utils import database_utils
from flask import abort, request from flask import abort, request
from flask.json import jsonify from flask.json import jsonify

View File

@ -1,4 +1,4 @@
from src.models.user import User from models.user import User
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import BooleanField, PasswordField, StringField, SubmitField from wtforms import BooleanField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError from wtforms.validators import DataRequired, Email, EqualTo, ValidationError

View File

@ -2,7 +2,7 @@ from src import db
from . import bp from . import bp
from .email import send_password_reset_email from .email import send_password_reset_email
from .forms import LoginForm, RegistrationForm, ResetPasswordForm, ResetPasswordRequestForm from .forms import LoginForm, RegistrationForm, ResetPasswordForm, ResetPasswordRequestForm
from src.models.user import User from models.user import User
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
from flask import flash, redirect, request, url_for from flask import flash, redirect, request, url_for
from flask_login import current_user, login_user, logout_user from flask_login import current_user, login_user, logout_user

View File

@ -3,7 +3,7 @@ from flask_login import current_user, login_required
from . import bp from . import bp
from .forms import EvaluateCandidateForm from .forms import EvaluateCandidateForm
from src import db, LOGGER from src import db, LOGGER
from src.models import Establishment, EstablishmentCandidate, LoginToken, User from models import Establishment, EstablishmentCandidate, LoginToken, User
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
from src.utils.database_utils import generate_token from src.utils.database_utils import generate_token

View File

@ -5,8 +5,8 @@ from . import bp
from .forms import JoinEstablishmentForm from .forms import JoinEstablishmentForm
from .utils import backend_validation from .utils import backend_validation
from src import db from src import db
from src.models import Establishment from models import Establishment
from src.models import EstablishmentCandidate from models import EstablishmentCandidate
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template

View File

@ -1,5 +1,5 @@
from flask_login import current_user from flask_login import current_user
from src.models import Establishment from models import Establishment
def backend_validation(join_establishment_form): def backend_validation(join_establishment_form):

View File

@ -1,7 +1,7 @@
from . import bp from . import bp
from .forms import NewEstablishmentForm from .forms import NewEstablishmentForm
from src import db, LOGGER from src import db, LOGGER
from src.models import Establishment, LoginToken from models import Establishment, LoginToken
from src.utils import database_utils from src.utils import database_utils
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
from flask import redirect, url_for from flask import redirect, url_for

View File

@ -3,7 +3,7 @@ from flask.json import jsonify
from flask_login import current_user, login_required from flask_login import current_user, login_required
from . import bp from . import bp
from src import LOGGER from src import LOGGER
from src.models import Establishment from models import Establishment
from src.utils import view_utils, database_utils from src.utils import view_utils, database_utils
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template

View File

@ -1,4 +1,4 @@
from src.models import LoginToken from models import LoginToken
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
from wtforms.validators import DataRequired, Optional from wtforms.validators import DataRequired, Optional

View File

@ -3,7 +3,7 @@ from flask_login import current_user, login_required
from . import bp from . import bp
from .forms import NewPaymentForm from .forms import NewPaymentForm
from src import db, LOGGER from src import db, LOGGER
from src.models import Establishment, Payment from models import Establishment, Payment
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template

View File

@ -4,4 +4,8 @@ bp = Blueprint('item', __name__, url_prefix='/item')
from .new import bp as bp_new_item from .new import bp as bp_new_item
bp.register_blueprint(bp_new_item) bp.register_blueprint(bp_new_item)
from .list import bp as bp_item_list from .list import bp as bp_item_list
bp.register_blueprint(bp_item_list) bp.register_blueprint(bp_item_list)
from .details import bp as bp_item_details
bp.register_blueprint(bp_item_details)
from .update import bp as bp_item_update
bp.register_blueprint(bp_item_update)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('details', __name__, url_prefix='/details')
from . import routes

View File

@ -0,0 +1,14 @@
# from flask_login import login_required
from . import bp
from models import Item
from models.schemas import ItemSchema
from src.utils.routes_utils import render_custom_template as render_template
@bp.route('/<item>', methods=['GET'])
def show_item(item: int):
itemobj = Item.query.get_or_404(item)
itemschema = ItemSchema().dump(itemobj)
itemschema['PriceChange'].sort(key=lambda d: d['date'], reverse=True)
itemschema['AmountChange'].sort(key=lambda d: d['date'], reverse=True)
print(itemschema)
return render_template('item/details/show_item.html', item = itemschema)

View File

@ -1,8 +1,8 @@
from flask import abort, current_app, request from flask import abort, current_app, request
from flask_login import current_user, login_required from flask_login import current_user, login_required
from . import bp from . import bp
from src.models import Item from models import Item
from src.models.schemas import ItemSchema from models.schemas import ItemSchema
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
@bp.route('/show_items', methods=['GET', 'POST']) @bp.route('/show_items', methods=['GET', 'POST'])

View File

@ -1,4 +1,4 @@
from src.models import Brand, Category from models import Brand, Category
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField from wtforms import DateField, FloatField, IntegerField, SelectField, SelectMultipleField, StringField, SubmitField
from wtforms.validators import DataRequired, Optional from wtforms.validators import DataRequired, Optional

View File

@ -4,7 +4,7 @@ from flask_login import current_user, login_required
from . import bp from . import bp
from .forms import NewItemForm from .forms import NewItemForm
from src import db, LOGGER from src import db, LOGGER
from src.models import AmountChange, Brand, Item, PriceChange from models import AmountChange, Brand, Item, PriceChange
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
@bp.route('/new_item', methods=['GET', 'POST']) @bp.route('/new_item', methods=['GET', 'POST'])

View File

@ -0,0 +1,7 @@
from flask import Blueprint
bp = Blueprint('update', __name__, url_prefix='/update')
from .price_change import bp as bp_price_change
bp.register_blueprint(bp_price_change)
from .amount_change import bp as bp_amount_change
bp.register_blueprint(bp_amount_change)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('amount_change', __name__, url_prefix='/amount_change')
from . import forms, routes

View File

@ -0,0 +1,9 @@
from flask_wtf import FlaskForm
from wtforms import DateField, HiddenField, IntegerField, SubmitField
from wtforms.validators import DataRequired
class NewAmountChangeForm(FlaskForm):
id = HiddenField("Product EAN", validators=[DataRequired()], render_kw={"class": "form-control"})
date = DateField("Insert Date", validators=[DataRequired()], render_kw={"class": "form-control"})
amount_change = IntegerField("Amount", validators=[DataRequired()], render_kw={"class": "form-control"})
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})

View File

@ -0,0 +1,24 @@
from flask import abort, redirect, url_for
from flask_login import current_user, login_required
from . import bp
from .forms import NewAmountChangeForm
from src import db, LOGGER
from models import AmountChange, Item
from src.utils.routes_utils import render_custom_template as render_template
@bp.route('/<int:item>', methods=['GET', 'POST'])
@login_required
def amount_change(item: int):
if current_user.is_anonymous:
abort(403)
db_item = Item.query.get_or_404(item)
form = NewAmountChangeForm()
form.id.data = item
if form.validate_on_submit():
db_amount_change = AmountChange(
item=form.id.data, date=form.date.data, amount=form.amount_change.data)
db.session.add(db_amount_change)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('item/update/amount_change/amount_change.html', form=form, item=db_item)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('price_change', __name__, url_prefix='/price_change')
from . import forms, routes

View File

@ -0,0 +1,9 @@
from flask_wtf import FlaskForm
from wtforms import DateField, HiddenField, FloatField, SubmitField
from wtforms.validators import DataRequired
class NewPriceChangeForm(FlaskForm):
id = HiddenField("Product EAN", validators=[DataRequired()], render_kw={"class": "form-control"})
date = DateField("Insert Date", validators=[DataRequired()], render_kw={"class": "form-control"})
price_change = FloatField("Price", validators=[DataRequired()], render_kw={"class": "form-control"})
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})

View File

@ -0,0 +1,24 @@
from flask import abort, redirect, url_for
from flask_login import current_user, login_required
from . import bp
from .forms import NewPriceChangeForm
from src import db, LOGGER
from models import Item, PriceChange
from src.utils.routes_utils import render_custom_template as render_template
@bp.route('/<int:item>', methods=['GET', 'POST'])
@login_required
def price_change(item: int):
if current_user.is_anonymous:
abort(403)
db_item = Item.query.get_or_404(item)
form = NewPriceChangeForm()
form.id.data = int(item)
if form.validate_on_submit():
db_price_change = PriceChange(
item=form.id.data, date=form.date.data, price=form.price_change.data)
db.session.add(db_price_change)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('item/update/price_change/price_change.html', form=form, item=db_item)

View File

@ -1,14 +0,0 @@
from src import ma
from src.models import Item
from src.models.schemas import BrandSchema
class ItemSchema(ma.SQLAlchemySchema):
class Meta:
model = Item
include_fk = True
id = ma.auto_field()
name = ma.auto_field()
description = ma.auto_field()
Brand = ma.Nested(BrandSchema)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('confirm', __name__, url_prefix='/confirm')
from . import forms, routes

View File

@ -0,0 +1,18 @@
from flask_wtf import FlaskForm
from wtforms import SelectMultipleField, SubmitField, widgets
class MultiCheckboxField(SelectMultipleField):
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
class CheckItemsForm(FlaskForm):
items = MultiCheckboxField("Items")
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})
#TODO create new() method which loads the form for a specific receipt
@classmethod
def new(cls, itemArray):
"""
"""
form = cls()
return form

View File

@ -0,0 +1,36 @@
from flask import abort, request, url_for
from flask_login import current_user, login_required
from . import bp
from .forms import CheckItemsForm, UploadReceiptForm
from src import db, LOGGER
from models.receipt import Receipt
from models.login_token import LoginToken
from src.utils.pdf_receipt_parser import PDFReceipt
from src.utils.routes_utils import render_custom_template as render_template
PDFDir = "./"
@bp.route('/<int:receipt_id>', methods=['GET', 'POST'])
@login_required
def confirm_receipt_items(receipt_id: int):
"""Check items from a receipt if they should be accounted for payment.
Get those items from the receipt PDF itself."""
if "receipt" in request.args:
receipt_details = Receipt.query.get(request.args['receipt'])
if current_user.is_anonymous and current_user.id == receipt_details.LoginToken.Establishment.owner:
receipt = PDFReceipt._getPDFReceiptFromFile(PDFDir + f"{receipt.date}_{receipt.id}.pdf")
form = CheckItemsForm()
# TODO: Precheck if items are already in database. If yes, check if item is present only once or multiple
# times and provide dropdown menu if necessary. If not, provide input field.
temp_choices = []
for item in receipt.items:
match item:
case {"itemname": itemname, "price": price}:
temp_choices.append((itemname.replace(" ", "_"), f"{itemname, price}"))
case {"itemname": itemname, "price": price, "amount": amount}:
temp_choices.append((itemname.replace(" ", "_"), f"{itemname}, {price} * {amount}"))
form.choices = temp_choices
if form.validate_on_submit():
pass # TODO
return render_template("receipts/confirm_items.html")
abort(403)

View File

@ -1,10 +1,11 @@
from flask import abort, request, url_for from flask import abort, request, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from werkzeug.utils import secure_filename
from . import bp from . import bp
from .forms import CheckItemsForm, UploadReceiptForm from .forms import UploadReceiptForm
from src import db, LOGGER from src import db, LOGGER
from src.models.receipt import Receipt from models.receipt import Receipt
from src.models.login_token import LoginToken from models.login_token import LoginToken
from src.utils.pdf_receipt_parser import PDFReceipt from src.utils.pdf_receipt_parser import PDFReceipt
from src.utils.routes_utils import render_custom_template as render_template from src.utils.routes_utils import render_custom_template as render_template
@ -20,43 +21,15 @@ def upload_receipt():
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first(): if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
form = UploadReceiptForm() form = UploadReceiptForm()
LOGGER.debug(form.pdfReceipt.data) LOGGER.debug(form.pdfReceipt.data)
if form.is_submitted():
LOGGER.debug("submitted")
if form.validate():
LOGGER.debug("valid")
else:
LOGGER.debug(form.errors)
if form.validate_on_submit(): if form.validate_on_submit():
receipt = PDFReceipt(form.pdfReceipt.data) receipt = PDFReceipt(form.pdfReceipt.data)
dbReceipt = Receipt(id = receipt.id, date = receipt.date, dbReceipt = Receipt(id = receipt.id, date = receipt.date,
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token) from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
form.pdfReceipt.data.save(PDFDir + f"{str(receipt.date)}_{receipt.id}.pdf") form.pdfReceipt.data.save(PDFDir + secure_filename(f"{str(receipt.date)}_{receipt.id}.pdf"))
db.session.add(dbReceipt) db.session.add(dbReceipt)
db.session.commit() db.session.commit()
return receipt.text.replace("\n", "<br>") return receipt.text.replace("\n", "<br>")
else:
LOGGER.debug(form.errors)
return render_template("receipts/upload.html", form = form) return render_template("receipts/upload.html", form = form)
abort(403)
@bp.route('/confirm_receipt', methods=['GET', 'POST'])
@login_required
def confirm_receipt_items():
"""Check items from a receipt if they should be accounted for payment."""
if "receipt" in request.args:
receipt_details = Receipt.query.get(request.args['receipt'])
if current_user.is_anonymous and current_user.id == receipt_details.LoginToken.Establishment.owner:
receipt = PDFReceipt._getPDFReceiptFromFile(PDFDir + f"{receipt.date}_{receipt.id}.pdf")
form = CheckItemsForm()
# TODO: Precheck if items are already in database. If yes, check if item is present only once or multiple
# times and provide dropdown menu if necessary. If not, provide input field.
temp_choices = []
for item in receipt.items:
match item:
case {"itemname": itemname, "price": price}:
temp_choices.append((itemname.replace(" ", "_"), f"{itemname, price}"))
case {"itemname": itemname, "price": price, "amount": amount}:
temp_choices.append((itemname.replace(" ", "_"), f"{itemname}, {price} * {amount}"))
form.choices = temp_choices
if form.validate_on_submit():
pass # TODO
return render_template("receipts/confirm_items.html")
abort(403) abort(403)

View File

@ -0,0 +1,5 @@
from flask import Blueprint
bp = Blueprint('upload', __name__, url_prefix='/upload')
from . import forms, routes

View File

@ -0,0 +1,7 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField, FileRequired
from wtforms import SubmitField
class UploadReceiptForm(FlaskForm):
pdfReceipt = FileField("PDF", validators=[FileRequired(), FileAllowed(["pdf"], "Invalid Format, must be .pdf")])
submit = SubmitField("Submit", render_kw={"class": "btn btn-primary mt-3"})

View File

@ -0,0 +1,37 @@
from flask import abort, request, url_for
from flask_login import current_user, login_required
from werkzeug.utils import secure_filename
from . import bp
from .forms import UploadReceiptForm
from src import db, LOGGER
from models.receipt import Receipt
from models.login_token import LoginToken
from src.utils.pdf_receipt_parser import PDFReceipt
from src.utils.routes_utils import render_custom_template as render_template
PDFDir = "./"
# TODO überarbeiten. PDFs müssen in der Datenbank eine eigene ID bekommen.
# Quittungen haben eine USt.-ID. Die muss als Unique Key in der Datenbank
# hinterlegt sein.
# Die laufende ID ist zum abspeichern der PDFs gedacht.
@bp.route('/<int:establishment>', methods=['GET', 'POST'])
@login_required
def upload_receipt(establishment: int):
"""Upload of a receipt."""
if current_user.is_anonymous:
abort(403)
if LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first():
form = UploadReceiptForm()
LOGGER.debug(form.pdfReceipt.data)
if form.validate_on_submit():
receipt = PDFReceipt(form.pdfReceipt.data)
dbReceipt = Receipt(id = receipt.id, date = receipt.date,
from_user = LoginToken.query.filter_by(establishment=request.args['establishment'], user=current_user.id).first().token)
form.pdfReceipt.data.save(PDFDir + secure_filename(f"{str(receipt.date)}_{receipt.id}.pdf"))
db.session.add(dbReceipt)
db.session.commit()
return receipt.text.replace("\n", "<br>")
else:
LOGGER.debug(form.errors)
return render_template("receipts/upload.html", form = form)
abort(403)

View File

@ -1,29 +0,0 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Beschreibung</th>
<th scope="col">Marke</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<th scope="row">{{ item.id }}</th>
<td>{{ item.name }}</td>
<td>{{ item.description }}</td>
<td>{{ item.Brand.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% with pagination_object=items %}
{% include "utils/general/_page_navigation.html" %}
{% endwith %}
{% endblock %}

View File

@ -8,7 +8,7 @@ from sqlalchemy.dialects.postgresql import insert
from string import ascii_letters, digits from string import ascii_letters, digits
from .view_utils import bought_with_prices as bwp from .view_utils import bought_with_prices as bwp
from src import db, LOGGER from src import db, LOGGER
from src.models import Bought, Establishment, Item, LoginToken, User from models import Bought, Establishment, Item, LoginToken, User
def insert_bought_items(token: str, dates: list[dict[str: any]]): def insert_bought_items(token: str, dates: list[dict[str: any]]):

View File

@ -1,8 +1,8 @@
from sqlalchemy_utils import create_view from sqlalchemy_utils import create_view
from src import db, LOGGER from src import db, LOGGER
from src.models.amount_change import AmountChange from models.amount_change import AmountChange
from src.models.bought import Bought from models.bought import Bought
from src.models.price_change import PriceChange from models.price_change import PriceChange
def group_results(results: tuple) -> list: def group_results(results: tuple) -> list:
result_list = [] result_list = []

View File

@ -0,0 +1,79 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<table class="table">
<tbody>
<tr>
<th scope="row">EAN</th>
<th scope="row" class="w-75">{{ item.id }}</th>
</tr>
<tr>
<td>Marke</td>
<td>{{ item.Brand.name }}</td>
</tr>
<tr>
<td>Bezeichnung auf dem Kassenzettel</td>
<td>{{ item.name }}</td>
</tr>
<tr>
<td>Beschreibung</td>
<td>{{ item.description }}</td>
</tr>
<tr>
<td>
{% if item.PriceChange %}
Momentaner Preis pro Scan</td>
<td>
{% if item.AmountChange %}
{{ ((item.PriceChange[0].price / item.AmountChange[0].amount)/100)|round(2, 'ceil') }}
{% else %}
{{ (item.PriceChange[0].price)/100 }}
{% endif %}
{% else %}
Kein Preis vorhanden.
{% endif %}
</td>
</tr>
</tbody>
</table>
<br>
<table class="table">
<thead>
<tr>
<th scope="col">Preishistorie</th>
<td class="w-75">
<a href="{{ url_for('item.update.price_change.price_change', item = item.id) }}">Einfügen</a>
</td>
</tr>
</thead>
<tbody>
{% for price in item.PriceChange %}
<tr>
<td>{{ price.date }}</td>
<td>€ {{ price.price/100 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<table class="table">
<thead>
<tr>
<th scope="col">Mengenhistorie</th>
<td class="w-75">
<a href="{{ url_for('item.update.amount_change.amount_change', item = item.id) }}">Einfügen</a>
</td>
</tr>
</thead>
<tbody>
{% for amount in item.AmountChange %}
<tr>
<td>{{ amount.date }}</td>
<td>{{ amount.amount }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<div class="table-responsive">
<table class="table table-hover table-sm">
<thead>
<tr>
<th scope="col" class="w-0">#</th>
<th scope="col" class="w-0">Name</th>
<th scope="col" class="w-50">Beschreibung</th>
<th scope="col">Marke</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<th scope="row">
<a href="{{ url_for('item.details.show_item', item=item.id) }}">{{ item.id }}</a>
</th>
<td>{{ item.name }}</td>
<td>{{ item.description }}</td>
<td>{{ item.Brand.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% with pagination_object=items %}
{% include "utils/general/_page_navigation.html" %}
{% endwith %}
{% endblock %}

View File

@ -0,0 +1 @@
<h3>({{ item.id }}) {{ item.name }}</h3>

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