121 Commits

Author SHA1 Message Date
5ca2f16757 Merge branch 'master' of ssh://repo.hottis.de:2922/hv2/hv2-all-in-one 2022-02-05 17:56:49 +01:00
292886f834 fiscalyear is number 2022-02-05 17:56:29 +01:00
3debcf6c2d fix formcontrol handling 2022-01-31 17:24:56 +01:00
af7b388118 fix 2022-01-29 19:37:09 +01:00
b561ec8a48 small fix 2022-01-29 19:18:50 +01:00
9cb599e2a6 ready so far 2022-01-29 18:35:58 +01:00
d7c713404d overview tmpl 2022-01-29 15:43:50 +01:00
7d8755aab9 overview calculated 2022-01-29 14:40:14 +01:00
f2f2100b8c tenant letter nearly done 2022-01-29 13:42:58 +01:00
b3a49b0fb6 minus_area in premise, calculation of areas and factors 2022-01-24 18:06:10 +01:00
e1ebfe254a consider year in creating fee and overhead requests 2022-01-24 16:36:28 +01:00
b748ba7a77 fiscal year 2022-01-24 16:11:02 +01:00
ab365c65a1 add fiscal_year, drop due_to 2022-01-21 20:46:28 +01:00
0e4fd12238 commercial unit area and overhead stuff 2022-01-06 23:16:34 +01:00
cb632e9e8e due at fix 2 2022-01-06 21:52:17 +01:00
2fc7922707 due_at fix 2022-01-06 21:06:16 +01:00
422a8d37ab get flat tenants 2021-12-19 14:44:27 +01:00
006b488c63 start overhead accounts 2021-12-19 14:17:44 +01:00
1d36d99462 option to select config file 2021-12-19 12:51:35 +01:00
05fb3c1677 disable description at payment entry form 2021-12-12 14:07:25 +01:00
cbc96036d9 adjusted gitignore file 2021-11-09 10:39:37 +01:00
44202ef9ed Merge branch 'overhead_account' of ssh://repo.hottis.de:2922/hv2/hv2-all-in-one into overhead_account 2021-11-09 10:22:12 +01:00
34f8e8ecd4 note on betriebskosten at ledger page 2021-11-09 10:21:47 +01:00
6f3248f03c more fix premise account stuff 2021-11-09 10:14:16 +01:00
3c97fb3582 fix premise account stuff 2021-11-09 10:02:53 +01:00
3ad019b374 schema changes 2021-11-08 21:05:24 +01:00
28e505f570 overhead account stuff 2021-11-08 21:04:21 +01:00
ba63874a18 change order 2021-11-02 13:11:02 +01:00
0e1e03f1a9 error dialog introduced 2021-11-02 13:06:34 +01:00
272500df8c one-way-binding in account entry form works now 2021-11-02 12:36:35 +01:00
0ab106d021 switch to one-way-binding in form 2021-11-01 21:17:20 +01:00
125af5a206 viewchild, das hat es noch nicht gebracht 2021-11-01 09:40:19 +01:00
419997cea5 not yet working correctly 2021-10-31 23:06:02 +01:00
797151d547 more ledger stuff 2021-10-31 22:27:00 +01:00
2b883aee02 ledger 2021-10-31 21:47:53 +01:00
8c4dbe7d71 add ledger, Buchfuehrung 2021-10-31 16:06:18 +01:00
d297eb60b3 option to disable colors in output 2021-09-15 17:36:57 +02:00
5744e84842 Merge branch 'master' of https://home.hottis.de/gitlab/hv2/hv2-all-in-one 2021-09-15 17:32:16 +02:00
b8083ec41e outer join for tenant-saldo-query 2021-09-15 17:31:55 +02:00
f559aba317 default today to monthly payment 2021-09-14 22:36:25 +02:00
97dfcbe2fb optimize tenant with saldo query, fix 2021-09-14 14:31:42 +02:00
76255efbe9 optimize tenant with saldo query 2021-09-14 14:14:56 +02:00
797cbb4b65 monthly payment 2021-09-14 13:38:27 +02:00
4a54190b00 fix 2021-09-14 10:34:33 +02:00
3977685915 consistency check 2021-09-14 10:33:08 +02:00
66283bb533 fix 2021-09-13 19:28:18 +02:00
966ad7aee8 begin cli work 2021-09-13 19:27:32 +02:00
2ec6311ca9 home component 2021-09-13 17:11:17 +02:00
db089a9f2a Mietzahlung eintragen 2021-09-13 16:47:56 +02:00
a8296ee210 logout out automatically when session expires 2021-09-12 13:26:08 +02:00
e3236b671d fix menu 2021-09-12 10:35:11 +02:00
457ba1bee5 disable messages when not logged in 2021-09-11 23:58:25 +02:00
22db10a31e disable menu when not logged in 2021-09-11 23:54:18 +02:00
4f88ac5d0e disable menu when not logged in 2021-09-11 23:50:59 +02:00
3ae92c020c disable menu when not logged in 2021-09-11 23:34:29 +02:00
d098317331 disable ui 2021-09-11 19:19:16 +02:00
641565d8ff config 2021-09-11 19:02:00 +02:00
25d016c154 cors 2021-09-11 18:50:54 +02:00
39703dca22 cors 2021-09-11 18:44:43 +02:00
c06184d1b1 fix in ci 2021-09-11 18:13:02 +02:00
1450c8fdd7 fix in ci 2021-09-11 18:12:09 +02:00
72a3241286 fix in ci 2021-09-11 18:09:10 +02:00
d2cf532a0e fix in ci 2021-09-11 17:52:38 +02:00
dbb9686312 changes in ci 2021-09-11 17:46:29 +02:00
05823a1829 nullable api params and tenancy mapping 2021-09-11 17:07:39 +02:00
df4adecda1 fix 2021-09-10 19:07:08 +02:00
412e2b61f3 date fixes 2021-09-10 13:35:12 +02:00
2a23d88dd9 fixes 2021-09-10 12:40:48 +02:00
2da6b667bc fixes 2021-09-10 11:59:08 +02:00
554a809ba4 fix 2021-09-09 18:43:27 +02:00
204d2a27f2 notes 2021-09-09 18:39:18 +02:00
6fcd785be0 locale stuff 2021-09-09 18:00:24 +02:00
8276e39a7e account entry stuff again 2021-09-09 17:01:27 +02:00
e55578759f account entries stuff 2021-09-09 16:39:05 +02:00
eae137bea8 fix 2021-09-09 15:52:38 +02:00
75120b1d80 account at tenant 2021-09-09 15:49:49 +02:00
b394a16d7e some changes about fee and tenancy mapping 2021-09-09 11:36:01 +02:00
3dfb7e2bf2 accordion 2021-09-08 18:47:05 +02:00
0ae59c644e changes 2021-09-08 18:13:59 +02:00
c6e865eca1 overhead advance mapping 2021-09-08 14:33:01 +02:00
6b8b332758 code beautify 2021-09-08 14:32:43 +02:00
acc2170455 Betriebskostenzuordnung 2021-09-08 13:20:00 +02:00
76cbcc8afc add special method here too 2021-09-08 13:11:42 +02:00
7de0ab2db9 add special method 2021-09-08 13:11:28 +02:00
01bd6877c6 add foreign key service methods 2021-09-08 12:39:23 +02:00
bb2aaa1e84 generate endpoint for all foreign key relationships 2021-09-08 12:33:26 +02:00
1335f0f059 additional endpoint mechanism and overhead advance mapping by flat method 2021-09-08 12:18:09 +02:00
cf35af9c1e fee pages 2021-09-07 17:52:02 +02:00
356c8c0bbd re-enable submit button in finally block 2021-09-07 16:02:09 +02:00
d21a5986a8 drop mislocated put definition from yaml 2021-09-07 15:46:31 +02:00
6470aa5358 update methods and immutable attribute added 2021-09-07 15:22:55 +02:00
0afef9f3cd unsubscribe an existing subscription before creating a new one (avoids jumps in session timer in case of multiple query on one page) 2021-09-07 14:44:48 +02:00
fd25f1fcf5 expirytime finally working 2021-09-06 18:12:45 +02:00
9478ad27c6 Merge branch 'master' of https://home.hottis.de/gitlab/hv2/hv2-all-in-one 2021-09-06 14:04:17 +02:00
bbe698fc12 expiry display not working 2021-09-06 14:03:08 +02:00
606ac6d81f THERE IS A RACE CONDITION BETWEEN USE AND REPEATED REFRESH OF A TOKEN 2021-09-03 22:07:52 +02:00
657d85538e expiry display, not yet working 2021-09-03 20:13:45 +02:00
2312f21d77 refresh token support 2021-09-03 19:31:46 +02:00
65d10685a4 Merge branch 'master' of https://home.hottis.de/gitlab/hv2/hv2-all-in-one 2021-09-03 11:57:52 +02:00
5957662f2d debug in authentication path 2021-09-03 11:57:46 +02:00
974415a93a fix insert of empty strings 2021-09-02 21:57:06 +02:00
1ce54cf9cf overhead stuff 2021-09-02 18:26:55 +02:00
7c180f0986 more details pages 2021-09-02 15:50:29 +02:00
ce4ff75bca add NULL objects to template 2021-09-02 15:37:43 +02:00
13c9cb4d96 changes for details 2021-09-01 17:58:49 +02:00
5099e4ae63 extend form to handle inserts for tenant 2021-08-31 17:26:12 +02:00
4f8f0fde9d add post call apis to services 2021-08-31 17:25:47 +02:00
6bb1eec181 add post method to api 2021-08-31 17:24:54 +02:00
e238b1fb9f option to insert new tenant 2021-08-31 10:51:08 +02:00
af0e4ffd74 tenant details form 2021-08-30 19:00:27 +02:00
829aefc514 add new pages 2021-08-30 15:55:08 +02:00
083badeacc add CORS 2021-08-30 15:54:41 +02:00
41af716208 docs 2021-08-29 13:39:04 +02:00
f88ec664ea fix path to configuration 2021-08-29 13:17:57 +02:00
8c443b19cd docs 2021-08-29 13:16:50 +02:00
c76b66517b ignore secret configuration 2021-08-29 13:15:53 +02:00
61dc2b28e6 add readme 2021-08-29 12:58:45 +02:00
b65b48a364 generate stuff 2021-08-29 12:50:59 +02:00
2733c5fbac drop generate.sh, generate.py can do it alone using recursive glob 2021-08-29 12:44:38 +02:00
95fbafb217 generated comment 2021-08-28 21:12:08 +02:00
a63076c07c add selector functionality to generate script 2021-08-28 19:00:49 +02:00
136 changed files with 8314 additions and 320 deletions

11
.gitignore vendored
View File

@ -1,3 +1,12 @@
__pycache__/ __pycache__/
ENV ENV
api/config/dbconfig.ini
api/config/authservice.pub
cli/config/dbconfig.ini
*~
.*~
.vscode/
cli/*.tex
cli/*.log
cli/*.pdf
cli/*.aux

View File

@ -2,6 +2,7 @@ stages:
- check - check
- build - build
- dockerize - dockerize
- deploy
check: check:
@ -55,6 +56,7 @@ build-ui:
- cd ui/hv2-ui - cd ui/hv2-ui
- if [ "$CI_COMMIT_TAG" != "" ]; then - if [ "$CI_COMMIT_TAG" != "" ]; then
sed -i -e 's/GITTAGVERSION/'"$CI_COMMIT_TAG"':'"$CI_COMMIT_SHORT_SHA"'/' ./src/app/navigation/navigation.component.html; sed -i -e 's/GITTAGVERSION/'"$CI_COMMIT_TAG"':'"$CI_COMMIT_SHORT_SHA"'/' ./src/app/navigation/navigation.component.html;
sed -i -e 's,http://localhost:8080,https://api.hv.nober.de,' ./src/app/config.ts;
fi fi
- npm install - npm install
- ./node_modules/.bin/ng build --prod - ./node_modules/.bin/ng build --prod
@ -80,4 +82,40 @@ dockerize-ui:
docker push $IMAGE_NAME:${CI_COMMIT_TAG}; docker push $IMAGE_NAME:${CI_COMMIT_TAG};
fi fi
.deploy:
image: registry.hottis.de/dockerized/docker-bash:latest
stage: deploy
tags:
- hottis
- linux
- docker
only:
- tags
variables:
GIT_STRATEGY: none
script:
- docker stop $CONTAINER_NAME || echo "container not running, never mind"
- docker rm $CONTAINER_NAME || echo "container not existing, never mind"
- docker run -d --network docker-server
--ip $CONTAINER_IP
$VOLUMEOPT
--name $CONTAINER_NAME
--restart always
$IMAGE_NAME:$CI_COMMIT_TAG
deploy-api:
extends:
- .deploy
variables:
IMAGE_NAME: ${CI_REGISTRY}/${CI_PROJECT_PATH}/api
CONTAINER_NAME: hv2-api
CONTAINER_IP: 172.16.10.38
VOLUMEOPT: -v hv2-api-conf:/opt/app/config
deploy-ui:
extends:
- .deploy
variables:
IMAGE_NAME: ${CI_REGISTRY}/${CI_PROJECT_PATH}/ui
CONTAINER_NAME: hv2-ui
CONTAINER_IP: 172.16.10.39

View File

@ -0,0 +1,30 @@
# -------------------------------------------------------------------
# ATTENTION: This file will not be parsed by Cheetah
# Use plain openapi/yaml syntax, no Cheetah
# escaping
# -------------------------------------------------------------------
tenant_with_saldo:
description: tenant with saldo
type: object
properties:
id:
type: integer
salutation:
type: string
nullable: true
firstname:
type: string
nullable: true
lastname:
type: string
nullable: true
address1:
type: string
nullable: true
saldo:
type: number
nullable: true

View File

@ -0,0 +1,130 @@
# -------------------------------------------------------------------
# ATTENTION: This file will not be parsed by Cheetah
# Use plain openapi/yaml syntax, no Cheetah
# escaping
# -------------------------------------------------------------------
/v1/overhead_advances/flat/{flatId}:
get:
tags: [ "overhead_advance", "flat" ]
summary: Return overhead_advances by flat
operationId: additional_methods.get_overhead_advances_by_flat
parameters:
- name: flatId
in: path
required: true
schema:
type: integer
responses:
'200':
description: overhead_advances_by_flat response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/overhead_advance'
security:
- jwt: ['secret']
/v1/fees/tenancy/{tenancyId}:
get:
tags: [ "fee", "tenancy" ]
summary: Return fees by tenancy
operationId: additional_methods.get_fees_by_tenancy
parameters:
- name: tenancyId
in: path
required: true
schema:
type: integer
responses:
'200':
description: get_fees_by_tenancy response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/fee'
security:
- jwt: ['secret']
/v1/account/saldo/{accountId}:
get:
tags: [ "account" ]
summary: Return saldo of the account
operationId: additional_methods.get_account_saldo
parameters:
- name: accountId
in: path
required: true
schema:
type: integer
responses:
'200':
description: get_account_saldo
content:
'application/json':
schema:
type: object
properties:
saldo:
type: number
security:
- jwt: ['secret']
/v1/tenants/saldo:
get:
tags: [ "tenant", "account" ]
summary: Return tenant with saldo of the account
operationId: additional_methods.get_tenant_with_saldo
responses:
'200':
description: get_tenant_with_saldo
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/tenant_with_saldo'
security:
- jwt: ['secret']
/v1/accounts/bydescription/{description}:
get:
tags: [ "account" ]
summary: Return the normalized account with given description
operationId: additional_methods.get_account_by_description
parameters:
- name: description
in: path
required: true
schema:
type: string
responses:
'200':
description: account response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/account'
security:
- jwt: ['secret']
/v1/uniquenumber:
get:
tags: [ "uniquenumber" ]
summary: Returns a unique number
operationId: additional_methods.get_unique_number
responses:
'200':
description: get_unique_number
content:
'application/json':
schema:
type: object
properties:
number:
type: number
security:
- jwt: ['secret']

63
api/additional_methods.py Normal file
View File

@ -0,0 +1,63 @@
from db import dbGetMany, dbGetOne, dbInsert, dbUpdate
from loguru import logger
import werkzeug
def get_overhead_advances_by_flat(user, token_info, flatId=None):
return dbGetMany(user, token_info, {
"statement": """
SELECT o.id ,o.description ,o.amount ,o.startdate ,o.enddate
FROM overhead_advance_t o, overhead_advance_flat_mapping_t m
WHERE o.id = m.overhead_advance and m.flat = %s""",
"params": (flatId, )
}
)
def get_fees_by_tenancy(user, token_info, tenancyId=None):
return dbGetMany(user, token_info, {
"statement": """
SELECT o.id, o.description, o.amount, o.fee_type, o.startdate, o.enddate
FROM fee_t o, tenancy_fee_mapping_t m
WHERE o.id = m.fee and m.tenancy = %s""",
"params": (tenancyId, )
}
)
def get_account_saldo(user, token_info, accountId=None):
return dbGetOne(user, token_info, {
"statement": "SELECT sum(amount) as saldo FROM account_entry_t WHERE account=%s",
"params": (accountId, )
}
)
def get_tenant_with_saldo(user, token_info):
return dbGetMany(user, token_info, {
"statement": """
SELECT t.id, t.firstname, t.lastname, t.address1, sum(a.amount) AS saldo
FROM tenant_t t LEFT OUTER JOIN account_entry_t a ON a.account = t.account
GROUP BY t.id, t.firstname, t.lastname, t.address1
""",
"params": ()
}
)
def get_account_by_description(user, token_info, description=None):
return dbGetOne(user, token_info, {
"statement": """
SELECT a.id ,a.description
FROM account_t a
WHERE a.description = %s""",
"params": (description, )
}
)
def get_unique_number(user, token_info):
return dbGetOne(user, token_info, {
"statement": """
SELECT nextval('unique_number_s') AS "number"
""",
"params": ()
})

View File

@ -8,7 +8,7 @@ JWT_PUB_KEY = ""
try: try:
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"] JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
except KeyError: except KeyError:
with open('/opt/app/config/authservice.pub', 'r') as f: with open('./config/authservice.pub', 'r') as f:
JWT_PUB_KEY = f.read() JWT_PUB_KEY = f.read()

View File

@ -19,7 +19,7 @@ try:
DB_NAME = os.environ["DB_NAME"] DB_NAME = os.environ["DB_NAME"]
except KeyError: except KeyError:
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read('/opt/app/config/dbconfig.ini') config.read('./config/dbconfig.ini')
DB_USER = config["database"]["user"] DB_USER = config["database"]["user"]
DB_PASS = config["database"]["pass"] DB_PASS = config["database"]["pass"]
DB_HOST = config["database"]["host"] DB_HOST = config["database"]["host"]
@ -49,8 +49,11 @@ def execDatabaseOperation(func, params):
sslmode = 'require') sslmode = 'require')
conn.autocommit = False conn.autocommit = False
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur: with conn:
return func(cur, params) with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
params["params"] = [ v if not v=='' else None for v in params["params"] ]
logger.debug("edo: {}".format(str(params)))
return func(cur, params)
except psycopg2.Error as err: except psycopg2.Error as err:
raise Exception("Error when connecting to database: {}".format(err)) raise Exception("Error when connecting to database: {}".format(err))
finally: finally:
@ -61,7 +64,7 @@ def execDatabaseOperation(func, params):
def _opGetMany(cursor, params): def _opGetMany(cursor, params):
items = [] items = []
cursor.execute(params["statement"]) cursor.execute(params["statement"], params["params"])
for itemObj in cursor: for itemObj in cursor:
logger.debug("item received {}".format(str(itemObj))) logger.debug("item received {}".format(str(itemObj)))
items.append(itemObj) items.append(itemObj)
@ -101,3 +104,19 @@ def dbGetOne(user, token_info, params):
except Exception as e: except Exception as e:
logger.error(f"Exception: {e}") logger.error(f"Exception: {e}")
raise werkzeug.exceptions.InternalServerError raise werkzeug.exceptions.InternalServerError
def dbInsert(user, token_info, params):
logger.info("params: {}, token: {}".format(params, json.dumps(token_info)))
try:
return execDatabaseOperation(_opGetOne, params)
except Exception as e:
logger.error(f"Exception: {e}")
raise werkzeug.exceptions.InternalServerError
def dbUpdate(user, token_info, params):
logger.info("params: {}, token: {}".format(params, json.dumps(token_info)))
try:
return execDatabaseOperation(_opGetOne, params)
except Exception as e:
logger.error(f"Exception: {e}")
raise werkzeug.exceptions.InternalServerError

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
from db import dbGetMany, dbGetOne $GENERATED_PYTHON_COMMENT
from db import dbGetMany, dbGetOne, dbInsert, dbUpdate
from loguru import logger
import werkzeug
#for $table in $tables #for $table in $tables
def get_${table.name}s(user, token_info): def get_${table.name}s(user, token_info):
@ -10,11 +14,56 @@ SELECT
,$column.name ,$column.name
#end for #end for
FROM ${table.name}_t FROM ${table.name}_t
#if $table.selectors
ORDER BY
#set $sep = ""
#for $selector in $table.selectors
$sep$selector
#set $sep = ","
#end for
#end if
""", """,
"params": () "params": ()
} }
) )
def insert_${table.name}(user, token_info, **args):
try:
body = args["body"]
#for $column in $table.columns
v_$column.name = body["$column.name"]
#end for
return dbInsert(user, token_info, {
"statement": """
INSERT INTO ${table.name}_t
(
#set $sep=""
#for $column in $table.columns
$sep$column.name
#set $sep=","
#end for
) VALUES (
#set $sep=""
#for $column in $table.columns
$sep%s
#set $sep=","
#end for
)
RETURNING *
""",
"params": [
#set $sep=""
#for $column in $table.columns
${sep}v_$column.name
#set $sep=","
#end for
]
})
except KeyError as e:
logger.warning("insert_${table.name}: parameter missing: {}".format(e))
raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e))
def get_${table.name}(user, token_info, ${table.name}Id=None): def get_${table.name}(user, token_info, ${table.name}Id=None):
return dbGetOne(user, token_info, { return dbGetOne(user, token_info, {
"statement": """ "statement": """
@ -29,4 +78,70 @@ SELECT
"params": (${table.name}Id, ) "params": (${table.name}Id, )
} }
) )
#if (('immutable' not in $table) or (not $table.immutable))
def update_${table.name}(user, token_info, ${table.name}Id=None, **args):
try:
body = args["body"]
#for $column in $table.columns
#if (('immutable' not in $column) or (not $column.immutable))
v_$column.name = body["$column.name"]
#end if
#end for
return dbUpdate(user, token_info, {
"statement": """
UPDATE ${table.name}_t
SET
#set $sep=""
#for $column in $table.columns
#if (('immutable' not in $column) or (not $column.immutable))
$sep$column.name = %s
#end if
#set $sep=","
#end for
WHERE id = %s
RETURNING *
""",
"params": [
#for $column in $table.columns
#if (('immutable' not in $column) or (not $column.immutable))
v_${column.name},
#end if
#end for
${table.name}Id
]
})
except KeyError as e:
logger.warning("update_${table.name}: parameter missing: {}".format(e))
raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e))
#end if
#for $column in $table.columns
#if (('foreignkey' in $column) and $column.foreignkey)
def get_${table.name}_by_${column.name}(user, token_info, ${column.name}Id=None):
return dbGetMany(user, token_info, {
"statement": """
SELECT
id
#for $innerColumn in $table.columns
,$innerColumn.name
#end for
FROM ${table.name}_t
WHERE ${column.name} = %s
#if $table.selectors
ORDER BY
#set $sep = ""
#for $selector in $table.selectors
$sep$selector
#set $sep = ","
#end for
#end if
""",
"params": (${column.name}Id, )
}
)
#end if
#end for
#end for #end for

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
$GENERATED_YAML_COMMENT
openapi: 3.0.0 openapi: 3.0.0
info: info:
title: hv2-api title: hv2-api
@ -29,6 +31,27 @@ paths:
\$ref: '#/components/schemas/$table.name' \$ref: '#/components/schemas/$table.name'
security: security:
- jwt: ['secret'] - jwt: ['secret']
post:
tags: [ "$table.name" ]
summary: Insert a ${table.name}
operationId: methods.insert_${table.name}
requestBody:
description: $table.name
content:
application/json:
schema:
\$ref: '#/components/schemas/$table.name'
responses:
'200':
description: ${table.name} successfully inserted
content:
'application/json':
schema:
type: array
items:
\$ref: '#/components/schemas/$table.name'
security:
- jwt: ['secret']
/v1/${table.name}s/{${table.name}Id}: /v1/${table.name}s/{${table.name}Id}:
get: get:
tags: [ "$table.name" ] tags: [ "$table.name" ]
@ -51,7 +74,64 @@ paths:
\$ref: '#/components/schemas/$table.name' \$ref: '#/components/schemas/$table.name'
security: security:
- jwt: ['secret'] - jwt: ['secret']
#if (('immutable' not in $table) or (not $table.immutable))
put:
tags: [ "$table.name" ]
summary: Update a ${table.name}
operationId: methods.update_${table.name}
parameters:
- name: ${table.name}Id
in: path
required: true
schema:
type: integer
requestBody:
description: $table.name
content:
application/json:
schema:
\$ref: '#/components/schemas/$table.name'
responses:
'200':
description: ${table.name} successfully inserted
content:
'application/json':
schema:
type: array
items:
\$ref: '#/components/schemas/$table.name'
security:
- jwt: ['secret']
#end if
#for $column in $table.columns
#if (('foreignkey' in $column) and $column.foreignkey)
/v1/${table.name}s/${column.name}/{${column.name}Id}:
get:
tags: [ "$table.name", "$column.name" ]
summary: Return $table.name by $$column.name
operationId: methods.get_${table.name}_by_${column.name}
parameters:
- name: ${column.name}Id
in: path
required: true
schema:
type: integer
responses:
'200':
description: $table.name response
content:
'application/json':
schema:
type: array
items:
\$ref: '#/components/schemas/$table.name'
security:
- jwt: ['secret']
#end if
#end for #end for
#end for
#include raw "./api/additional_endpoints.yaml"
components: components:
securitySchemes: securitySchemes:
@ -71,5 +151,10 @@ components:
#for $column in $table.columns #for $column in $table.columns
$column.name: $column.name:
type: $column.apitype type: $column.apitype
#if (('notnull' not in $column) or (not $column.notnull))
nullable: true
#end if
#end for #end for
#end for #end for
#include raw "./api/additional_components.yaml"

View File

@ -1,14 +0,0 @@
#!/bin/bash
. ENV
IMAGE_NAME="registry.hottis.de/hv2/hv2-api"
VERSION=0.0.x
docker run \
-d \
--rm \
--name "hv2-api" \
-p 5000:5000
${IMAGE_NAME}:${VERSION}

View File

@ -3,10 +3,16 @@ from flask_cors import CORS
# instantiate the webservice # instantiate the webservice
app = connexion.App(__name__) app = connexion.App(__name__)
app.add_api('openapi.yaml') app.add_api('openapi.yaml', options = {"swagger_ui": False})
# CORSify it - otherwise Angular won't accept it # CORSify it - otherwise Angular won't accept it
CORS(app.app) CORS(app.app,
origins=[
"http://localhost:4200",
"https://base.hv.nober.de"
],
supports_credentials=True
)
# provide the webservice application to uwsgi # provide the webservice application to uwsgi
application = app.app application = app.app

View File

@ -1,8 +1,10 @@
import connexion import connexion
import logging import logging
from flask_cors import CORS
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
app = connexion.App('hv2-api') app = connexion.App('hv2-api')
app.add_api('./openapi.yaml') app.add_api('./openapi.yaml', options = {"swagger_ui": True})
CORS(app.app)
app.run(port=8080) app.run(port=8080)

View File

@ -1,61 +0,0 @@
stages:
- check
- build
- deploy
variables:
IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH
check:
image: registry.hottis.de/dockerized/base-build-env:latest
stage: check
tags:
- hottis
- linux
- docker
rules:
- if: $CI_COMMIT_TAG
script:
- checksemver.py -v
--versionToValidate "${CI_COMMIT_TAG}"
--validateMessage
--messageToValidate "${CI_COMMIT_MESSAGE}"
build:
image: registry.hottis.de/dockerized/docker-bash:latest
stage: build
tags:
- hottis
- linux
- docker
script:
- docker build --tag $IMAGE_NAME:latest .
- if [ "$CI_COMMIT_TAG" != "" ]; then
docker tag $IMAGE_NAME:latest $IMAGE_NAME:${CI_COMMIT_TAG};
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY;
docker push $IMAGE_NAME:latest;
docker push $IMAGE_NAME:${CI_COMMIT_TAG};
fi
deploy:
stage: deploy
image: registry.hottis.de/dockerized/docker-bash:latest
only:
- tags
tags:
- hottis
- linux
- docker
variables:
GIT_STRATEGY: none
script:
- CONTAINER_NAME=$CI_PROJECT_NAME
- SERVICE_VOLUME=$CI_PROJECT_NAME"-conf"
- SERVICE_PORT=5000
- docker volume inspect $SERVICE_VOLUME || docker volume create $SERVICE_VOLUME
- docker stop $CONTAINER_NAME || echo "$CONTAINER_NAME not running, anyway okay"
- docker rm $CONTAINER_NAME || echo "$CONTAINER_NAME not exsting, anyway okay"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY;
- docker pull $IMAGE_NAME:${CI_COMMIT_TAG}
- docker run -d --restart always --name $CONTAINER_NAME --network docker-server --ip 172.16.10.38 -v $SERVICE_VOLUME:/opt/app/config $IMAGE_NAME:${CI_COMMIT_TAG}

View File

@ -1,88 +0,0 @@
stages:
- check
- build
- dockerize
- deploy
variables:
IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH
check:
image: registry.hottis.de/dockerized/base-build-env:latest
stage: check
tags:
- hottis
- linux
- docker
rules:
- if: $CI_COMMIT_TAG
script:
- checksemver.py -v
--versionToValidate "$CI_COMMIT_TAG"
--validateMessage
--messageToValidate "$CI_COMMIT_MESSAGE"
build:
image: registry.hottis.de/hv2/hv2-node-build-env:1.0.0
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
tags:
- hottis
- linux
- docker
artifacts:
paths:
- dist.tgz
expire_in: 1 day
script:
- cd hv2-ui
- if [ "$CI_COMMIT_TAG" != "" ]; then
sed -i -e 's/GITTAGVERSION/'"$CI_COMMIT_TAG"':'"$CI_COMMIT_SHORT_SHA"'/' ./src/app/navigation/navigation.component.html;
fi
- npm install
- for F in ./src/app/*.tmpl; do
python ../helpers/hv2-api/generate.py -s ../helpers/hv2-api/schema.json -t $F;
done
- ./node_modules/.bin/ng build --prod
- tar -czf ../dist.tgz dist
dockerize:
image: registry.hottis.de/dockerized/docker-bash:latest
stage: dockerize
tags:
- hottis
- linux
- docker
rules:
- if: $CI_COMMIT_TAG
script:
- tar -xzf dist.tgz
- docker build --tag $IMAGE_NAME:latest --tag $IMAGE_NAME:$CI_COMMIT_TAG .
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker push $IMAGE_NAME:latest
- docker push $IMAGE_NAME:$CI_COMMIT_TAG
deploy:
stage: deploy
image: registry.hottis.de/dockerized/docker-bash:latest
only:
- tags
tags:
- hottis
- linux
- docker
variables:
GIT_STRATEGY: none
script:
- CONTAINER_NAME=$CI_PROJECT_NAME
- docker stop $CONTAINER_NAME || echo "$CONTAINER_NAME not running, anyway okay"
- docker rm $CONTAINER_NAME || echo "$CONTAINER_NAME not exsting, anyway okay"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY;
- docker pull $IMAGE_NAME:${CI_COMMIT_TAG}
- docker run -d --restart always --name $CONTAINER_NAME --network docker-server --ip 172.16.10.39 $IMAGE_NAME:${CI_COMMIT_TAG}

126
cli/ConsistencyCheck.py Normal file
View File

@ -0,0 +1,126 @@
from db import dbGetMany
from loguru import logger
errorCnt = 0
def perform(dbh, params):
global errorCnt
checkTenant(dbh, params)
checkFlats(dbh, params)
checkParkings(dbh, params)
checkCommercialPremise(dbh, params)
if (errorCnt > 0):
logger.error(f"Total error count: {errorCnt}")
def checkTenant(dbh, params):
global errorCnt
tenants = dbGetMany(dbh, { "statement": "SELECT * FROM tenant_t", "params": () })
for tenant in tenants:
outPre = f"Tenant: {tenant['firstname']} {tenant['lastname']}"
logger.info(outPre)
# check tenancies
tenancyCnt = 0
flatTenancyCnt = 0
tenancies = dbGetMany(dbh, {
"statement": "SELECT * FROM tenancy_t WHERE tenant = %s AND startdate < now() AND (enddate > now() or enddate is null)",
"params": (tenant['id'], )
}
)
for tenancy in tenancies:
tenancyCnt += 1
if (tenancy['flat']):
flatTenancyCnt += 1
logger.info(f"{outPre}: Flat tenancy: {tenancy['id']}, start: {tenancy['startdate']}, end: {tenancy['enddate']}")
if (tenancy['parking']):
logger.info(f"{outPre}: Garage tenancy: {tenancy['id']}, start: {tenancy['startdate']}, end: {tenancy['enddate']}")
if (tenancy['commercial_premise']):
logger.info(f"{outPre}: Commercial premise tenancy: {tenancy['id']}, start: {tenancy['startdate']}, end: {tenancy['enddate']}")
if (flatTenancyCnt == 0):
logger.warning(f"{outPre}: no flat tenancy")
if (flatTenancyCnt > 1):
logger.warning(f"{outPre}: more than one flat tenancy ({flatTenancyCnt})")
if (tenancyCnt == 0):
logger.error(f"{outPre}: no tenancy at all")
errorCnt += 1
noCurrentTenancies = dbGetMany(dbh, {
"statement": "SELECT * FROM tenancy_t WHERE tenant = %s",
"params": (tenant['id'], )
}
)
for noCurrentTenancy in noCurrentTenancies:
logger.error(f"{outPre}: but: flat {noCurrentTenancy['flat']}, parking: {noCurrentTenancy['parking']}, commercial premise: {noCurrentTenancy['commercial_premise']}, start: {noCurrentTenancy['startdate']}, end: {noCurrentTenancy['enddate']}")
def _checkRentals(dbh, params, rentalType):
global errorCnt
table = f"{rentalType}_t"
rentals = dbGetMany(dbh, {
"statement": f"SELECT * FROM {table}",
"params": ()
}
)
for rental in rentals:
outPre = f"{rentalType}: {rental['description']}, premise: {rental['premise']}"
logger.info(outPre)
if (rentalType == 'flat'):
overheadMappingCnt = 0
overheadMappings = dbGetMany(dbh, {
"statement": "SELECT * FROM overhead_advance_flat_mapping_t WHERE flat = %s",
"params": (rental['id'], )
}
)
for overheadMapping in overheadMappings:
overheadMappingCnt += 1
logger.info(f"{outPre}: overhead mapping: {overheadMapping['id']}")
if (overheadMappingCnt == 0):
errorCnt += 1
logger.error(f"{outPre}: no overhead mapping available")
if (overheadMappingCnt > 1):
errorCnt += 1
logger.error(f"{outPre}: more than one overhead mapping available")
tenancyCnt = 0
tenancies = dbGetMany(dbh, {
"statement": f"SELECT * FROM tenancy_t WHERE {rentalType} = %s AND startdate < now() AND (enddate > now() or enddate is null)",
"params": (rental['id'], )
}
)
for tenancy in tenancies:
tenancyCnt += 1
logger.info(f"{outPre}: tenant: {tenancy['tenant']}, start: {tenancy['startdate']}, end: {tenancy['enddate']}")
feeMappingCnt = 0
feeMappings = dbGetMany(dbh, {
"statement": "SELECT * FROM tenancy_fee_mapping_t where tenancy = %s",
"params": (tenancy['id'], )
}
)
for feeMapping in feeMappings:
feeMappingCnt += 1
logger.info(f"{outPre}: fee mapping: {feeMapping['id']}")
if (feeMappingCnt == 0):
errorCnt += 1
logger.error(f"{outPre}: no fee mapping available")
if (feeMappingCnt > 1):
errorCnt += 1
logger.error(f"{outPre}: more than one fee mapping available")
if (tenancyCnt == 0):
errorCnt += 1
logger.error(f"{outPre}: vacant")
if (tenancyCnt > 1):
errorCnt += 1
logger.error(f"{outPre}: overbooked")
def checkFlats(dbh, params):
_checkRentals(dbh, params, "flat")
def checkParkings(dbh, params):
_checkRentals(dbh, params, "parking")
def checkCommercialPremise(dbh, params):
_checkRentals(dbh, params, "commercial_premise")

View File

@ -0,0 +1,117 @@
from db import dbGetMany, dbGetOne
from loguru import logger
from decimal import Decimal
import datetime
import iso8601
def perform(dbh, params):
try:
createdAt = iso8601.parse_date(params['created_at'])
except iso8601.iso8601.ParseError:
msg = f"Can not parse given date {params['created_at']}"
logger.error(msg)
raise Exception(msg)
except KeyError:
createdAt = datetime.datetime.today()
year = createdAt.year
createdAt = createdAt.strftime("%Y-%m-%d")
tenants = dbGetMany(dbh, { "statement": "SELECT * FROM tenant_t", "params": () })
for tenant in tenants:
logger.info(f"Tenant: {tenant['firstname']} {tenant['lastname']}")
# check tenancies
tenancies = dbGetMany(dbh, {
"statement": "SELECT * FROM tenancy_t WHERE tenant = %s AND startdate < now() AND (enddate > now() or enddate is null)",
"params": (tenant['id'], )
}
)
requests = []
for tenancy in tenancies:
fee = dbGetOne(dbh, {
"statement": """
SELECT f.amount, f.fee_type
FROM fee_t f, tenancy_fee_mapping_t t
WHERE t.tenancy = %s AND
f.id = t.fee AND
f.startdate < now() AND
(f.enddate > now() OR f.enddate is null)
""",
"params": (tenancy['id'], )
}
)
if (tenancy['flat']):
logger.debug(f" Flat tenancy: {tenancy['id']}, Fee: {fee['amount']}, Fee_Type: {fee['fee_type']}")
flat = dbGetOne(dbh, { "statement": "SELECT area FROM flat_t WHERE id = %s", "params": (tenancy['flat'], ) })
logger.debug(f" Area: {flat['area']}")
if (fee['fee_type'] == 'per_area'):
feeRequest = flat['area'] * fee['amount']
else:
feeRequest = fee['amount']
feeRequest = feeRequest.quantize(Decimal('1.00'))
requests.append({
'description': f"Miete {tenancy['description']}",
'account': tenant['account'],
'created_at': createdAt,
'amount': feeRequest,
'category': 'Mietforderung'
})
overheadAdvance = dbGetOne(dbh, {
"statement": """
SELECT o.amount
FROM overhead_advance_t o, overhead_advance_flat_mapping_t m
WHERE m.flat = %s AND
o.id = m.overhead_advance AND
o.startdate < now() AND
(o.enddate > now() OR o.enddate is null)
""",
"params": (tenancy['flat'], )
}
)
overheadAdvanceRequest = flat['area'] * overheadAdvance['amount']
overheadAdvanceRequest = overheadAdvanceRequest.quantize(Decimal('1.00'))
requests.append({
'description': f"Betriebskosten {tenancy['description']}",
'account': tenant['account'],
'created_at': createdAt,
'amount': overheadAdvanceRequest,
'category': 'Betriebskostenforderung'
})
if (tenancy['parking']):
logger.debug(f" Garage tenancy: {tenancy['id']}, Fee: {fee['amount']}, Fee_Type: {fee['fee_type']}")
feeRequest = fee['amount']
feeRequest = feeRequest.quantize(Decimal('1.00'))
requests.append({
'description': f"Miete {tenancy['description']}",
'account': tenant['account'],
'created_at': createdAt,
'amount': feeRequest,
'category': 'Mietforderung'
})
if (tenancy['commercial_premise']):
logger.debug(f" Commercial premise tenancy: {tenancy['id']}, Fee: {fee['amount']}, Fee_Type: {fee['fee_type']}")
feeRequest = fee['amount']
feeRequest = feeRequest.quantize(Decimal('1.00'))
requests.append({
'description': f"Miete {tenancy['description']}",
'account': tenant['account'],
'created_at': createdAt,
'amount': feeRequest,
'category': 'Mietforderung'
})
for request in requests:
request['amount'] = Decimal('-1.0') * request['amount']
logger.info(f" {request['description']}, {request['account']}, {request['created_at']}, {request['amount']}, {request['category']}")
accountEntry = dbGetOne(dbh, {
"statement": """
INSERT INTO account_entry_t
(description, account, created_at, fiscal_year, amount, account_entry_category)
VALUES (%s, %s, %s, %s, %s, (SELECT id FROM account_entry_category_t WHERE description = %s))
RETURNING id
""",
"params": (request['description'], request['account'], request['created_at'], year, request['amount'], request['category'])
}
)
logger.info(f" account entry entered with id {accountEntry['id']}")

314
cli/OverheadAccounts.py Normal file
View File

@ -0,0 +1,314 @@
from db import dbGetMany, dbGetOne
import datetime
from loguru import logger
from decimal import *
from utils import getParam
from Cheetah.Template import Template
EPSILON = Decimal('0.000000001')
def perform(dbh, params):
year = getParam(params, 'year', datetime.datetime.today().year)
startDate = datetime.datetime(year, 1, 1, 0, 0, 0)
endDate = datetime.datetime(year, 12, 31, 23, 59, 59)
premises = (1, 2)
houses = {}
for premise in premises:
# get overhead sums by object, category and timespan
overheadItems = dbGetMany(
dbh,
{
"statement":
"""
select sum(ae.amount) as sum,
aec.description as category,
aec.considerminusarea as considerminusarea
from account_t a,
premise_t p,
account_entry_t ae,
account_entry_category_t aec
where p.account = a.id and
ae.account = a.id and
aec.overhead_relevant = 't' and
ae.account_entry_category = aec.id and
ae.fiscal_year = %(year)s and
p.id = %(premise)s
group by category, considerminusarea
union
select 0 as sum,
aec.description as category,
aec.considerminusarea as considerminusarea
from account_t a,
premise_t p,
account_entry_t ae,
account_entry_category_t aec
where p.account = a.id and
ae.account = a.id and
aec.overhead_relevant = 't' and
aec.id not in (select distinct account_entry_category from account_entry_t) and
ae.fiscal_year = %(year)s and
p.id = %(premise)s
group by category, considerminusarea
union
select 120 as sum,
'Waschmaschine' as category,
false as considerminusarea
from premise_t
where id = %(premise)s
order by category
""",
"params": {
"year": year,
"premise": premise
}
}
)
# get areas and factors
totalArea = {}
flatArea = dbGetOne(
dbh,
{
"statement":
"""
select
sum(f.area) as flat_area
from
premise_t p,
flat_t f
where
f.premise = p.id and
p.id = %(premise)s
""",
"params": {
"premise": premise
}
}
)
totalArea['flat_area'] = flatArea['flat_area']
commercialArea = dbGetOne(
dbh,
{
"statement":
"""
select
coalesce(sum(c.area), 0) as commercial_area
from
premise_t p full outer join commercial_premise_t c on c.premise = p.id
where
p.id = %(premise)s
""",
"params": {
"premise": premise
}
}
)
totalArea['commercial_area'] = commercialArea['commercial_area']
minusAreaAndDetails = dbGetOne(
dbh,
{
"statement":
"""
select
p.minus_area as minus_area,
p.id as house_id,
p.description as house
from
premise_t p
where
p.id = %(premise)s
""",
"params": {
"premise": premise
}
}
)
totalArea['minus_area'] = minusAreaAndDetails['minus_area']
details = { 'id': minusAreaAndDetails['house_id'], 'description': minusAreaAndDetails['house'] }
totalArea['other_area'] = totalArea['commercial_area'] + totalArea['minus_area']
totalArea['total_area'] = totalArea['flat_area'] + totalArea['other_area']
totalArea['flat_factor'] = totalArea['flat_area'] / totalArea['total_area']
totalArea['other_factor'] = totalArea['other_area'] / totalArea['total_area']
totalArea['factor_check'] = totalArea['flat_factor'] + totalArea['other_factor']
totalSum = Decimal('0')
flatSum = Decimal('0')
otherSum = Decimal('0')
for overheadItem in overheadItems:
totalSum += overheadItem['sum']
overheadItem['flat_part'] = overheadItem['sum'] * totalArea['flat_factor'] if overheadItem['considerminusarea'] else overheadItem['sum']
flatSum += overheadItem['flat_part']
overheadItem['other_part'] = overheadItem['sum'] * totalArea['other_factor'] if overheadItem['considerminusarea'] else Decimal('0')
otherSum += overheadItem['other_part']
verifyDifference = totalSum - flatSum - otherSum
logger.debug(f"{totalSum=}, {verifyDifference=}")
if abs(verifyDifference) > EPSILON:
raise Exception(f"Verify Difference is too large: {premise=}, {verifyDifference=}")
umlageAusfallWagnis = flatSum * Decimal('0.02')
flatSum2 = flatSum + umlageAusfallWagnis
partByMonthArea = flatSum2 / Decimal('12') / totalArea['flat_area']
houses[details['id']] = {
'details': details,
'areas': totalArea,
'overhead_items': overheadItems,
'flat_sum': flatSum,
'flat_sum_2': flatSum2,
'other_sum': otherSum,
'total_sum': totalSum,
'umlage_ausfall_wagnis': umlageAusfallWagnis,
'part_by_montharea': partByMonthArea,
'year': year }
logger.info(f"{houses=}")
printOverviews = getParam(params, 'printOverviews', True)
if printOverviews:
overviewTemplate = getParam(params, 'overviewTemplate', 'betriebskostenuebersicht.tmpl')
overviewPrefix = getParam(params, 'overviewPrefix', 'overview')
overviewSuffix = getParam(params, 'overviewSuffix', 'tex')
for house in houses.values():
logger.debug(f"Processing item: {house}")
outputFile = f"{overviewPrefix}-{house['details']['id']}.{overviewSuffix}"
tmpl = Template(file=overviewTemplate, searchList=[ house ])
logger.debug(tmpl)
with open(outputFile, 'w') as f:
f.write(str(tmpl))
# get flat tenants by object and timespan with paid overhead and due overhead
tenants = dbGetMany(
dbh,
{
"statement":
"""
select t.id as tenant_id,
t.salutation as tenant_salutation,
t.firstname as tenant_firstname,
t.lastname as tenant_lastname,
t.address1 as tenant_address1,
t.address2 as tenant_address2,
t.address3 as tenant_address3,
t.zip as tenant_zip,
t.city as tenant_city,
f.id as flat_id,
f.description as flat,
f.area as flat_area,
p.id as house_id,
p.description as house,
ty.startdate as startdate,
ty.enddate as enddate,
t.account as tenant_account
from tenant_t t,
premise_t p,
flat_t f,
tenancy_t ty
where ty.tenant = t.id and
ty.flat = f.id and
ty.startdate <= %(startDate)s and
(ty.enddate >= %(endDate)s or ty.enddate is null) and
f.premise = p.id and
p.id in %(premises)s
order by house_id, tenant_id
""",
"params": {
"startDate": startDate,
"endDate": endDate,
"premises": premises
}
}
)
letters = []
for tenant in tenants:
letter = {}
paidTotal = dbGetOne(
dbh,
{
"statement":
"""
SELECT sum(amount) AS sum,
count(id) AS cnt
FROM account_entry_t
WHERE account = %(account)s AND
account_entry_category = 2 AND
fiscal_year = %(year)s
""",
"params": {
"account": tenant['tenant_account'],
"year": year
}
}
)
tenant['paid_total'] = paidTotal['sum']
receivableFee = dbGetOne(
dbh,
{
"statement":
"""
SELECT sum(amount) * -1 AS sum ,
count(id) AS cnt
FROM account_entry_t
WHERE account = %(account)s AND
account_entry_category = 3 AND
fiscal_year = %(year)s
""",
"params": {
"account": tenant['tenant_account'],
"year": year
}
}
)
tenant['receivable_fee'] = receivableFee['sum']
tenant['rent_time'] = receivableFee['cnt']
tenant['paid_overhead'] = paidTotal['sum'] - receivableFee['sum']
letter['tenant'] = tenant
letter['year'] = year
letter['flat_area'] = houses[tenant['house_id']]['areas']['flat_area']
letter['receivable_overhead'] = tenant['flat_area'] * houses[tenant['house_id']]['part_by_montharea'] * tenant['rent_time']
letter['unbalanced_overhead'] = tenant['paid_overhead'] - letter['receivable_overhead']
letter['unbalanced_overhead_unsigned'] = abs(letter['unbalanced_overhead'])
letter['total_overhead'] = houses[tenant['house_id']]['flat_sum_2']
letters.append(letter)
logger.info(f"{letter=}")
printLetters = getParam(params, 'printLetters', True)
if printLetters:
letterTemplate = getParam(params, 'letterTemplate', 'jahresabrechnung.tmpl')
letterPrefix = getParam(params, 'letterPrefix', 'letter')
letterSuffix = getParam(params, 'letterSuffix', 'tex')
for letter in letters:
logger.debug(f"Processing item: {letter}")
outputFile = f"{letterPrefix}-{letter['tenant']['tenant_id']}.{letterSuffix}"
tmpl = Template(file=letterTemplate, searchList=[ letter ])
logger.debug(tmpl)
with open(outputFile, 'w') as f:
f.write(str(tmpl))

View File

@ -0,0 +1,42 @@
\documentclass[12pt]{article}
\usepackage{german}
\usepackage{eurosym}
\usepackage[a4paper, left=2cm, right=2cm, top=2cm]{geometry}
\usepackage{icomma}
\setlength{\parindent}{0pt}
\begin{document}
\subsection*{Betriebskostenabrechnung}
\begin{tabular}{ll}
Objekt & $details['description'] \\
Jahr & $year \\
Eigent"umer & Nober Grundbesitz GmbH \\
\end{tabular}
\addvspace{1cm}
\begin{tabular}{|p{5cm}|r|r|r|}
\hline Art & Wohnungen & andere Fl"achen & Gesamtfl"ache \\
\hline
\hline Fl"ache & \tt{$areas['flat_area']\,m\textsuperscript{2}} & \tt{$areas['other_area']\,m\textsuperscript{2}} & \tt{$areas['total_area']\,m\textsuperscript{2}} \\
\hline Faktor & \tt{#echo '%.10f' % $areas['flat_factor'] #} & \tt{#echo '%.10f' % $areas['other_factor'] #} & \\
\hline
#for $item in $overhead_items
\hline $item['category'] & \tt{#echo '%.2f' % $item['flat_part'] #\,\euro{}} & \tt{#echo '%.2f' % $item['other_part'] #\,\euro{}} & \tt{#echo '%.2f' % $item['sum'] #\,\euro{}} \\
#end for
\hline \multicolumn{4}{c}{ } \\
\hline Zwischensumme & \tt{#echo '%.2f' % $flat_sum #\,\euro{}} & \tt{#echo '%.2f' % $other_sum #\,\euro{}} & \tt{#echo '%.2f' % $total_sum #\,\euro{}} \\
\hline Umlageausfallwagnis & \tt{#echo '%.2f' % $umlage_ausfall_wagnis #\,\euro{}} & & \\
\hline Summe & \tt{#echo '%.2f' % $flat_sum_2 #\,\euro{}} & & \\
\hline \multicolumn{4}{c}{ } \\
\hline Anteil pro Monat und m\textsuperscript{2} & \tt{#echo '%.10f' % $part_by_montharea #\,\euro{}} & & \\
\hline
\end{tabular}
\end{document}

47
cli/db.py Normal file
View File

@ -0,0 +1,47 @@
import psycopg2
import psycopg2.extras
from loguru import logger
class NoDataFoundException(Exception): pass
class TooMuchDataFoundException(Exception): pass
def execDatabaseOperation(dbh, func, params):
cur = None
try:
with dbh.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
# params["params"] = [ v if not v=='' else None for v in params["params"] ]
logger.debug("edo: {}".format(str(params)))
return func(cur, params)
except psycopg2.Error as err:
raise Exception("Error when working on cursor: {}".format(err))
def _opGetMany(cursor, params):
#logger.warning(f"{params=}")
items = []
cursor.execute(params["statement"], params["params"])
for itemObj in cursor:
logger.debug("item received {}".format(str(itemObj)))
items.append(itemObj)
return items
def dbGetMany(dbh, params):
return execDatabaseOperation(dbh, _opGetMany, params)
def _opGetOne(cursor, params):
cursor.execute(params["statement"], params["params"])
itemObj = cursor.fetchone()
logger.debug(f"item received: {itemObj}")
if not itemObj:
raise NoDataFoundException
dummyObj = cursor.fetchone()
if dummyObj:
raise TooMuchDataFoundException
return itemObj
def dbGetOne(dbh, params):
return execDatabaseOperation(dbh, _opGetOne, params)

38
cli/fixDueDate.py Normal file
View File

@ -0,0 +1,38 @@
from loguru import logger
from db import dbGetMany, dbGetOne
def perform(dbh, params):
accountEntries = dbGetMany(
dbh,
{
"statement":
"""
select id, due_at from account_entry_t where due_at is not null
""",
"params": {}
}
)
for accountEntry in accountEntries:
id = accountEntry['id']
oldDueAt = accountEntry['due_at']
newDueAt = oldDueAt.replace(day=1)
logger.info(f"id: {id}, due_at: {oldDueAt} -> {newDueAt}")
fixedEntry = dbGetOne(
dbh,
{
"statement":
"""
UPDATE account_entry_t
SET due_at = %(dueAt)s
WHERE id = %(id)s
RETURNING *
""",
"params": {
"id": id,
"dueAt": newDueAt
}
}
)
logger.info("fixed")

82
cli/hv2cli.py Normal file
View File

@ -0,0 +1,82 @@
import psycopg2
import psycopg2.extras
from loguru import logger
import os
import configparser
import json
import argparse
import importlib
import sys
parser = argparse.ArgumentParser(description="hv2cli.py")
parser.add_argument('--config', '-c',
help="Config file, default is ./config/dbconfig.ini",
required=False,
default="./config/dbconfig.ini")
parser.add_argument('--operation', '-o',
help='Operation to perform.',
required=True)
parser.add_argument('--params', '-p',
help='JSON string with parameter for the selected operation, default: {}',
required=False,
default="{}")
parser.add_argument('--verbosity', '-v',
help='Minimal log level for output: DEBUG, INFO, WARNING, ..., default: DEBUG',
required=False,
default="DEBUG")
parser.add_argument('--nocolorize', '-n',
help='disable colored output (for cron)',
required=False,
action='store_true',
default=False)
args = parser.parse_args()
operation = args.operation
params = json.loads(args.params)
logLevel = args.verbosity
noColorize = args.nocolorize
DB_USER = ""
DB_PASS = ""
DB_HOST = ""
DB_NAME = ""
try:
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
except KeyError:
config = configparser.ConfigParser()
config.read(args.config)
DB_USER = config["database"]["user"]
DB_PASS = config["database"]["pass"]
DB_HOST = config["database"]["host"]
DB_NAME = config["database"]["name"]
logger.remove()
logger.add(sys.stderr, colorize=(not noColorize), level=logLevel)
dbh = None
try:
opMod = importlib.import_module(operation)
dbh = psycopg2.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME,
sslmode = 'require')
dbh.autocommit = False
with dbh:
opMod.perform(dbh, params)
except psycopg2.Error as err:
raise Exception("Error when working on the database: {}".format(err))
except Exception as err:
raise err
finally:
if dbh:
dbh.close()

74
cli/jahresabrechnung.tmpl Normal file
View File

@ -0,0 +1,74 @@
\documentclass[12pt]{dinbrief}
\usepackage{german}
\usepackage{eurosym}
\address{Nober Grundbesitz GbR\\
Eupenstr. 20\\
45259 Essen}
\backaddress{Nober Grundbesitz GbR, Eupenstr. 20, 45259 Essen}
\signature{Wolfgang Hottgenroth} %% Hier Unterschrift einfuegen
\nowindowrules
\writer{wh} %% Hier eigenen Namen oder Kuerzel einfuegen
\phone{0174}{3072474} %% Hier eigene Telefon einfuegen
\bottomtext{\sffamily\footnotesize Gesch"aftsf"uhrer: Wolfgang Hottgenroth, Robert Nober, Gregor Nober ---
EMail: hausverwaltung@nober.de\\
Bankverbindung: IBAN DE14 3605 0105 0001 5130 35, Sparkasse Essen}
\begin{document}
\pagenumbering{gobble}
\begin{letter}{$tenant['tenant_firstname'] $tenant['tenant_lastname']\\
#if $tenant['tenant_address1']
$tenant['tenant_address1']\\
#end if
#if $tenant['tenant_address2']
$tenant['tenant_address2']\\
#end if
#if $tenant['tenant_address3']
$tenant['tenant_address3']\\
#end if
$tenant['tenant_zip'] $tenant['tenant_city']}
\subject{Betriebskostenabrechnung $year}
\opening{$tenant['tenant_salutation'],}
f"ur die im vergangenen Jahr angefallenen Betriebskosten ergibt sich f"ur Sie folgende Abrechnung.
\begin{tabular}{|p{10cm}|r|}
\hline Jahr & \tt{$year} \\
\hline Haus & $tenant['house'] \\
\hline Betriebskosten gesamt & \tt{#echo '%.2f' % $total_overhead #\,\euro{}} \\
\hline Gesamt-Wohnfl"ache & \tt{$flat_area\,m\textsuperscript{2}} \\
\hline Ihre Wohnung & $tenant['flat'] \\
\hline Ihre Wohnfl"ache & \tt{$tenant['flat_area']\,m\textsuperscript{2}} \\
\hline Ihre Mietzeit & \tt{$tenant['rent_time']\,Monate} \\
\hline Ihr Betriebskostenanteil nach Fl"ache und Mietzeit & \tt{#echo '%.2f' % $receivable_overhead #\,\euro{}} \\
\hline Ihre Zahlungen & \tt{$tenant['paid_total']\,\euro{}} \\
\hline davon Anteil Miete & \tt{$tenant['receivable_fee']\,\euro{}} \\
\hline davon Anteil Betriebskostenvorauszahlung & \tt{$tenant['paid_overhead']\,\euro{}} \\
\hline
#if $unbalanced_overhead < 0
Zuwenig
#else
Zuviel
#end if
gezahlte Betriebskosten & \tt{#echo '%.2f' % $unbalanced_overhead_unsigned #\,\euro{}} \\
\hline
\end{tabular}
#if 1 < 0
Bitte "uberweisen Sie den Betrag von #echo '%.2f' % $unbalanced_overhead_unsigned #\,\euro{} kurzfristig auf mein Konto.
#else
Ich werde den Betrag von #echo '%.2f' % $unbalanced_overhead_unsigned #\,\euro{} in den n"achsten Tagen auf Ihr Konto "uberweisen.
#end if
Eine tabellarische "Ubersicht "uber die Zusammensetzung der Gesamt-Betriebskosten
finden Sie in der Anlage.
\closing{Mit freundlichen Gr"u"sen}
\end{letter}
\end{document}

5
cli/listTenants.py Normal file
View File

@ -0,0 +1,5 @@
from db import dbGetMany
def perform(dbh, params):
tenants = dbGetMany(dbh, { "statement": "SELECT * FROM tenant_t", "params": () })
print(tenants)

2
cli/utils.py Normal file
View File

@ -0,0 +1,2 @@
def getParam(params, attr, default):
return params[attr] if attr in params else default

View File

@ -3,6 +3,10 @@ from Cheetah.Template import Template
import glob import glob
import argparse import argparse
parser = argparse.ArgumentParser(description="generate.py") parser = argparse.ArgumentParser(description="generate.py")
parser.add_argument('--schema', '-s', parser.add_argument('--schema', '-s',
help='Schema file. Default: schema.json in the current folder.', help='Schema file. Default: schema.json in the current folder.',
@ -10,9 +14,9 @@ parser.add_argument('--schema', '-s',
default='./schema.json') default='./schema.json')
parser.add_argument('--template', '-t', parser.add_argument('--template', '-t',
help="""Template file, templates files must be named as the final output file help="""Template file, templates files must be named as the final output file
with an additional .tmpl extension. Default: all template files in the current folder.""", with an additional .tmpl extension. Default: all template files recursively from the current folder.""",
required=False, required=False,
default="*.tmpl") default="**/*.tmpl")
args = parser.parse_args() args = parser.parse_args()
@ -20,7 +24,8 @@ with open(args.schema) as schemaFile:
schema = json.load(schemaFile) schema = json.load(schemaFile)
for table in schema["tables"]: for table in schema["tables"]:
for column in table["columns"]: columns = table["columns"]
for column in columns:
if column["sqltype"] == 'serial': if column["sqltype"] == 'serial':
column["apitype"] = 'integer' column["apitype"] = 'integer'
column["jstype"] = 'number' column["jstype"] = 'number'
@ -33,14 +38,43 @@ for table in schema["tables"]:
elif column["sqltype"] == 'timestamp': elif column["sqltype"] == 'timestamp':
column["apitype"] = 'string' column["apitype"] = 'string'
column["jstype"] = 'string' column["jstype"] = 'string'
elif column["sqltype"] == 'boolean':
column["apitype"] = 'boolean'
column["jstype"] = 'boolean'
elif column["sqltype"].startswith('varchar'): elif column["sqltype"].startswith('varchar'):
column["apitype"] = 'string' column["apitype"] = 'string'
column["jstype"] = 'string' column["jstype"] = 'string'
elif column["sqltype"].startswith('numeric'): elif column["sqltype"].startswith('numeric'):
column["apitype"] = 'number' column["apitype"] = 'number'
column["jstype"] = 'number' column["jstype"] = 'number'
table["selectors"] = [ y["name"] for y in sorted([ x for x in columns if "selector" in x ], key=lambda x: x["selector"])]
for f in glob.glob(args.template): schema["GENERATED_SQL_COMMENT"] = """
-- ----------------------------------------
-- THIS FILE HAS BEEN GENERATED
-- DO NOT EDIT MANUALLY
-- ----------------------------------------
"""
schema["GENERATED_PYTHON_COMMENT"] = """
# -----------------------------------------
# THIS FILE HAS BEEN GENERATED
# DO NOT EDIT MANUALLY
# -----------------------------------------
"""
schema["GENERATED_YAML_COMMENT"] = """
# -----------------------------------------
# THIS FILE HAS BEEN GENERATED
# DO NOT EDIT MANUALLY
# -----------------------------------------
"""
schema["GENERATED_TS_COMMENT"] = """
// -----------------------------------------
// THIS FILE HAS BEEN GENERATED
// DO NOT EDIT MANUALLY
// -----------------------------------------
"""
for f in glob.glob(args.template, recursive=True):
print(f"process {f}") print(f"process {f}")
tmpl = Template(file=f, searchList=[schema]) tmpl = Template(file=f, searchList=[schema])
with open(f[:-5], 'w') as outFile: with open(f[:-5], 'w') as outFile:

View File

@ -1,5 +0,0 @@
#!/bin/bash
find . -name \*.tmpl -exec python generate.py --schema schema.json --template {} \;

16
readme.md Normal file
View File

@ -0,0 +1,16 @@
# Developing in HV2 umbrella project
## Tools and Templates
* all tools provide should work on Linux and on Windows, shell scripts are avoided
* templates files must be generated at development time, generated files must be put into the repository
* use ``generate.py`` in the umbrella project's root without any arguments to generate all template files in all subdirectories using the ``schema.json`` from the umbrella project's root
## Subprojects
### API
* to start the API set the required env variables (see ``ENV.tmp``) or the configuration files in the subdirectory ``./config`` and run ``test.py`` from the API subproject
* make sure these configuration files are not added to the repository, they contain secrets
* in the development environment the API will answer at ``http://localhost:8080``, the Swagger-UI is available at ``http://localhost:8080/ui``

View File

@ -3,15 +3,15 @@
{ {
"name": "account", "name": "account",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)", "notnull": true } { "name": "description", "sqltype": "varchar(128)", "notnull": true, "unique": true, "selector": 0 }
] ]
}, },
{ {
"name": "tenant", "name": "tenant",
"columns": [ "columns": [
{ "name": "salutation", "sqltype": "varchar(128)" }, { "name": "salutation", "sqltype": "varchar(128)" },
{ "name": "firstname", "sqltype": "varchar(128)" }, { "name": "firstname", "sqltype": "varchar(128)", "selector": 1 },
{ "name": "lastname", "sqltype": "varchar(128)" }, { "name": "lastname", "sqltype": "varchar(128)", "selector": 0 },
{ "name": "address1", "sqltype": "varchar(128)" }, { "name": "address1", "sqltype": "varchar(128)" },
{ "name": "address2", "sqltype": "varchar(128)" }, { "name": "address2", "sqltype": "varchar(128)" },
{ "name": "address3", "sqltype": "varchar(128)" }, { "name": "address3", "sqltype": "varchar(128)" },
@ -20,99 +20,142 @@
{ "name": "phone1", "sqltype": "varchar(64)" }, { "name": "phone1", "sqltype": "varchar(64)" },
{ "name": "phone2", "sqltype": "varchar(64)" }, { "name": "phone2", "sqltype": "varchar(64)" },
{ "name": "iban", "sqltype": "varchar(64)" }, { "name": "iban", "sqltype": "varchar(64)" },
{ "name": "account", "sqltype": "integer", "notnull": true, "foreignkey": true } { "name": "account", "sqltype": "integer", "notnull": true, "foreignkey": true, "immutable": true, "unique": true }
] ] ,
"tableConstraints": [
"unique(firstname, lastname)"
]
}, },
{ {
"name": "premise", "name": "premise",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 0, "unique": true },
{ "name": "street", "sqltype": "varchar(128)", "notnull": true }, { "name": "street", "sqltype": "varchar(128)", "notnull": true },
{ "name": "zip", "sqltype": "varchar(10)", "notnull": true }, { "name": "zip", "sqltype": "varchar(10)", "notnull": true },
{ "name": "city", "sqltype": "varchar(128)", "notnull": true } { "name": "city", "sqltype": "varchar(128)", "notnull": true },
{ "name": "minus_area", "sqltype": "numeric(10,2)", "notnull": true, "default": 0},
{ "name": "account", "sqltype": "integer", "notnull": true, "foreignkey": true, "immutable": true, "unique": true }
] ]
}, },
{ {
"name": "flat", "name": "flat",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 1 },
{ "name": "premise", "sqltype": "integer", "foreignkey": true }, { "name": "premise", "sqltype": "integer", "foreignkey": true, "selector": 0 },
{ "name": "area", "sqltype": "numeric(10,2)", "notnull": true }, { "name": "area", "sqltype": "numeric(10,2)", "notnull": true },
{ "name": "flat_no", "sqltype": "integer" } { "name": "flat_no", "sqltype": "integer" }
],
"tableConstraints": [
"unique(description, premise)"
] ]
}, },
{ {
"name": "overhead_advance", "name": "overhead_advance",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 0, "unique": true },
{ "name": "amount", "sqltype": "numeric(10,4)", "notnull": true }, { "name": "amount", "sqltype": "numeric(10,4)", "notnull": true, "immutable": true },
{ "name": "startdate", "sqltype": "date" }, { "name": "startdate", "sqltype": "date", "selector": 1, "immutable": true },
{ "name": "enddate", "sqltype": "date" } { "name": "enddate", "sqltype": "date" }
] ]
}, },
{ {
"name": "overhead_advance_flat_mapping", "name": "overhead_advance_flat_mapping",
"immutable": true,
"columns": [ "columns": [
{ "name": "overhead_advance", "sqltype": "integer", "notnull": true, "foreignkey": true }, { "name": "overhead_advance", "sqltype": "integer", "notnull": true, "foreignkey": true, "selector": 0 },
{ "name": "flat", "sqltype": "integer", "notnull": true, "foreignkey": true } { "name": "flat", "sqltype": "integer", "notnull": true, "foreignkey": true, "selector": 1 }
] ]
}, },
{ {
"name": "parking", "name": "parking",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 1 },
{ "name": "premise", "sqltype": "integer", "foreignkey": true } { "name": "premise", "sqltype": "integer", "foreignkey": true, "selector": 0 }
],
"tableConstraints": [
"unique(description, premise)"
] ]
}, },
{ {
"name": "commercial_premise", "name": "commercial_premise",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 1 },
{ "name": "premise", "sqltype": "integer", "foreignkey": true } { "name": "premise", "sqltype": "integer", "foreignkey": true, "selector": 0 },
{ "name": "area", "sqltype": "numeric(10,2)", "notnull": false }
],
"tableConstraints": [
"unique(description, premise)"
] ]
}, },
{ {
"name": "tenancy", "name": "tenancy",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 0, "unique": true },
{ "name": "tenant", "sqltype": "integer", "notnull": true, "foreignkey": true }, { "name": "tenant", "sqltype": "integer", "notnull": true, "foreignkey": true, "immutable": true },
{ "name": "flat", "sqltype": "integer", "notnull": false, "foreignkey": true }, { "name": "flat", "sqltype": "integer", "notnull": false, "foreignkey": true, "immutable": true },
{ "name": "parking", "sqltype": "integer", "notnull": false, "foreignkey": true }, { "name": "parking", "sqltype": "integer", "notnull": false, "foreignkey": true, "immutable": true },
{ "name": "commercial_premise", "sqltype": "integer", "notnull": false, "foreignkey": true }, { "name": "commercial_premise", "sqltype": "integer", "notnull": false, "foreignkey": true, "immutable": true },
{ "name": "startdate", "sqltype": "date", "notnull": true }, { "name": "startdate", "sqltype": "date", "notnull": true, "selector": 1, "immutable": true },
{ "name": "enddate", "sqltype": "date", "notnull": false } { "name": "enddate", "sqltype": "date", "notnull": false }
], ],
"tableConstraints": [ "tableConstraints": [
"constraint tenancy_only_one_object check ((flat is not null and parking is null and commercial_premise is null) or (flat is null and parking is not null and commercial_premise is null) or (flat is null and parking is null and commercial_premise is not null))" "check ((flat is not null and parking is null and commercial_premise is null) or (flat is null and parking is not null and commercial_premise is null) or (flat is null and parking is null and commercial_premise is not null))",
"unique(flat, parking, commercial_premise, startdate)"
] ]
}, },
{ {
"name": "fee", "name": "fee",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)" }, { "name": "description", "sqltype": "varchar(128)", "selector": 0, "unique": true },
{ "name": "amount", "sqltype": "numeric(10,2)", "notnull": true }, { "name": "amount", "sqltype": "numeric(10,2)", "notnull": true, "immutable": true },
{ "name": "fee_type", "sqltype": "varchar(10)", "notnull": true }, { "name": "fee_type", "sqltype": "varchar(10)", "notnull": true, "immutable": true },
{ "name": "startdate", "sqltype": "date" }, { "name": "startdate", "sqltype": "date", "selector": 1, "immutable": true },
{ "name": "enddate", "sqltype": "date" } { "name": "enddate", "sqltype": "date" }
], ],
"tableConstraints": [ "tableConstraints": [
"constraint fee_fee_type check (fee_type = 'per_area' or fee_type = 'total')" "check (fee_type = 'per_area' or fee_type = 'total')"
] ]
}, },
{ {
"name": "tenancy_fee_mapping", "name": "tenancy_fee_mapping",
"immutable": true,
"columns": [ "columns": [
{ "name": "tenancy", "sqltype": "integer", "notnull": true, "foreignkey": true }, { "name": "tenancy", "sqltype": "integer", "notnull": true, "foreignkey": true },
{ "name": "fee", "sqltype": "integer", "notnull": true, "foreignkey": true } { "name": "fee", "sqltype": "integer", "notnull": true, "foreignkey": true }
] ]
}, },
{ {
"name": "account_entry", "name": "account_entry_category",
"immutable": true,
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)", "notnull": true }, { "name": "description", "sqltype": "varchar(128)", "notnull": true, "selector": 0, "unique": true },
{ "name": "considerMinusArea", "sqltype": "boolean", "notnull": true, "default": true },
{ "name": "overhead_relevant", "sqltype": "boolean", "notnull": true, "default": true }
]
},
{
"name": "account_entry",
"immutable": true,
"columns": [
{ "name": "description", "sqltype": "varchar(1024)", "notnull": true },
{ "name": "account", "sqltype": "integer", "notnull": true, "foreignkey": true }, { "name": "account", "sqltype": "integer", "notnull": true, "foreignkey": true },
{ "name": "created_at", "sqltype": "timestamp", "notnull": true, "default": "now()" }, { "name": "created_at", "sqltype": "timestamp", "notnull": true, "default": "now()", "selector": 0 },
{ "name": "amount", "sqltype": "numeric(10,2)", "notnull": true } { "name": "fiscal_year", "sqltype": "integer", "notnull": true },
{ "name": "amount", "sqltype": "numeric(10,2)", "notnull": true },
{ "name": "document_no", "sqltype": "integer", "unique": true },
{ "name": "account_entry_category", "sqltype": "integer", "notnull": true, "foreignkey": true }
],
"tableConstraints": [
"unique(description, account, created_at)"
]
},
{
"name": "note",
"immutable": true,
"columns": [
{ "name": "created_at", "sqltype": "timestamp", "notnull": true, "default": "now()", "selector": 0 },
{ "name": "tenant", "sqltype": "integer", "notnull": true, "foreignkey": true },
{ "name": "note", "sqltype": "varchar(4096)", "notnull": true }
] ]
} }
] ]

6
schema/changes01.sql Normal file
View File

@ -0,0 +1,6 @@
alter table premise_t add column account integer;
alter table premise_t add constraint fk_premise_t_account foreign key (account) references account_t(id);
alter table premise_t add constraint uk_premise_t_account unique(account);
alter table account_entry_t add column document_no integer unique;
create sequence unique_number_s start with 1 increment by 1;

View File

@ -1,8 +1,18 @@
-- ----------------------------------------
-- THIS FILE HAS BEEN GENERATED
-- DO NOT EDIT MANUALLY
-- ----------------------------------------
CREATE TABLE account_t ( CREATE TABLE account_t (
id serial not null primary key id serial not null primary key
,description varchar(128) not null ,description varchar(128) not null unique
); );
GRANT SELECT, INSERT, UPDATE ON account_t TO hv2;
GRANT SELECT, UPDATE ON account_t_id_seq TO hv2;
CREATE TABLE tenant_t ( CREATE TABLE tenant_t (
id serial not null primary key id serial not null primary key
,salutation varchar(128) ,salutation varchar(128)
@ -16,86 +26,152 @@ CREATE TABLE tenant_t (
,phone1 varchar(64) ,phone1 varchar(64)
,phone2 varchar(64) ,phone2 varchar(64)
,iban varchar(64) ,iban varchar(64)
,account integer not null references account_t (id) ,account integer not null references account_t (id) unique
,unique(firstname, lastname)
); );
GRANT SELECT, INSERT, UPDATE ON tenant_t TO hv2;
GRANT SELECT, UPDATE ON tenant_t_id_seq TO hv2;
CREATE TABLE premise_t ( CREATE TABLE premise_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128) unique
,street varchar(128) not null ,street varchar(128) not null
,zip varchar(10) not null ,zip varchar(10) not null
,city varchar(128) not null ,city varchar(128) not null
,minus_area numeric(10,2) not null default 0
,account integer not null references account_t (id) unique
); );
GRANT SELECT, INSERT, UPDATE ON premise_t TO hv2;
GRANT SELECT, UPDATE ON premise_t_id_seq TO hv2;
CREATE TABLE flat_t ( CREATE TABLE flat_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128)
,premise integer references premise_t (id) ,premise integer references premise_t (id)
,area numeric(10,2) not null ,area numeric(10,2) not null
,flat_no integer ,flat_no integer
,unique(description, premise)
); );
GRANT SELECT, INSERT, UPDATE ON flat_t TO hv2;
GRANT SELECT, UPDATE ON flat_t_id_seq TO hv2;
CREATE TABLE overhead_advance_t ( CREATE TABLE overhead_advance_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128) unique
,amount numeric(10,4) not null ,amount numeric(10,4) not null
,startdate date ,startdate date
,enddate date ,enddate date
); );
GRANT SELECT, INSERT, UPDATE ON overhead_advance_t TO hv2;
GRANT SELECT, UPDATE ON overhead_advance_t_id_seq TO hv2;
CREATE TABLE overhead_advance_flat_mapping_t ( CREATE TABLE overhead_advance_flat_mapping_t (
id serial not null primary key id serial not null primary key
,overhead_advance integer not null references overhead_advance_t (id) ,overhead_advance integer not null references overhead_advance_t (id)
,flat integer not null references flat_t (id) ,flat integer not null references flat_t (id)
); );
GRANT SELECT, INSERT ON overhead_advance_flat_mapping_t TO hv2;
GRANT SELECT, UPDATE ON overhead_advance_flat_mapping_t_id_seq TO hv2;
CREATE TABLE parking_t ( CREATE TABLE parking_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128)
,premise integer references premise_t (id) ,premise integer references premise_t (id)
,unique(description, premise)
); );
GRANT SELECT, INSERT, UPDATE ON parking_t TO hv2;
GRANT SELECT, UPDATE ON parking_t_id_seq TO hv2;
CREATE TABLE commercial_premise_t ( CREATE TABLE commercial_premise_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128)
,premise integer references premise_t (id) ,premise integer references premise_t (id)
,area numeric(10,2)
,unique(description, premise)
); );
GRANT SELECT, INSERT, UPDATE ON commercial_premise_t TO hv2;
GRANT SELECT, UPDATE ON commercial_premise_t_id_seq TO hv2;
CREATE TABLE tenancy_t ( CREATE TABLE tenancy_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128) unique
,tenant integer not null references tenant_t (id) ,tenant integer not null references tenant_t (id)
,flat integer references flat_t (id) ,flat integer references flat_t (id)
,parking integer references parking_t (id) ,parking integer references parking_t (id)
,commercial_premise integer references commercial_premise_t (id) ,commercial_premise integer references commercial_premise_t (id)
,startdate date not null ,startdate date not null
,enddate date ,enddate date
,constraint tenancy_only_one_object check ((flat is not null and parking is null and commercial_premise is null) or (flat is null and parking is not null and commercial_premise is null) or (flat is null and parking is null and commercial_premise is not null)) ,check ((flat is not null and parking is null and commercial_premise is null) or (flat is null and parking is not null and commercial_premise is null) or (flat is null and parking is null and commercial_premise is not null))
,unique(flat, parking, commercial_premise, startdate)
); );
GRANT SELECT, INSERT, UPDATE ON tenancy_t TO hv2;
GRANT SELECT, UPDATE ON tenancy_t_id_seq TO hv2;
CREATE TABLE fee_t ( CREATE TABLE fee_t (
id serial not null primary key id serial not null primary key
,description varchar(128) ,description varchar(128) unique
,amount numeric(10,2) not null ,amount numeric(10,2) not null
,fee_type varchar(10) not null ,fee_type varchar(10) not null
,startdate date ,startdate date
,enddate date ,enddate date
,constraint fee_fee_type check (fee_type = 'per_area' or fee_type = 'total') ,check (fee_type = 'per_area' or fee_type = 'total')
); );
GRANT SELECT, INSERT, UPDATE ON fee_t TO hv2;
GRANT SELECT, UPDATE ON fee_t_id_seq TO hv2;
CREATE TABLE tenancy_fee_mapping_t ( CREATE TABLE tenancy_fee_mapping_t (
id serial not null primary key id serial not null primary key
,tenancy integer not null references tenancy_t (id) ,tenancy integer not null references tenancy_t (id)
,fee integer not null references fee_t (id) ,fee integer not null references fee_t (id)
); );
CREATE TABLE account_entry_t ( GRANT SELECT, INSERT ON tenancy_fee_mapping_t TO hv2;
GRANT SELECT, UPDATE ON tenancy_fee_mapping_t_id_seq TO hv2;
CREATE TABLE account_entry_category_t (
id serial not null primary key id serial not null primary key
,description varchar(128) not null ,description varchar(128) not null unique
,account integer not null references account_t (id) ,considerMinusArea boolean not null default True
,created_at timestamp not null default now() ,overhead_relevant boolean not null default True
,amount numeric(10,2) not null
); );
GRANT SELECT, INSERT ON account_entry_category_t TO hv2;
GRANT SELECT, UPDATE ON account_entry_category_t_id_seq TO hv2;
CREATE TABLE account_entry_t (
id serial not null primary key
,description varchar(1024) not null
,account integer not null references account_t (id)
,created_at timestamp not null default now()
,fiscal_year integer not null
,amount numeric(10,2) not null
,document_no integer unique
,account_entry_category integer not null references account_entry_category_t (id)
,unique(description, account, created_at)
);
GRANT SELECT, INSERT ON account_entry_t TO hv2;
GRANT SELECT, UPDATE ON account_entry_t_id_seq TO hv2;
CREATE TABLE note_t (
id serial not null primary key
,created_at timestamp not null default now()
,tenant integer not null references tenant_t (id)
,note varchar(4096) not null
);
GRANT SELECT, INSERT ON note_t TO hv2;
GRANT SELECT, UPDATE ON note_t_id_seq TO hv2;

View File

@ -1,3 +1,5 @@
$GENERATED_SQL_COMMENT
#for $table in $tables #for $table in $tables
CREATE TABLE ${table.name}_t ( CREATE TABLE ${table.name}_t (
id serial not null primary key id serial not null primary key
@ -12,6 +14,9 @@ CREATE TABLE ${table.name}_t (
#if (('foreignkey' in $column) and $column.foreignkey) #if (('foreignkey' in $column) and $column.foreignkey)
references ${column.name}_t (id) #slurp references ${column.name}_t (id) #slurp
#end if #end if
#if (('unique' in $column) and $column.unique)
unique #slurp
#end if
#if ('default' in $column) #if ('default' in $column)
default $column.default #slurp default $column.default #slurp
#end if #end if
@ -24,6 +29,14 @@ CREATE TABLE ${table.name}_t (
#end if #end if
); );
GRANT SELECT, INSERT#slurp
#if (('immutable' not in $table) or (not $table.immutable))
, UPDATE#slurp
#end if
ON ${table.name}_t TO hv2;
GRANT SELECT, UPDATE ON ${table.name}_t_id_seq TO hv2;
#end for #end for

View File

@ -460,6 +460,141 @@
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@angular/localize": {
"version": "11.0.9",
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-11.0.9.tgz",
"integrity": "sha512-5NtyqCcBN8G6muXpyrHuVHTdD9slpyUAl6vF6NbyejgVeuV35wPXwIa+3qauPiVlFGEBQpn/uKAU57mVGm8WUg==",
"dev": true,
"requires": {
"@babel/core": "7.8.3",
"glob": "7.1.2",
"yargs": "^16.1.1"
},
"dependencies": {
"@babel/core": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz",
"integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.8.3",
"@babel/helpers": "^7.8.3",
"@babel/parser": "^7.8.3",
"@babel/template": "^7.8.3",
"@babel/traverse": "^7.8.3",
"@babel/types": "^7.8.3",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.0",
"lodash": "^4.17.13",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
}
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true
},
"yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
}
},
"yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true
}
}
},
"@angular/material": { "@angular/material": {
"version": "11.2.13", "version": "11.2.13",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-11.2.13.tgz", "resolved": "https://registry.npmjs.org/@angular/material/-/material-11.2.13.tgz",
@ -468,6 +603,14 @@
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@angular/material-moment-adapter": {
"version": "11.2.13",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-11.2.13.tgz",
"integrity": "sha512-KDD7QcfePpwvPG3HYy4kA8Ju222fUZeoyGXxFVE98KgaIIHrTRIGPFLHbpzxIl+AYVgJcW2OiIXKLI09FiYirg==",
"requires": {
"tslib": "^2.0.0"
}
},
"@angular/platform-browser": { "@angular/platform-browser": {
"version": "11.0.9", "version": "11.0.9",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.0.9.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.0.9.tgz",
@ -7618,6 +7761,11 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -9,7 +9,6 @@
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"generate": "python ../helpers/hv2-api/generate.py -s ../helpers/hv2-api/schema.json -t ./src/app/*.tmpl" "generate": "python ../helpers/hv2-api/generate.py -s ../helpers/hv2-api/schema.json -t ./src/app/*.tmpl"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@ -20,10 +19,12 @@
"@angular/core": "~11.0.6", "@angular/core": "~11.0.6",
"@angular/forms": "~11.0.6", "@angular/forms": "~11.0.6",
"@angular/material": "^11.2.13", "@angular/material": "^11.2.13",
"@angular/material-moment-adapter": "^11.2.13",
"@angular/platform-browser": "~11.0.6", "@angular/platform-browser": "~11.0.6",
"@angular/platform-browser-dynamic": "~11.0.6", "@angular/platform-browser-dynamic": "~11.0.6",
"@angular/router": "~11.0.6", "@angular/router": "~11.0.6",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"moment": "^2.29.1",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"tslib": "^2.0.0", "tslib": "^2.0.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"
@ -32,6 +33,7 @@
"@angular-devkit/build-angular": "~0.1100.6", "@angular-devkit/build-angular": "~0.1100.6",
"@angular/cli": "~11.0.6", "@angular/cli": "~11.0.6",
"@angular/compiler-cli": "~11.0.6", "@angular/compiler-cli": "~11.0.6",
"@angular/localize": "^11.0.9",
"@types/jasmine": "~3.6.0", "@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",

View File

@ -0,0 +1,37 @@
table {
width: 75%;
border-spacing: 20px;
}
.mat-table {
border-spacing: 20px;
}
.spacer {
flex: 1 1 auto;
}
#addEntryfield {
margin-right: 15px;
}
#divider {
margin-top: 20px;
}
#firstblock {
margin-bottom: 20px;
}
#secondblock {
margin-top: 20px;
}
.rightaligned {
text-align: right;
}
.large {
font-size: large;
}

View File

@ -0,0 +1,66 @@
<div id="firstBlock">
<form (ngSubmit)="addAccountEntry(accountEntryForm)" #accountEntryForm="ngForm">
<mat-form-field appearance="outline" id="addEntryfield">
<mat-label>Datum</mat-label>
<input matInput ngModel name="createdAt" [matDatepicker]="createdAtPicker"/>
<mat-datepicker-toggle matSuffix [for]="createdAtPicker"></mat-datepicker-toggle>
<mat-datepicker #createdAtPicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Jahr</mat-label>
<input matInput type="number" name="fiscalYear" [formControl]="presetFiscalYear" ngModel/>
</mat-form-field>
<mat-form-field appearance="outline" *ngIf="!shallBeRentPayment">
<mat-label>Kategorie</mat-label>
<mat-select ngModel name="category" [disabled]="shallBeRentPayment">
<mat-option *ngFor="let p of accountEntryCategories" [value]="p.id">{{p.description}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Betrag (€)</mat-label>
<input matInput type="number" name="amount" ngModel/>
</mat-form-field>
<mat-form-field appearance="outline" *ngIf="!shallBeRentPayment">
<mat-label>Beschreibung</mat-label>
<input matInput name="description" [disabled]="shallBeRentPayment" ngModel/>
</mat-form-field>
<button #addAccountEntryButton type="submit" mat-raised-button color="primary">Buchung speichern</button>
</form>
</div>
<div class="large">
Saldo: {{saldo?.saldo | number:'1.2-2'}} €
</div>
<div id="secondBlock">
<table mat-table [dataSource]="accountEntriesDataSource" #zftable>
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef>Datum</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.created_at | date}}</td>
</ng-container>
<ng-container matColumnDef="fiscalYear">
<th mat-header-cell *matHeaderCellDef>Jahr</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.fiscal_year}}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.description}}</td>
</ng-container>
<ng-container matColumnDef="document_no">
<th mat-header-cell *matHeaderCellDef>Belegnummer</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.document_no}}</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>Betrag</th>
<td mat-cell *matCellDef="let element" class="rightaligned">{{element.rawAccountEntry.amount | number:'1.2-2'}} €</td>
</ng-container>
<ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>Kategorie</th>
<td mat-cell *matCellDef="let element">{{element.accountEntryCategory}}</td>
</ng-container>
<ng-container matColumnDef="overhead_relevant">
<th mat-header-cell *matHeaderCellDef>BK relevant</th>
<td mat-cell *matCellDef="let element">{{element.overheadRelevant}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="accountEntriesDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: accountEntriesDisplayedColumns;"></tr>
</table>
</div>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AccountComponent } from './account.component';
describe('AccountComponent', () => {
let component: AccountComponent;
let fixture: ComponentFixture<AccountComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AccountComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AccountComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,167 @@
import { ViewFlags } from '@angular/compiler/src/core';
import { Component, Input, OnInit, OnChanges, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { MatExpansionPanel } from '@angular/material/expansion';
import { MatTableDataSource } from '@angular/material/table';
import { AccountEntryCategoryService, AccountEntryService, AccountService } from '../data-object-service';
import { Account, AccountEntry, AccountEntryCategory, NULL_AccountEntry } from '../data-objects';
import { ErrorDialogService } from '../error-dialog.service';
import { ExtApiService } from '../ext-data-object-service';
import { Saldo, UniqueNumber } from '../ext-data-objects';
import { MessageService } from '../message.service';
interface DN_AccountEntry {
rawAccountEntry: AccountEntry
accountEntryCategory: string
overheadRelevant: boolean
}
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css']
})
export class AccountComponent implements OnInit {
@Input() selectedAccountId: number
@Input() shallBeRentPayment: boolean
@ViewChild('addAccountEntryButton') addAccountEntryButton: MatButton
account: Account
accountEntries: DN_AccountEntry[]
accountEntriesDataSource: MatTableDataSource<DN_AccountEntry>
accountEntriesDisplayedColumns: string[] = [ "description", "document_no", "amount", "createdAt", "fiscalYear", "category", "overhead_relevant" ]
saldo: Saldo
accountEntryCategories: AccountEntryCategory[]
accountEntryCategoriesMap: Map<number, AccountEntryCategory>
accountEntryCategoriesInverseMap: Map<string, AccountEntryCategory>
presetFiscalYear: FormControl
constructor(
private accountService: AccountService,
private accountEntryService: AccountEntryService,
private extApiService: ExtApiService,
private accountEntryCategoryService: AccountEntryCategoryService,
private messageService: MessageService,
private errorDialogService: ErrorDialogService
) { }
async getAccount(): Promise<void> {
try {
if (this.selectedAccountId) {
this.messageService.add(`Trying to load account ${this.selectedAccountId} and entries`)
this.account = await this.accountService.getAccount(this.selectedAccountId)
this.messageService.add(`Account: ${JSON.stringify(this.account, undefined, 4)}`)
this.getAccountEntries()
}
} catch (err) {
this.messageService.add(`Error in getAccount: ${JSON.stringify(err, undefined, 4)}`)
}
}
async getAccountEntries(): Promise<void> {
try {
const rawAccountEntries = await this.accountEntryService.getAccountEntrysByAccount(this.selectedAccountId)
rawAccountEntries.reverse()
this.messageService.add(`AccountEntries: ${JSON.stringify(rawAccountEntries, undefined, 4)}`)
this.accountEntries = []
for (let f of rawAccountEntries) {
this.accountEntries.push({
rawAccountEntry: f,
accountEntryCategory: this.accountEntryCategoriesMap.get(f.account_entry_category).description,
overheadRelevant: this.accountEntryCategoriesMap.get(f.account_entry_category).overhead_relevant
})
}
this.accountEntriesDataSource = new MatTableDataSource<DN_AccountEntry>(this.accountEntries)
this.saldo = await this.extApiService.getAccountSaldo(this.selectedAccountId)
} catch (err) {
throw err
this.messageService.add(`Error in getAccountEntries: ${JSON.stringify(err, undefined, 4)}`)
}
}
async addAccountEntry(formData: any): Promise<void> {
try {
this.addAccountEntryButton.disabled = true
this.messageService.add(`${JSON.stringify(formData.value, undefined, 4)}`)
let uniquenumber: UniqueNumber = await this.extApiService.getUniqueNumber();
this.messageService.add(`Got unique number as document_no: ${uniquenumber.number}`)
let newAccountEntry: AccountEntry = {
description: formData.value.description,
account: this.account.id,
created_at: formData.value.createdAt,
fiscal_year: this.presetFiscalYear.value,
amount: formData.value.amount,
id: 0,
document_no: uniquenumber.number,
account_entry_category: 0
}
if (this.shallBeRentPayment) {
newAccountEntry.account_entry_category = this.accountEntryCategoriesInverseMap.get('Mietzahlung').id
newAccountEntry.description = "Miete"
this.messageService.add(`shall be rentpayment, category is ${newAccountEntry.account_entry_category}`)
} else {
newAccountEntry.account_entry_category = formData.value.category
this.messageService.add(`category is ${newAccountEntry.account_entry_category}`)
}
this.messageService.add(`addAccountEntry: ${ JSON.stringify(newAccountEntry, undefined, 4) }`)
newAccountEntry = await this.accountEntryService.postAccountEntry(newAccountEntry)
this.messageService.add(`New accountEntry created: ${newAccountEntry.id}`)
formData.reset()
this.getAccountEntries()
} catch (err) {
this.messageService.add(`Error in addAccountEntry: ${JSON.stringify(err, undefined, 4)}`)
this.errorDialogService.openDialog('AccountComponent', 'addAccountEntry', JSON.stringify(err, undefined, 4))
} finally {
this.addAccountEntryButton.disabled = false
}
}
async getAccountEntryCategories(): Promise<void> {
try {
this.accountEntryCategories = await this.accountEntryCategoryService.getAccountEntryCategorys()
this.accountEntryCategoriesMap = new Map<number, AccountEntryCategory>()
this.accountEntryCategoriesInverseMap = new Map<string, AccountEntryCategory>()
for (let p of this.accountEntryCategories) {
this.accountEntryCategoriesMap.set(p.id, p)
this.accountEntryCategoriesInverseMap.set(p.description, p)
}
this.messageService.add(`getAccountEntryCategories: ${JSON.stringify(this.accountEntryCategories, undefined, 4)}`)
} catch (err) {
this.messageService.add(`Error in getAccountEntryCategories: ${JSON.stringify(err, undefined, 4)}`)
}
}
private async init(): Promise<void> {
let currentDate = new Date()
let y = currentDate.getFullYear().toString()
this.presetFiscalYear = new FormControl(y)
this.messageService.add(`AccountComponent.init, account: ${this.selectedAccountId}`)
this.getAccount()
await this.getAccountEntryCategories()
}
ngOnInit(): void {
this.init()
}
ngOnChanges(): void {
this.init()
}
}

View File

@ -3,14 +3,53 @@ import { RouterModule, Routes } from '@angular/router';
import { AuthGuardService } from './auth-guard.service'; import { AuthGuardService } from './auth-guard.service';
import { LoginComponent } from './login/login.component'; import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component'; import { LogoutComponent } from './logout/logout.component';
import { TestOutputComponent } from './test-output/test-output.component'; import { MyTenantsComponent } from './my-tenants/my-tenants.component';
import { MyPremisesComponent } from './my-premises/my-premises.component';
import { MyFlatsComponent } from './my-flats/my-flats.component';
import { MyParkingsComponent } from './my-parkings/my-parkings.component';
import { MyCommercialUnitsComponent } from './my-commercial-units/my-commercial-units.component';
import { TenantDetailsComponent } from './tenant-details/tenant-details.component';
import { PremiseDetailsComponent } from './premise-details/premise-details.component';
import { FlatDetailsComponent } from './flat-details/flat-details.component';
import { ParkingDetailsComponent } from './parking-details/parking-details.component';
import { CommercialUnitDetailsComponent } from './commercial-unit-details/commercial-unit-details.component';
import { OverheadAdvanceListComponent } from './overhead-advance-list/overhead-advance-list.component';
import { OverheadAdvanceDetailsComponent } from './overhead-advance-details/overhead-advance-details.component';
import { FeeListComponent } from './fee-list/fee-list.component';
import { FeeDetailsComponent } from './fee-details/fee-details.component';
import { EnterPaymentComponent } from './enter-payment/enter-payment.component';
import { HomeComponent } from './home/home.component';
import { LedgerComponent } from './ledger/ledger.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'test', component: TestOutputComponent, canActivate: [ AuthGuardService ] }, { path: 'tenants', component: MyTenantsComponent, canActivate: [ AuthGuardService ] },
{ path: 'premises', component: MyPremisesComponent, canActivate: [ AuthGuardService ] },
{ path: 'flats', component: MyFlatsComponent, canActivate: [ AuthGuardService ] },
{ path: 'parkings', component: MyParkingsComponent, canActivate: [ AuthGuardService ] },
{ path: 'commercialunits', component: MyCommercialUnitsComponent, canActivate: [ AuthGuardService ] },
{ path: 'tenant/:id', component: TenantDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'tenant', component: TenantDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'premise/:id', component: PremiseDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'premise', component: PremiseDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'flat/:id', component: FlatDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'flat', component: FlatDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'parking/:id', component: ParkingDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'parking', component: ParkingDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'commercialunit/:id', component: CommercialUnitDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'commercialunit', component: CommercialUnitDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'overheadadvances', component: OverheadAdvanceListComponent, canActivate: [ AuthGuardService ] },
{ path: 'overheadadvance/:id', component: OverheadAdvanceDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'overheadadvance', component: OverheadAdvanceDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'fees', component: FeeListComponent, canActivate: [ AuthGuardService ] },
{ path: 'fee/:id', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'fee', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'enterPayment', component: EnterPaymentComponent, canActivate: [ AuthGuardService ] },
{ path: 'ledger', component: LedgerComponent, canActivate: [ AuthGuardService ] },
{ path: 'home', component: HomeComponent },
{ path: 'logout', component: LogoutComponent }, { path: 'logout', component: LogoutComponent },
{ path: 'login', component: LoginComponent } { path: 'login', component: LoginComponent },
{ path: '', pathMatch: 'full', redirectTo: 'home' }
] ]
@NgModule({ @NgModule({

View File

@ -1,5 +1,7 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { LOCALE_ID, NgModule } from '@angular/core';
import localeDe from '@angular/common/locales/de';
import { registerLocaleData } from '@angular/common';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@ -21,7 +23,35 @@ import { LogoutComponent } from './logout/logout.component';
import { LoginComponent } from './login/login.component'; import { LoginComponent } from './login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field'
import { MyTenantsComponent } from './my-tenants/my-tenants.component';
import { MatTableModule } from '@angular/material/table';
import { MyPremisesComponent } from './my-premises/my-premises.component';
import { MyFlatsComponent } from './my-flats/my-flats.component';
import { MyParkingsComponent } from './my-parkings/my-parkings.component';
import { MyCommercialUnitsComponent } from './my-commercial-units/my-commercial-units.component';
import { TenantDetailsComponent } from './tenant-details/tenant-details.component';
import { PremiseDetailsComponent } from './premise-details/premise-details.component';
import { FlatDetailsComponent } from './flat-details/flat-details.component';
import { MatSelectModule } from '@angular/material/select';
import { ParkingDetailsComponent } from './parking-details/parking-details.component';
import { CommercialUnitDetailsComponent } from './commercial-unit-details/commercial-unit-details.component';
import { OverheadAdvanceListComponent } from './overhead-advance-list/overhead-advance-list.component';
import { OverheadAdvanceDetailsComponent } from './overhead-advance-details/overhead-advance-details.component'
import { MatDatepickerModule } from '@angular/material/datepicker'
import { MatNativeDateModule } from '@angular/material/core';
import { FeeListComponent } from './fee-list/fee-list.component';
import { FeeDetailsComponent } from './fee-details/fee-details.component';
import { MatExpansionModule } from '@angular/material/expansion';
import { AccountComponent } from './account/account.component';
import { NoteComponent } from './note/note.component'
import { MatMomentDateModule, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';
import { EnterPaymentComponent } from './enter-payment/enter-payment.component';
import { HomeComponent } from './home/home.component';
import { LedgerComponent } from './ledger/ledger.component';
import { ErrorDialogComponent } from './error-dialog/error-dialog.component'
registerLocaleData(localeDe)
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -30,7 +60,27 @@ import { MatInputModule } from '@angular/material/input';
MessagesComponent, MessagesComponent,
TestOutputComponent, TestOutputComponent,
LogoutComponent, LogoutComponent,
LoginComponent LoginComponent,
MyTenantsComponent,
MyPremisesComponent,
MyFlatsComponent,
MyParkingsComponent,
MyCommercialUnitsComponent,
TenantDetailsComponent,
PremiseDetailsComponent,
FlatDetailsComponent,
ParkingDetailsComponent,
CommercialUnitDetailsComponent,
OverheadAdvanceListComponent,
OverheadAdvanceDetailsComponent,
FeeListComponent,
FeeDetailsComponent,
AccountComponent,
NoteComponent,
EnterPaymentComponent,
HomeComponent,
LedgerComponent,
ErrorDialogComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -46,11 +96,22 @@ import { MatInputModule } from '@angular/material/input';
AppRoutingModule, AppRoutingModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule MatTableModule,
MatInputModule,
MatFormFieldModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatExpansionModule
],
exports: [
MatMomentDateModule
], ],
providers: [ providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthHandlerInterceptor, multi: true } { provide: HTTP_INTERCEPTORS, useClass: AuthHandlerInterceptor, multi: true },
{ provide: LOCALE_ID, useValue: 'de' },
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -8,6 +8,8 @@ import {
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
import { TokenService } from './token.service'; import { TokenService } from './token.service';
import { serviceBaseUrl } from './config';
@Injectable() @Injectable()
export class AuthHandlerInterceptor implements HttpInterceptor { export class AuthHandlerInterceptor implements HttpInterceptor {
@ -15,8 +17,12 @@ export class AuthHandlerInterceptor implements HttpInterceptor {
constructor(private tokenService: TokenService, private messageService: MessageService) {} constructor(private tokenService: TokenService, private messageService: MessageService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const token = localStorage.getItem(TokenService.Id_Token_Key) const token = localStorage.getItem(TokenService.Id_AuthToken_Key)
if (request.url.includes('api.hv.nober.de') && token) { if (request.url.includes(serviceBaseUrl) && token) {
this.messageService.add("start refresh of tokens")
this.tokenService.refresh()
this.messageService.add("api request intercepted")
const clone = request.clone({ const clone = request.clone({
setHeaders: { Authorization: `Bearer ${token}`} setHeaders: { Authorization: `Bearer ${token}`}
}) })

View File

@ -0,0 +1,35 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
{{commercialPremise?.description}} {{premise?.description}}
</mat-card-title>
<mat-card-subtitle>
ID: {{commercialPremise?.id}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>
<form (ngSubmit)="saveCommercialPremise()">
<div>
<mat-form-field appearance="outline">
<mat-label>Beschreibung</mat-label>
<input matInput name="description" [(ngModel)]="commercialPremise.description"/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Haus</mat-label>
<mat-select [(ngModel)]="commercialPremise.premise" name="premise">
<mat-option *ngFor="let p of premises" [value]="p.id">{{p.description}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Fläche</mat-label>
<input type="number" matInput name="area" [(ngModel)]="commercialPremise.area"/>
</mat-form-field>
</div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommercialUnitDetailsComponent } from './commercial-unit-details.component';
describe('CommercialUnitDetailsComponent', () => {
let component: CommercialUnitDetailsComponent;
let fixture: ComponentFixture<CommercialUnitDetailsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CommercialUnitDetailsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CommercialUnitDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,79 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { ActivatedRoute, Router } from '@angular/router';
import { CommercialPremiseService, PremiseService } from '../data-object-service';
import { NULL_CommercialPremise, NULL_Premise, CommercialPremise, Premise } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-commercial-unit-details',
templateUrl: './commercial-unit-details.component.html',
styleUrls: ['./commercial-unit-details.component.css']
})
export class CommercialUnitDetailsComponent implements OnInit {
@ViewChild('submitButton') submitButton: MatButton
commercialPremise: CommercialPremise = NULL_CommercialPremise
premise: Premise = NULL_Premise
premises: Premise[]
constructor(
private commercialPremiseService: CommercialPremiseService,
private premiseService: PremiseService,
private messageService: MessageService,
private route: ActivatedRoute,
private router: Router
) { }
async getCommercialPremise(): Promise<void> {
try {
const id = +this.route.snapshot.paramMap.get('id')
if (id != 0) {
this.commercialPremise = await this.commercialPremiseService.getCommercialPremise(id)
this.premise = await this.premiseService.getPremise(this.commercialPremise.premise)
}
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async getPremises(): Promise<void> {
try {
this.messageService.add("Trying to load premises")
this.premises = await this.premiseService.getPremises()
this.messageService.add("Premises loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async saveCommercialPremise() {
try {
this.submitButton.disabled = true
this.messageService.add(`saveCommercialPremise: ${ JSON.stringify(this.commercialPremise, undefined, 4) }`)
if (this.commercialPremise.id == 0) {
this.messageService.add("about to insert new commercialPremise")
this.commercialPremise = await this.commercialPremiseService.postCommercialPremise(this.commercialPremise)
this.messageService.add(`Successfully added commercialPremise with id ${this.commercialPremise.id}`)
} else {
this.messageService.add("about to update existing commercialPremise")
this.commercialPremise = await this.commercialPremiseService.putCommercialPremise(this.commercialPremise)
this.messageService.add(`Successfully changed commercialPremise with id ${this.commercialPremise.id}`)
}
this.router.navigate(['/commercialunits'])
} finally {
this.submitButton.disabled = false
}
}
ngOnInit(): void {
this.getPremises()
this.getCommercialPremise()
}
}

View File

@ -1,2 +1,5 @@
export const serviceBaseUrl = "https://api.hv.nober.de"; export const serviceBaseUrl = "https://api.hv.nober.de";
// export const serviceBaseUrl = "http://172.16.10.38:5000"; // export const serviceBaseUrl = "http://172.16.10.38:5000";
// export const serviceBaseUrl = "http://localhost:8080"
export const authserviceBaseUrl = "https://authservice.hottis.de"
export const applicationId = "hv2"

View File

@ -1,6 +1,13 @@
// -----------------------------------------
// THIS FILE HAS BEEN GENERATED
// DO NOT EDIT MANUALLY
// -----------------------------------------
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
import { serviceBaseUrl } from './config'; import { serviceBaseUrl } from './config';
@ -17,124 +24,499 @@ import { CommercialPremise } from './data-objects';
import { Tenancy } from './data-objects'; import { Tenancy } from './data-objects';
import { Fee } from './data-objects'; import { Fee } from './data-objects';
import { TenancyFeeMapping } from './data-objects'; import { TenancyFeeMapping } from './data-objects';
import { AccountEntryCategory } from './data-objects';
import { AccountEntry } from './data-objects'; import { AccountEntry } from './data-objects';
import { Note } from './data-objects';
@Injectable({
providedIn: 'root'
})
@Injectable({ providedIn: 'root' })
export class AccountService { export class AccountService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getAccount(): Promise<Account> { async getAccounts(): Promise<Account[]> {
this.messageService.add(`AccountService: fetch data`); this.messageService.add(`AccountService: get data`);
return this.http.get<Account>(`${serviceBaseUrl}/v1/account`).toPromise() return this.http.get<Account[]>(`${serviceBaseUrl}/v1/accounts`).toPromise()
} }
async getAccount(id: number): Promise<Account> {
this.messageService.add(`AccountService: get data for ${id}`);
return this.http.get<Account>(`${serviceBaseUrl}/v1/accounts/${id}`).toPromise()
}
async postAccount(item: Account): Promise<Account> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`AccountService: post data for ${itemStr}`);
return this.http.post<Account>(`${serviceBaseUrl}/v1/accounts`, item).toPromise()
}
async putAccount(item: Account): Promise<Account> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`AccountService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Account>(`${serviceBaseUrl}/v1/accounts/${id}`, item).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class TenantService { export class TenantService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getTenant(): Promise<Tenant> { async getTenants(): Promise<Tenant[]> {
this.messageService.add(`TenantService: fetch data`); this.messageService.add(`TenantService: get data`);
return this.http.get<Tenant>(`${serviceBaseUrl}/v1/tenant`).toPromise() return this.http.get<Tenant[]>(`${serviceBaseUrl}/v1/tenants`).toPromise()
} }
async getTenant(id: number): Promise<Tenant> {
this.messageService.add(`TenantService: get data for ${id}`);
return this.http.get<Tenant>(`${serviceBaseUrl}/v1/tenants/${id}`).toPromise()
}
async postTenant(item: Tenant): Promise<Tenant> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`TenantService: post data for ${itemStr}`);
return this.http.post<Tenant>(`${serviceBaseUrl}/v1/tenants`, item).toPromise()
}
async putTenant(item: Tenant): Promise<Tenant> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`TenantService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Tenant>(`${serviceBaseUrl}/v1/tenants/${id}`, item).toPromise()
}
async getTenantsByAccount(id: number): Promise<Tenant[]> {
this.messageService.add(`TenantService: get data by Account ${id}`);
return this.http.get<Tenant[]>(`${serviceBaseUrl}/v1/tenants/account/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class PremiseService { export class PremiseService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getPremise(): Promise<Premise> { async getPremises(): Promise<Premise[]> {
this.messageService.add(`PremiseService: fetch data`); this.messageService.add(`PremiseService: get data`);
return this.http.get<Premise>(`${serviceBaseUrl}/v1/premise`).toPromise() return this.http.get<Premise[]>(`${serviceBaseUrl}/v1/premises`).toPromise()
} }
async getPremise(id: number): Promise<Premise> {
this.messageService.add(`PremiseService: get data for ${id}`);
return this.http.get<Premise>(`${serviceBaseUrl}/v1/premises/${id}`).toPromise()
}
async postPremise(item: Premise): Promise<Premise> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`PremiseService: post data for ${itemStr}`);
return this.http.post<Premise>(`${serviceBaseUrl}/v1/premises`, item).toPromise()
}
async putPremise(item: Premise): Promise<Premise> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`PremiseService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Premise>(`${serviceBaseUrl}/v1/premises/${id}`, item).toPromise()
}
async getPremisesByAccount(id: number): Promise<Premise[]> {
this.messageService.add(`PremiseService: get data by Account ${id}`);
return this.http.get<Premise[]>(`${serviceBaseUrl}/v1/premises/account/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class FlatService { export class FlatService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getFlat(): Promise<Flat> { async getFlats(): Promise<Flat[]> {
this.messageService.add(`FlatService: fetch data`); this.messageService.add(`FlatService: get data`);
return this.http.get<Flat>(`${serviceBaseUrl}/v1/flat`).toPromise() return this.http.get<Flat[]>(`${serviceBaseUrl}/v1/flats`).toPromise()
} }
async getFlat(id: number): Promise<Flat> {
this.messageService.add(`FlatService: get data for ${id}`);
return this.http.get<Flat>(`${serviceBaseUrl}/v1/flats/${id}`).toPromise()
}
async postFlat(item: Flat): Promise<Flat> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`FlatService: post data for ${itemStr}`);
return this.http.post<Flat>(`${serviceBaseUrl}/v1/flats`, item).toPromise()
}
async putFlat(item: Flat): Promise<Flat> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`FlatService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Flat>(`${serviceBaseUrl}/v1/flats/${id}`, item).toPromise()
}
async getFlatsByPremise(id: number): Promise<Flat[]> {
this.messageService.add(`FlatService: get data by Premise ${id}`);
return this.http.get<Flat[]>(`${serviceBaseUrl}/v1/flats/premise/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class OverheadAdvanceService { export class OverheadAdvanceService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getOverheadAdvance(): Promise<OverheadAdvance> { async getOverheadAdvances(): Promise<OverheadAdvance[]> {
this.messageService.add(`OverheadAdvanceService: fetch data`); this.messageService.add(`OverheadAdvanceService: get data`);
return this.http.get<OverheadAdvance>(`${serviceBaseUrl}/v1/overhead_advance`).toPromise() return this.http.get<OverheadAdvance[]>(`${serviceBaseUrl}/v1/overhead_advances`).toPromise()
} }
async getOverheadAdvance(id: number): Promise<OverheadAdvance> {
this.messageService.add(`OverheadAdvanceService: get data for ${id}`);
return this.http.get<OverheadAdvance>(`${serviceBaseUrl}/v1/overhead_advances/${id}`).toPromise()
}
async postOverheadAdvance(item: OverheadAdvance): Promise<OverheadAdvance> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`OverheadAdvanceService: post data for ${itemStr}`);
return this.http.post<OverheadAdvance>(`${serviceBaseUrl}/v1/overhead_advances`, item).toPromise()
}
async putOverheadAdvance(item: OverheadAdvance): Promise<OverheadAdvance> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`OverheadAdvanceService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<OverheadAdvance>(`${serviceBaseUrl}/v1/overhead_advances/${id}`, item).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class OverheadAdvanceFlatMappingService { export class OverheadAdvanceFlatMappingService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getOverheadAdvanceFlatMapping(): Promise<OverheadAdvanceFlatMapping> { async getOverheadAdvanceFlatMappings(): Promise<OverheadAdvanceFlatMapping[]> {
this.messageService.add(`OverheadAdvanceFlatMappingService: fetch data`); this.messageService.add(`OverheadAdvanceFlatMappingService: get data`);
return this.http.get<OverheadAdvanceFlatMapping>(`${serviceBaseUrl}/v1/overhead_advance_flat_mapping`).toPromise() return this.http.get<OverheadAdvanceFlatMapping[]>(`${serviceBaseUrl}/v1/overhead_advance_flat_mappings`).toPromise()
} }
async getOverheadAdvanceFlatMapping(id: number): Promise<OverheadAdvanceFlatMapping> {
this.messageService.add(`OverheadAdvanceFlatMappingService: get data for ${id}`);
return this.http.get<OverheadAdvanceFlatMapping>(`${serviceBaseUrl}/v1/overhead_advance_flat_mappings/${id}`).toPromise()
}
async postOverheadAdvanceFlatMapping(item: OverheadAdvanceFlatMapping): Promise<OverheadAdvanceFlatMapping> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`OverheadAdvanceFlatMappingService: post data for ${itemStr}`);
return this.http.post<OverheadAdvanceFlatMapping>(`${serviceBaseUrl}/v1/overhead_advance_flat_mappings`, item).toPromise()
}
async getOverheadAdvanceFlatMappingsByOverheadAdvance(id: number): Promise<OverheadAdvanceFlatMapping[]> {
this.messageService.add(`OverheadAdvanceFlatMappingService: get data by OverheadAdvance ${id}`);
return this.http.get<OverheadAdvanceFlatMapping[]>(`${serviceBaseUrl}/v1/overhead_advance_flat_mappings/overhead_advance/${id}`).toPromise()
}
async getOverheadAdvanceFlatMappingsByFlat(id: number): Promise<OverheadAdvanceFlatMapping[]> {
this.messageService.add(`OverheadAdvanceFlatMappingService: get data by Flat ${id}`);
return this.http.get<OverheadAdvanceFlatMapping[]>(`${serviceBaseUrl}/v1/overhead_advance_flat_mappings/flat/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class ParkingService { export class ParkingService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getParking(): Promise<Parking> { async getParkings(): Promise<Parking[]> {
this.messageService.add(`ParkingService: fetch data`); this.messageService.add(`ParkingService: get data`);
return this.http.get<Parking>(`${serviceBaseUrl}/v1/parking`).toPromise() return this.http.get<Parking[]>(`${serviceBaseUrl}/v1/parkings`).toPromise()
} }
async getParking(id: number): Promise<Parking> {
this.messageService.add(`ParkingService: get data for ${id}`);
return this.http.get<Parking>(`${serviceBaseUrl}/v1/parkings/${id}`).toPromise()
}
async postParking(item: Parking): Promise<Parking> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`ParkingService: post data for ${itemStr}`);
return this.http.post<Parking>(`${serviceBaseUrl}/v1/parkings`, item).toPromise()
}
async putParking(item: Parking): Promise<Parking> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`ParkingService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Parking>(`${serviceBaseUrl}/v1/parkings/${id}`, item).toPromise()
}
async getParkingsByPremise(id: number): Promise<Parking[]> {
this.messageService.add(`ParkingService: get data by Premise ${id}`);
return this.http.get<Parking[]>(`${serviceBaseUrl}/v1/parkings/premise/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class CommercialPremiseService { export class CommercialPremiseService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getCommercialPremise(): Promise<CommercialPremise> { async getCommercialPremises(): Promise<CommercialPremise[]> {
this.messageService.add(`CommercialPremiseService: fetch data`); this.messageService.add(`CommercialPremiseService: get data`);
return this.http.get<CommercialPremise>(`${serviceBaseUrl}/v1/commercial_premise`).toPromise() return this.http.get<CommercialPremise[]>(`${serviceBaseUrl}/v1/commercial_premises`).toPromise()
} }
async getCommercialPremise(id: number): Promise<CommercialPremise> {
this.messageService.add(`CommercialPremiseService: get data for ${id}`);
return this.http.get<CommercialPremise>(`${serviceBaseUrl}/v1/commercial_premises/${id}`).toPromise()
}
async postCommercialPremise(item: CommercialPremise): Promise<CommercialPremise> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`CommercialPremiseService: post data for ${itemStr}`);
return this.http.post<CommercialPremise>(`${serviceBaseUrl}/v1/commercial_premises`, item).toPromise()
}
async putCommercialPremise(item: CommercialPremise): Promise<CommercialPremise> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`CommercialPremiseService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<CommercialPremise>(`${serviceBaseUrl}/v1/commercial_premises/${id}`, item).toPromise()
}
async getCommercialPremisesByPremise(id: number): Promise<CommercialPremise[]> {
this.messageService.add(`CommercialPremiseService: get data by Premise ${id}`);
return this.http.get<CommercialPremise[]>(`${serviceBaseUrl}/v1/commercial_premises/premise/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class TenancyService { export class TenancyService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getTenancy(): Promise<Tenancy> { async getTenancys(): Promise<Tenancy[]> {
this.messageService.add(`TenancyService: fetch data`); this.messageService.add(`TenancyService: get data`);
return this.http.get<Tenancy>(`${serviceBaseUrl}/v1/tenancy`).toPromise() return this.http.get<Tenancy[]>(`${serviceBaseUrl}/v1/tenancys`).toPromise()
} }
async getTenancy(id: number): Promise<Tenancy> {
this.messageService.add(`TenancyService: get data for ${id}`);
return this.http.get<Tenancy>(`${serviceBaseUrl}/v1/tenancys/${id}`).toPromise()
}
async postTenancy(item: Tenancy): Promise<Tenancy> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`TenancyService: post data for ${itemStr}`);
return this.http.post<Tenancy>(`${serviceBaseUrl}/v1/tenancys`, item).toPromise()
}
async putTenancy(item: Tenancy): Promise<Tenancy> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`TenancyService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Tenancy>(`${serviceBaseUrl}/v1/tenancys/${id}`, item).toPromise()
}
async getTenancysByTenant(id: number): Promise<Tenancy[]> {
this.messageService.add(`TenancyService: get data by Tenant ${id}`);
return this.http.get<Tenancy[]>(`${serviceBaseUrl}/v1/tenancys/tenant/${id}`).toPromise()
}
async getTenancysByFlat(id: number): Promise<Tenancy[]> {
this.messageService.add(`TenancyService: get data by Flat ${id}`);
return this.http.get<Tenancy[]>(`${serviceBaseUrl}/v1/tenancys/flat/${id}`).toPromise()
}
async getTenancysByParking(id: number): Promise<Tenancy[]> {
this.messageService.add(`TenancyService: get data by Parking ${id}`);
return this.http.get<Tenancy[]>(`${serviceBaseUrl}/v1/tenancys/parking/${id}`).toPromise()
}
async getTenancysByCommercialPremise(id: number): Promise<Tenancy[]> {
this.messageService.add(`TenancyService: get data by CommercialPremise ${id}`);
return this.http.get<Tenancy[]>(`${serviceBaseUrl}/v1/tenancys/commercial_premise/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class FeeService { export class FeeService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getFee(): Promise<Fee> { async getFees(): Promise<Fee[]> {
this.messageService.add(`FeeService: fetch data`); this.messageService.add(`FeeService: get data`);
return this.http.get<Fee>(`${serviceBaseUrl}/v1/fee`).toPromise() return this.http.get<Fee[]>(`${serviceBaseUrl}/v1/fees`).toPromise()
} }
async getFee(id: number): Promise<Fee> {
this.messageService.add(`FeeService: get data for ${id}`);
return this.http.get<Fee>(`${serviceBaseUrl}/v1/fees/${id}`).toPromise()
}
async postFee(item: Fee): Promise<Fee> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`FeeService: post data for ${itemStr}`);
return this.http.post<Fee>(`${serviceBaseUrl}/v1/fees`, item).toPromise()
}
async putFee(item: Fee): Promise<Fee> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`FeeService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Fee>(`${serviceBaseUrl}/v1/fees/${id}`, item).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class TenancyFeeMappingService { export class TenancyFeeMappingService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getTenancyFeeMapping(): Promise<TenancyFeeMapping> { async getTenancyFeeMappings(): Promise<TenancyFeeMapping[]> {
this.messageService.add(`TenancyFeeMappingService: fetch data`); this.messageService.add(`TenancyFeeMappingService: get data`);
return this.http.get<TenancyFeeMapping>(`${serviceBaseUrl}/v1/tenancy_fee_mapping`).toPromise() return this.http.get<TenancyFeeMapping[]>(`${serviceBaseUrl}/v1/tenancy_fee_mappings`).toPromise()
} }
async getTenancyFeeMapping(id: number): Promise<TenancyFeeMapping> {
this.messageService.add(`TenancyFeeMappingService: get data for ${id}`);
return this.http.get<TenancyFeeMapping>(`${serviceBaseUrl}/v1/tenancy_fee_mappings/${id}`).toPromise()
}
async postTenancyFeeMapping(item: TenancyFeeMapping): Promise<TenancyFeeMapping> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`TenancyFeeMappingService: post data for ${itemStr}`);
return this.http.post<TenancyFeeMapping>(`${serviceBaseUrl}/v1/tenancy_fee_mappings`, item).toPromise()
}
async getTenancyFeeMappingsByTenancy(id: number): Promise<TenancyFeeMapping[]> {
this.messageService.add(`TenancyFeeMappingService: get data by Tenancy ${id}`);
return this.http.get<TenancyFeeMapping[]>(`${serviceBaseUrl}/v1/tenancy_fee_mappings/tenancy/${id}`).toPromise()
}
async getTenancyFeeMappingsByFee(id: number): Promise<TenancyFeeMapping[]> {
this.messageService.add(`TenancyFeeMappingService: get data by Fee ${id}`);
return this.http.get<TenancyFeeMapping[]>(`${serviceBaseUrl}/v1/tenancy_fee_mappings/fee/${id}`).toPromise()
}
} }
@Injectable({ providedIn: 'root' })
export class AccountEntryCategoryService {
constructor(private messageService: MessageService, private http: HttpClient) { }
async getAccountEntryCategorys(): Promise<AccountEntryCategory[]> {
this.messageService.add(`AccountEntryCategoryService: get data`);
return this.http.get<AccountEntryCategory[]>(`${serviceBaseUrl}/v1/account_entry_categorys`).toPromise()
}
async getAccountEntryCategory(id: number): Promise<AccountEntryCategory> {
this.messageService.add(`AccountEntryCategoryService: get data for ${id}`);
return this.http.get<AccountEntryCategory>(`${serviceBaseUrl}/v1/account_entry_categorys/${id}`).toPromise()
}
async postAccountEntryCategory(item: AccountEntryCategory): Promise<AccountEntryCategory> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`AccountEntryCategoryService: post data for ${itemStr}`);
return this.http.post<AccountEntryCategory>(`${serviceBaseUrl}/v1/account_entry_categorys`, item).toPromise()
}
}
@Injectable({ providedIn: 'root' })
export class AccountEntryService { export class AccountEntryService {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async getAccountEntry(): Promise<AccountEntry> { async getAccountEntrys(): Promise<AccountEntry[]> {
this.messageService.add(`AccountEntryService: fetch data`); this.messageService.add(`AccountEntryService: get data`);
return this.http.get<AccountEntry>(`${serviceBaseUrl}/v1/account_entry`).toPromise() return this.http.get<AccountEntry[]>(`${serviceBaseUrl}/v1/account_entrys`).toPromise()
} }
async getAccountEntry(id: number): Promise<AccountEntry> {
this.messageService.add(`AccountEntryService: get data for ${id}`);
return this.http.get<AccountEntry>(`${serviceBaseUrl}/v1/account_entrys/${id}`).toPromise()
}
async postAccountEntry(item: AccountEntry): Promise<AccountEntry> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`AccountEntryService: post data for ${itemStr}`);
return this.http.post<AccountEntry>(`${serviceBaseUrl}/v1/account_entrys`, item).toPromise()
}
async getAccountEntrysByAccount(id: number): Promise<AccountEntry[]> {
this.messageService.add(`AccountEntryService: get data by Account ${id}`);
return this.http.get<AccountEntry[]>(`${serviceBaseUrl}/v1/account_entrys/account/${id}`).toPromise()
}
async getAccountEntrysByAccountEntryCategory(id: number): Promise<AccountEntry[]> {
this.messageService.add(`AccountEntryService: get data by AccountEntryCategory ${id}`);
return this.http.get<AccountEntry[]>(`${serviceBaseUrl}/v1/account_entrys/account_entry_category/${id}`).toPromise()
}
}
@Injectable({ providedIn: 'root' })
export class NoteService {
constructor(private messageService: MessageService, private http: HttpClient) { }
async getNotes(): Promise<Note[]> {
this.messageService.add(`NoteService: get data`);
return this.http.get<Note[]>(`${serviceBaseUrl}/v1/notes`).toPromise()
}
async getNote(id: number): Promise<Note> {
this.messageService.add(`NoteService: get data for ${id}`);
return this.http.get<Note>(`${serviceBaseUrl}/v1/notes/${id}`).toPromise()
}
async postNote(item: Note): Promise<Note> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`NoteService: post data for ${itemStr}`);
return this.http.post<Note>(`${serviceBaseUrl}/v1/notes`, item).toPromise()
}
async getNotesByTenant(id: number): Promise<Note[]> {
this.messageService.add(`NoteService: get data by Tenant ${id}`);
return this.http.get<Note[]>(`${serviceBaseUrl}/v1/notes/tenant/${id}`).toPromise()
}
} }

View File

@ -1,6 +1,8 @@
$GENERATED_TS_COMMENT
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
import { serviceBaseUrl } from './config'; import { serviceBaseUrl } from './config';
@ -11,9 +13,6 @@ import { ${JsNameConverter($table.name)} } from './data-objects';
#end for #end for
@Injectable({
providedIn: 'root'
})
@ -22,13 +21,46 @@ import { ${JsNameConverter($table.name)} } from './data-objects';
#from generateHelper import JsNameConverter #from generateHelper import JsNameConverter
#for $table in $tables #for $table in $tables
@Injectable({ providedIn: 'root' })
export class ${JsNameConverter($table.name)}Service { export class ${JsNameConverter($table.name)}Service {
constructor(private messageService: MessageService, private http: HttpClient) { } constructor(private messageService: MessageService, private http: HttpClient) { }
async get${JsNameConverter($table.name)}(): Promise<${JsNameConverter($table.name)}> { async get${JsNameConverter($table.name)}s(): Promise<${JsNameConverter($table.name)}[]> {
this.messageService.add(`${JsNameConverter($table.name)}Service: fetch data`); this.messageService.add(`${JsNameConverter($table.name)}Service: get data`);
return this.http.get<${JsNameConverter($table.name)}>(`\${serviceBaseUrl}/v1/${table.name}`).toPromise() return this.http.get<${JsNameConverter($table.name)}[]>(`\${serviceBaseUrl}/v1/${table.name}s`).toPromise()
} }
async get${JsNameConverter($table.name)}(id: number): Promise<${JsNameConverter($table.name)}> {
this.messageService.add(`${JsNameConverter($table.name)}Service: get data for \${id}`);
return this.http.get<${JsNameConverter($table.name)}>(`\${serviceBaseUrl}/v1/${table.name}s/\${id}`).toPromise()
}
async post${JsNameConverter($table.name)}(item: ${JsNameConverter($table.name)}): Promise<${JsNameConverter($table.name)}> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`${JsNameConverter($table.name)}Service: post data for \${itemStr}`);
return this.http.post<${JsNameConverter($table.name)}>(`\${serviceBaseUrl}/v1/${table.name}s`, item).toPromise()
}
#if (('immutable' not in $table) or (not $table.immutable))
async put${JsNameConverter($table.name)}(item: ${JsNameConverter($table.name)}): Promise<${JsNameConverter($table.name)}> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`${JsNameConverter($table.name)}Service: put data for \${itemStr}`)
let id: number = item["id"]
return this.http.put<${JsNameConverter($table.name)}>(`\${serviceBaseUrl}/v1/${table.name}s/\${id}`, item).toPromise()
}
#end if
#for $column in $table.columns
#if (('foreignkey' in $column) and $column.foreignkey)
async get${JsNameConverter($table.name)}sBy${JsNameConverter($column.name)}(id: number): Promise<${JsNameConverter($table.name)}[]> {
this.messageService.add(`${JsNameConverter($table.name)}Service: get data by ${JsNameConverter($column.name)} \${id}`);
return this.http.get<${JsNameConverter($table.name)}[]>(`\${serviceBaseUrl}/v1/${table.name}s/${column.name}/\${id}`).toPromise()
}
#end if
#end for
} }
#end for #end for

View File

@ -1,9 +1,22 @@
// -----------------------------------------
// THIS FILE HAS BEEN GENERATED
// DO NOT EDIT MANUALLY
// -----------------------------------------
export interface Account { export interface Account {
id: number
description: string description: string
} }
export const NULL_Account: Account = {
id: 0
,description: ''
}
export interface Tenant { export interface Tenant {
id: number
salutation: string salutation: string
firstname: string firstname: string
lastname: string lastname: string
@ -17,44 +30,108 @@ export interface Tenant {
iban: string iban: string
account: number account: number
} }
export const NULL_Tenant: Tenant = {
id: 0
,salutation: ''
,firstname: ''
,lastname: ''
,address1: ''
,address2: ''
,address3: ''
,zip: ''
,city: ''
,phone1: ''
,phone2: ''
,iban: ''
,account: undefined
}
export interface Premise { export interface Premise {
id: number
description: string description: string
street: string street: string
zip: string zip: string
city: string city: string
minus_area: number
account: number
}
export const NULL_Premise: Premise = {
id: 0
,description: ''
,street: ''
,zip: ''
,city: ''
,minus_area: undefined
,account: undefined
} }
export interface Flat { export interface Flat {
id: number
description: string description: string
premise: number premise: number
area: number area: number
flat_no: number flat_no: number
} }
export const NULL_Flat: Flat = {
id: 0
,description: ''
,premise: undefined
,area: undefined
,flat_no: undefined
}
export interface OverheadAdvance { export interface OverheadAdvance {
id: number
description: string description: string
amount: number amount: number
startdate: string startdate: string
enddate: string enddate: string
} }
export const NULL_OverheadAdvance: OverheadAdvance = {
id: 0
,description: ''
,amount: undefined
,startdate: ''
,enddate: ''
}
export interface OverheadAdvanceFlatMapping { export interface OverheadAdvanceFlatMapping {
id: number
overhead_advance: number overhead_advance: number
flat: number flat: number
} }
export const NULL_OverheadAdvanceFlatMapping: OverheadAdvanceFlatMapping = {
id: 0
,overhead_advance: undefined
,flat: undefined
}
export interface Parking { export interface Parking {
id: number
description: string description: string
premise: number premise: number
} }
export const NULL_Parking: Parking = {
id: 0
,description: ''
,premise: undefined
}
export interface CommercialPremise { export interface CommercialPremise {
id: number
description: string description: string
premise: number premise: number
area: number
}
export const NULL_CommercialPremise: CommercialPremise = {
id: 0
,description: ''
,premise: undefined
,area: undefined
} }
export interface Tenancy { export interface Tenancy {
id: number
description: string description: string
tenant: number tenant: number
flat: number flat: number
@ -63,25 +140,90 @@ export interface Tenancy {
startdate: string startdate: string
enddate: string enddate: string
} }
export const NULL_Tenancy: Tenancy = {
id: 0
,description: ''
,tenant: undefined
,flat: undefined
,parking: undefined
,commercial_premise: undefined
,startdate: ''
,enddate: ''
}
export interface Fee { export interface Fee {
id: number
description: string description: string
amount: number amount: number
fee_type: string fee_type: string
startdate: string startdate: string
enddate: string enddate: string
} }
export const NULL_Fee: Fee = {
id: 0
,description: ''
,amount: undefined
,fee_type: ''
,startdate: ''
,enddate: ''
}
export interface TenancyFeeMapping { export interface TenancyFeeMapping {
id: number
tenancy: number tenancy: number
fee: number fee: number
} }
export const NULL_TenancyFeeMapping: TenancyFeeMapping = {
id: 0
,tenancy: undefined
,fee: undefined
}
export interface AccountEntryCategory {
id: number
description: string
considerMinusArea: boolean
overhead_relevant: boolean
}
export const NULL_AccountEntryCategory: AccountEntryCategory = {
id: 0
,description: ''
,considerMinusArea: false
,overhead_relevant: false
}
export interface AccountEntry { export interface AccountEntry {
id: number
description: string description: string
account: number account: number
created_at: string created_at: string
fiscal_year: number
amount: number amount: number
document_no: number
account_entry_category: number
}
export const NULL_AccountEntry: AccountEntry = {
id: 0
,description: ''
,account: undefined
,created_at: ''
,fiscal_year: undefined
,amount: undefined
,document_no: undefined
,account_entry_category: undefined
}
export interface Note {
id: number
created_at: string
tenant: number
note: string
}
export const NULL_Note: Note = {
id: 0
,created_at: ''
,tenant: undefined
,note: ''
} }

View File

@ -1,11 +1,27 @@
$GENERATED_TS_COMMENT
#from generateHelper import JsNameConverter #from generateHelper import JsNameConverter
#for $table in $tables #for $table in $tables
export interface $JsNameConverter($table.name) { export interface $JsNameConverter($table.name) {
id: number
#for $column in $table.columns #for $column in $table.columns
${column.name}: ${column.jstype} ${column.name}: ${column.jstype}
#end for #end for
} }
export const NULL_$JsNameConverter($table.name): $JsNameConverter($table.name) = {
id: 0
#for $column in $table.columns
,${column.name}: #slurp
#if $column.jstype == "string"
''
#else if $column.jstype == "number"
undefined
#else if $column.jstype == "boolean"
false
#end if
#end for
}
#end for #end for

View File

@ -0,0 +1,22 @@
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Mietzahlung eintragen
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<span>Mieter auswählen: </span>
<mat-form-field appearance="outline">
<mat-select #mapSelect [(ngModel)]="accountId" name="tenantAccount">
<mat-label>Mieter</mat-label>
<mat-option *ngFor="let p of tenants" [value]="p.id">{{p.firstname}} {{p.lastname}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<app-account [selectedAccountId]="accountId" [shallBeRentPayment]="true"></app-account>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EnterPaymentComponent } from './enter-payment.component';
describe('EnterPaymentComponent', () => {
let component: EnterPaymentComponent;
let fixture: ComponentFixture<EnterPaymentComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EnterPaymentComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(EnterPaymentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { TenantService } from '../data-object-service';
import { Tenant } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-enter-payment',
templateUrl: './enter-payment.component.html',
styleUrls: ['./enter-payment.component.css']
})
export class EnterPaymentComponent implements OnInit {
tenants: Tenant[]
accountId: number
constructor(
private tenantService: TenantService,
private messageService: MessageService
) { }
async getTenants(): Promise<void> {
try {
this.messageService.add("Trying to load tenants")
this.tenants = await this.tenantService.getTenants()
this.messageService.add("Tenants loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
ngOnInit(): void {
this.getTenants()
}
}

View File

@ -0,0 +1,6 @@
export interface ErrorDialogData {
module: string,
func: string,
msg: string
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ErrorDialogService } from './error-dialog.service';
describe('ErrorDialogService', () => {
let service: ErrorDialogService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ErrorDialogService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ErrorDialogComponent } from './error-dialog/error-dialog.component';
@Injectable({
providedIn: 'root'
})
export class ErrorDialogService {
constructor(public dialog: MatDialog) { }
openDialog(module: string, func: string, msg: string): void {
const dialogRef = this.dialog.open(ErrorDialogComponent, {
width: '450px',
data: { module: module, func: func, msg: msg }
})
}
}

View File

@ -0,0 +1,11 @@
<h1>Fehler</h1>
<h2>Module</h2>
<p>{{data.module}}</p>
<h2>Function</h2>
<p>{{data.func}}</p>
<h2>Message</h2>
<p>{{data.msg}}</p>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ErrorDialogComponent } from './error-dialog.component';
describe('ErrorDialogComponent', () => {
let component: ErrorDialogComponent;
let fixture: ComponentFixture<ErrorDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ErrorDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ErrorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ErrorDialogData } from '../error-dialog-data';
@Component({
selector: 'app-error-dialog',
templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.css']
})
export class ErrorDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<ErrorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ErrorDialogData
) { }
onNoClick(): void {
this.dialogRef.close()
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { MessageService } from './message.service';
import { serviceBaseUrl } from './config';
import { Account, Fee, OverheadAdvance } from './data-objects';
import { Saldo, Tenant_with_Saldo, UniqueNumber } from './ext-data-objects';
@Injectable({ providedIn: 'root' })
export class ExtApiService {
constructor(private messageService: MessageService, private http: HttpClient) { }
async getOverheadAdvancesByFlat(id: number): Promise<OverheadAdvance[]> {
this.messageService.add(`ExtApiService: get overheadadvances by flat ${id}`);
return this.http.get<OverheadAdvance[]>(`${serviceBaseUrl}/v1/overhead_advances/flat/${id}`).toPromise()
}
async getAccountByDescription(description: string): Promise<Account> {
this.messageService.add(`ExtApiService: get account by description ${description}`);
return this.http.get<Account>(`${serviceBaseUrl}/v1/accounts/bydescription/${description}`).toPromise()
}
async getFeeByTenancies(id: number): Promise<Fee[]> {
this.messageService.add(`ExtApiService: get fees by flat ${id}`);
return this.http.get<Fee[]>(`${serviceBaseUrl}/v1/fees/tenancy/${id}`).toPromise()
}
async getAccountSaldo(id: number): Promise<Saldo> {
this.messageService.add(`ExtApiService: get saldo for account ${id}`);
return this.http.get<Saldo>(`${serviceBaseUrl}/v1/account/saldo/${id}`).toPromise()
}
async getTenantsWithSaldo(): Promise<Tenant_with_Saldo[]> {
this.messageService.add("ExtApiService: get tenants with saldo");
return this.http.get<Tenant_with_Saldo[]>(`${serviceBaseUrl}/v1/tenants/saldo`).toPromise()
}
async getUniqueNumber(): Promise<UniqueNumber> {
this.messageService.add("ExtApiService: get unique number");
return this.http.get<UniqueNumber>(`${serviceBaseUrl}/v1/uniquenumber`).toPromise()
}
}

View File

@ -0,0 +1,16 @@
export interface Saldo {
saldo: number
}
export interface Tenant_with_Saldo {
id: number
firstname: string
lastname: string
address1: string
saldo: number
}
export interface UniqueNumber {
number: number
}

View File

@ -0,0 +1,49 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
{{fee?.description}}
</mat-card-title>
<mat-card-subtitle>
ID: {{fee?.id}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>
<form (ngSubmit)="saveFee()">
<div>
<mat-form-field appearance="outline">
<mat-label>Beschreibung</mat-label>
<input matInput name="description" [(ngModel)]="fee.description"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>Betrag (€)</mat-label>
<input matInput type="number" name="amount" [(ngModel)]="fee.amount" [readonly]="readonly"/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Typ</mat-label>
<mat-select [(ngModel)]="fee.fee_type" name="fee_type">
<mat-option *ngFor="let p of fee_types" [value]="p">{{p}}</mat-option>
</mat-select>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>Beginn</mat-label>
<input matInput name="startdate" [(ngModel)]="fee.startdate" [matDatepicker]="startdatepicker" [readonly]="readonly"/>
<mat-datepicker-toggle matSuffix [for]="startdatepicker" [disabled]="readonly"></mat-datepicker-toggle>
<mat-datepicker #startdatepicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Ende</mat-label>
<input matInput name="enddate" [(ngModel)]="fee.enddate" [matDatepicker]="enddatepicker"/>
<mat-datepicker-toggle matSuffix [for]="enddatepicker"></mat-datepicker-toggle>
<mat-datepicker #enddatepicker></mat-datepicker>
</mat-form-field>
</div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeeDetailsComponent } from './fee-details.component';
describe('FeeDetailsComponent', () => {
let component: FeeDetailsComponent;
let fixture: ComponentFixture<FeeDetailsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FeeDetailsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FeeDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,73 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { ActivatedRoute, Router } from '@angular/router';
import { FeeService } from '../data-object-service';
import { NULL_Fee, Fee } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-fee-details',
templateUrl: './fee-details.component.html',
styleUrls: ['./fee-details.component.css']
})
export class FeeDetailsComponent implements OnInit {
@ViewChild('submitButton') submitButton: MatButton
fee: Fee = NULL_Fee
fee_types: string[] = [ "total", "per_area" ]
readonly: boolean
constructor(
private feeService: FeeService,
private messageService: MessageService,
private route: ActivatedRoute,
private router: Router
) { }
async getFee(): Promise<void> {
try {
const id = +this.route.snapshot.paramMap.get('id')
this.readonly = false
if (id != 0) {
this.fee = await this.feeService.getFee(id)
this.readonly = true
}
this.messageService.add(`Fee is ${ JSON.stringify(this.fee, undefined, 4)}`)
} catch (err) {
this.messageService.add(`Error in getFee: ${ JSON.stringify(err, undefined, 4) }`)
}
}
async saveFee() {
try {
this.submitButton.disabled = true
this.messageService.add("saveFee")
this.messageService.add(JSON.stringify(this.fee, undefined, 4))
if (this.fee.enddate == null) {
this.fee.enddate = ''
}
if (this.fee.startdate == null) {
this.fee.startdate = ''
}
if (this.fee.id == 0) {
this.messageService.add("about to insert new fee")
this.fee = await this.feeService.postFee(this.fee)
this.messageService.add(`Successfully added fee with id ${this.fee.id}`)
} else {
this.messageService.add("about to update existing fee")
this.fee = await this.feeService.putFee(this.fee)
this.messageService.add(`Successfully changed fee with id ${this.fee.id}`)
}
this.router.navigate(['/fees'])
} finally {
this.submitButton.disabled = false
}
}
ngOnInit(): void {
this.getFee()
}
}

View File

@ -0,0 +1,8 @@
table {
width: 75%;
}
.spacer {
flex: 1 1 auto;
}

View File

@ -0,0 +1,39 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
<span>Mietsätze</span>
<span class="spacer"></span>
<a mat-button routerLink="/fee">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.description}}</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>Betrag</th>
<td mat-cell *matCellDef="let element">{{element.amount | number:'1.2-2'}} €</td>
</ng-container>
<ng-container matColumnDef="fee_type">
<th mat-header-cell *matHeaderCellDef>Typ</th>
<td mat-cell *matCellDef="let element">{{element.fee_type}}</td>
</ng-container>
<ng-container matColumnDef="startdate">
<th mat-header-cell *matHeaderCellDef>Beginn</th>
<td mat-cell *matCellDef="let element">{{element.startdate | date}}</td>
</ng-container>
<ng-container matColumnDef="enddate">
<th mat-header-cell *matHeaderCellDef>Ende</th>
<td mat-cell *matCellDef="let element">{{element.enddate | date}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/fee/', row.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeeListComponent } from './fee-list.component';
describe('FeeListComponent', () => {
let component: FeeListComponent;
let fixture: ComponentFixture<FeeListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FeeListComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FeeListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,42 @@
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { FeeService } from '../data-object-service';
import { Fee } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-fee-list',
templateUrl: './fee-list.component.html',
styleUrls: ['./fee-list.component.css']
})
export class FeeListComponent implements OnInit {
fees: Fee[]
dataSource: MatTableDataSource<Fee>
displayedColumns: string[] = [ "description", "amount", "fee_type", "startdate", "enddate" ]
constructor(
private feeService: FeeService,
private messageService: MessageService
) { }
async getFees(): Promise<void> {
try {
this.messageService.add("Trying to load fees")
this.fees = await this.feeService.getFees()
this.messageService.add("Fees loaded")
this.dataSource = new MatTableDataSource<Fee>(this.fees)
} catch (err) {
this.messageService.add(`Error in getFees: ${ JSON.stringify(err, undefined, 4) }`)
}
}
ngOnInit(): void {
this.messageService.add("FeeListComponent.ngOnInit")
this.getFees()
}
}

View File

@ -0,0 +1,11 @@
table {
width: 75%;
}
.spacer {
flex: 1 1 auto;
}
#addoverheadfield {
margin-right: 15px;
}

View File

@ -0,0 +1,84 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
{{flat?.description}} {{flat?.flat_no}} {{premise?.description}}
</mat-card-title>
<mat-card-subtitle>
ID: {{flat?.id}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>
<form (ngSubmit)="saveFlat()">
<div>
<mat-form-field appearance="outline">
<mat-label>Beschreibung</mat-label>
<input matInput name="description" [(ngModel)]="flat.description"/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Haus</mat-label>
<mat-select [(ngModel)]="flat.premise" name="premise">
<mat-option *ngFor="let p of premises" [value]="p.id">{{p.description}}</mat-option>
</mat-select>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>Fläche</mat-label>
<input type="number" matInput name="area" [(ngModel)]="flat.area"/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Wohnungsnummer</mat-label>
<input type="number" matInput name="flat_no" [(ngModel)]="flat.flat_no"/>
</mat-form-field>
</div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form>
</div>
</mat-card-content>
</mat-card>
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Betriebskostenzuordnungen
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.description}}</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>Betrag</th>
<td mat-cell *matCellDef="let element">{{element.amount}} €</td>
</ng-container>
<ng-container matColumnDef="startdate">
<th mat-header-cell *matHeaderCellDef>Beginn</th>
<td mat-cell *matCellDef="let element">{{element.startdate}}</td>
</ng-container>
<ng-container matColumnDef="enddate">
<th mat-header-cell *matHeaderCellDef>Ende</th>
<td mat-cell *matCellDef="let element">{{element.enddate}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<div>
<form (ngSubmit)="addOverheadAdvance()">
<mat-form-field appearance="standard" id="addoverheadfield">
<mat-label>Betriebskostensatz</mat-label>
<mat-select #mapSelect [(ngModel)]="selectedOverheadAdvance" name="overheadadvance">
<mat-option *ngFor="let p of allOverheadAdvances" [value]="p.id">{{p.description}} {{p.startdate}}</mat-option>
</mat-select>
</mat-form-field>
<button #mapButton type="submit" mat-raised-button color="primary">Zuordnen</button>
</form>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FlatDetailsComponent } from './flat-details.component';
describe('FlatDetailsComponent', () => {
let component: FlatDetailsComponent;
let fixture: ComponentFixture<FlatDetailsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FlatDetailsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FlatDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,131 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatSelect } from '@angular/material/select';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { FlatService, OverheadAdvanceFlatMappingService, OverheadAdvanceService, PremiseService } from '../data-object-service';
import { Flat, NULL_Flat, NULL_Premise, OverheadAdvance, OverheadAdvanceFlatMapping, Premise } from '../data-objects';
import { ExtApiService } from '../ext-data-object-service';
import { MessageService } from '../message.service';
@Component({
selector: 'app-flat-details',
templateUrl: './flat-details.component.html',
styleUrls: ['./flat-details.component.css']
})
export class FlatDetailsComponent implements OnInit {
flat: Flat = NULL_Flat
premise: Premise = NULL_Premise
premises: Premise[]
mappedOverheadAdvances: OverheadAdvance[]
allOverheadAdvances: OverheadAdvance[]
selectedOverheadAdvance: number
dataSource: MatTableDataSource<OverheadAdvance>
displayedColumns: string[] = [ "description", "amount", "startdate", "enddate" ]
@ViewChild('submitButton') submitButton: MatButton
@ViewChild('mapButton') mapButton: MatButton
@ViewChild('mapSelect') mapSelect: MatSelect
constructor(
private flatService: FlatService,
private premiseService: PremiseService,
private extApiService: ExtApiService,
private overheadAdvanceService: OverheadAdvanceService,
private overheadAdvanceFlatMappingService: OverheadAdvanceFlatMappingService,
private messageService: MessageService,
private route: ActivatedRoute,
private router: Router
) { }
async getFlat(): Promise<void> {
try {
const id = +this.route.snapshot.paramMap.get('id')
this.messageService.add(`getFlat ${id}`)
if (id != 0) {
this.flat = await this.flatService.getFlat(id)
this.premise = await this.premiseService.getPremise(this.flat.premise)
this.mappedOverheadAdvances = await this.extApiService.getOverheadAdvancesByFlat(this.flat.id)
this.dataSource = new MatTableDataSource<OverheadAdvance>(this.mappedOverheadAdvances)
} else {
this.flat = NULL_Flat
this.premise = NULL_Premise
this.mappedOverheadAdvances = undefined
this.dataSource = undefined
}
} catch (err) {
throw err
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async getPremises(): Promise<void> {
try {
this.messageService.add("Trying to load premises")
this.premises = await this.premiseService.getPremises()
this.messageService.add("Premises loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async getOverheadAdvances(): Promise<void> {
try {
this.messageService.add("Trying to load overheadadvances")
this.allOverheadAdvances = await this.overheadAdvanceService.getOverheadAdvances()
this.messageService.add("overheadadvances loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async saveFlat() {
try {
this.submitButton.disabled = true
this.messageService.add(`saveFlat: ${ JSON.stringify(this.flat, undefined, 4) }`)
if (this.flat.id == 0) {
this.messageService.add("about to insert new flat")
this.flat = await this.flatService.postFlat(this.flat)
this.messageService.add(`Successfully added flat with id ${this.flat.id}`)
} else {
this.messageService.add("about to update existing flat")
this.flat = await this.flatService.putFlat(this.flat)
this.messageService.add(`Successfully changed flat with id ${this.flat.id}`)
}
this.router.navigate(['/flats'])
} finally {
this.submitButton.disabled = false
}
}
async addOverheadAdvance() {
try {
this.mapButton.disabled = true
this.messageService.add(`addOverheadAdvance: ${ JSON.stringify(this.selectedOverheadAdvance, undefined, 4) }`)
let newMapping: OverheadAdvanceFlatMapping = {
'flat': this.flat.id,
'overhead_advance': this.selectedOverheadAdvance,
'id': 0
}
newMapping = await this.overheadAdvanceFlatMappingService.postOverheadAdvanceFlatMapping(newMapping)
this.messageService.add(`New overheadadvance flat mapping created: ${newMapping.id}`)
this.selectedOverheadAdvance = undefined
this.getFlat()
} finally {
this.mapButton.disabled = false
}
}
ngOnInit(): void {
this.messageService.add("FlatDetailsComponent.ngOnInit")
this.getPremises()
this.getOverheadAdvances()
this.getFlat()
}
}

View File

@ -0,0 +1 @@
<p>home works!</p>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,34 @@
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Buchführung
</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-accordion>
<mat-expansion-panel (opened)="collapseExpenseDetails = true"
(closed)="collapseExpenseDetails = false">
<mat-expansion-panel-header>
<mat-panel-title>
Ausgaben
</mat-panel-title>
<mat-panel-description>
<div>Betriebskosten-relevante Ausgaben nicht hier sondern im Betriebskostenkonto unter "Meine Häuser" erfassen.</div>
</mat-panel-description>
</mat-expansion-panel-header>
<app-account #expenseAccountComponent [selectedAccountId]="expenseAccountId" [shallBeRentPayment]="false"></app-account>
</mat-expansion-panel>
<mat-expansion-panel (opened)="collapseIncomeDetails = true"
(closed)="collapseIncomeDetails = false">
<mat-expansion-panel-header>
<mat-panel-title>
Einnahmen
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<app-account #incomeAccountComponent [selectedAccountId]="incomeAccountId" [shallBeRentPayment]="false"></app-account>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LedgerComponent } from './ledger.component';
describe('LedgerComponent', () => {
let component: LedgerComponent;
let fixture: ComponentFixture<LedgerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LedgerComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LedgerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,48 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { AccountComponent } from '../account/account.component';
import { Account } from '../data-objects';
import { ExtApiService } from '../ext-data-object-service';
import { MessageService } from '../message.service';
@Component({
selector: 'app-ledger',
templateUrl: './ledger.component.html',
styleUrls: ['./ledger.component.css']
})
export class LedgerComponent implements OnInit {
incomeAccount: Account
incomeAccountId: number
expenseAccount: Account
expenseAccountId: number
collapseIncomeDetails: boolean = false
collapseExpenseDetails: boolean = false
@ViewChild('incomeAccountComponent') incomeAccountComponent: AccountComponent
@ViewChild('expenseAccountComponent') expenseAccountComponent: AccountComponent
constructor(
private extApiService: ExtApiService,
private messageService: MessageService
) { }
async getAccount(): Promise<void> {
try {
this.messageService.add("Trying to load ledger account")
this.incomeAccount = await this.extApiService.getAccountByDescription('LedgerIncome')
this.expenseAccount = await this.extApiService.getAccountByDescription('LedgerExpense')
this.messageService.add("Account loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async ngOnInit(): Promise<void> {
await this.getAccount()
this.incomeAccountId = this.incomeAccount.id
this.expenseAccountId = this.expenseAccount.id
}
}

View File

@ -1,11 +1,24 @@
<section class="mat-typography"> <section class="mat-typography">
<mat-card class="defaultCard" *ngIf="messageService.messages.length"> <mat-card class="defaultCard">
<mat-card-header> <mat-card-header>
<mat-card-title>Messages</mat-card-title> <mat-card-title>Messages</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<button (click)="messageService.clear()">clear</button> <mat-accordion>
<mat-expansion-panel (opened)="collapse = true"
(closed)="collapse = false">
<mat-expansion-panel-header>
<mat-panel-title *ngIf="!collapse">
Übersicht
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<button (click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div> <div *ngFor='let message of messageService.messages'> {{message}} </div>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</section> </section>

View File

@ -8,6 +8,8 @@ import { MessageService } from '../message.service';
}) })
export class MessagesComponent implements OnInit { export class MessagesComponent implements OnInit {
collapse: boolean = false
constructor(public messageService: MessageService) { } constructor(public messageService: MessageService) { }
ngOnInit(): void { ngOnInit(): void {

View File

@ -0,0 +1,33 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Meine Büros
<span>Meine Büros</span>
<span class="spacer"></span>
<a mat-button routerLink="/commercialunit">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.commercialPremise.description}}</td>
</ng-container>
<ng-container matColumnDef="premise">
<th mat-header-cell *matHeaderCellDef>Haus</th>
<td mat-cell *matCellDef="let element">{{element.premise.description}}</td>
</ng-container>
<ng-container matColumnDef="area">
<th mat-header-cell *matHeaderCellDef>Fläche</th>
<td mat-cell *matCellDef="let element">{{element.commercialPremise.area | number:'1.2-2'}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/commercialunit/', row.commercialPremise.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyCommercialUnitsComponent } from './my-commercial-units.component';
describe('MyCommercialUnitsComponent', () => {
let component: MyCommercialUnitsComponent;
let fixture: ComponentFixture<MyCommercialUnitsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyCommercialUnitsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MyCommercialUnitsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,64 @@
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';
import { CommercialPremiseService, PremiseService } from '../data-object-service';
import { CommercialPremise, Premise } from '../data-objects';
import { MatTableDataSource } from '@angular/material/table'
interface DN_CommercialPremise {
commercialPremise: CommercialPremise
premise: Premise
}
@Component({
selector: 'app-my-commercial-units',
templateUrl: './my-commercial-units.component.html',
styleUrls: ['./my-commercial-units.component.css']
})
export class MyCommercialUnitsComponent implements OnInit {
commercialPremises: CommercialPremise[]
premises: Premise[]
dnCommercialPremises: DN_CommercialPremise[] = []
dataSource: MatTableDataSource<DN_CommercialPremise>
displayedColumns: string[] = ["description", "premise", "area"]
constructor(
private commercialPremiseService: CommercialPremiseService,
private premiseService: PremiseService,
private messageService: MessageService
) { }
async getCommercialPremises(): Promise<void> {
try {
this.messageService.add("Trying to load commercialPremises")
this.commercialPremises = await this.commercialPremiseService.getCommercialPremises()
this.messageService.add("CommercialPremises loaded")
this.messageService.add("Trying to load premises")
this.premises = await this.premiseService.getPremises()
this.messageService.add(`Premises loaded: ${ JSON.stringify(this.premises, undefined, 4) }`)
const premisesDict = new Map<number, Premise>()
for (let p of this.premises) {
premisesDict.set(p.id, p)
}
for (let f of this.commercialPremises) {
this.dnCommercialPremises.push({
commercialPremise: f,
premise: premisesDict.get(f.premise)
})
}
this.dataSource = new MatTableDataSource<DN_CommercialPremise>(this.dnCommercialPremises)
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
ngOnInit(): void {
this.messageService.add("MyCommercialUnitsComponent.ngOnInit")
this.getCommercialPremises()
}
}

View File

@ -0,0 +1,35 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
<span>Meine Wohnungen</span>
<span class="spacer"></span>
<a mat-button routerLink="/flat">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.flat.description}}</td>
</ng-container>
<ng-container matColumnDef="premise">
<th mat-header-cell *matHeaderCellDef>Haus</th>
<td mat-cell *matCellDef="let element">{{element.premise.description}}</td>
</ng-container>
<ng-container matColumnDef="area">
<th mat-header-cell *matHeaderCellDef>Wohnfläche</th>
<td mat-cell *matCellDef="let element">{{element.flat.area | number:'1.2-2'}}</td>
</ng-container>
<ng-container matColumnDef="flat_no">
<th mat-header-cell *matHeaderCellDef>Wohnungsnummer</th>
<td mat-cell *matCellDef="let element">{{element.flat.flat_no}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/flat/', row.flat.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyFlatsComponent } from './my-flats.component';
describe('MyFlatsComponent', () => {
let component: MyFlatsComponent;
let fixture: ComponentFixture<MyFlatsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyFlatsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MyFlatsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';
import { FlatService, PremiseService } from '../data-object-service';
import { Flat, Premise } from '../data-objects';
import { MatTableDataSource } from '@angular/material/table'
interface DN_Flat {
flat: Flat
premise: Premise
}
@Component({
selector: 'app-my-flats',
templateUrl: './my-flats.component.html',
styleUrls: ['./my-flats.component.css']
})
export class MyFlatsComponent implements OnInit {
flats: Flat[]
premises: Premise[]
dnFlats: DN_Flat[] = []
dataSource: MatTableDataSource<DN_Flat>
displayedColumns: string[] = ["description", "premise", "area", "flat_no"]
constructor(
private flatService: FlatService,
private premiseService: PremiseService,
private messageService: MessageService
) { }
async getFlats(): Promise<void> {
try {
this.messageService.add("Trying to load flats")
this.flats = await this.flatService.getFlats()
this.messageService.add(`Flats loaded: ${ JSON.stringify(this.flats, undefined, 4) }`)
this.messageService.add("Trying to load premises")
this.premises = await this.premiseService.getPremises()
this.messageService.add(`Premises loaded: ${ JSON.stringify(this.premises, undefined, 4) }`)
const premisesDict = new Map<number, Premise>()
for (let p of this.premises) {
premisesDict.set(p.id, p)
}
for (let f of this.flats) {
this.dnFlats.push({
flat: f,
premise: premisesDict.get(f.premise)
})
}
this.dataSource = new MatTableDataSource<DN_Flat>(this.dnFlats)
} catch (err) {
// throw err
this.messageService.add(`Error in getFlats: ${ JSON.stringify(err, undefined, 4) }`)
}
}
ngOnInit(): void {
this.messageService.add("MyFlatsComponent.ngOnInit")
this.getFlats()
}
}

View File

@ -0,0 +1,28 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Meine Garagen
<span>Meine Garagen</span>
<span class="spacer"></span>
<a mat-button routerLink="/parking">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.parking.description}}</td>
</ng-container>
<ng-container matColumnDef="premise">
<th mat-header-cell *matHeaderCellDef>Haus</th>
<td mat-cell *matCellDef="let element">{{element.premise.description}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/parking/', row.parking.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyParkingsComponent } from './my-parkings.component';
describe('MyParkingsComponent', () => {
let component: MyParkingsComponent;
let fixture: ComponentFixture<MyParkingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ MyParkingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(MyParkingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,62 @@
import { Component, OnInit } from '@angular/core';
import { MessageService } from '../message.service';
import { ParkingService, PremiseService } from '../data-object-service';
import { Parking, Premise } from '../data-objects';
import { MatTableDataSource } from '@angular/material/table'
interface DN_Parking {
parking: Parking
premise: Premise
}
@Component({
selector: 'app-my-parkings',
templateUrl: './my-parkings.component.html',
styleUrls: ['./my-parkings.component.css']
})
export class MyParkingsComponent implements OnInit {
parkings: Parking[]
premises: Premise[]
dnParkings: DN_Parking[] = []
dataSource: MatTableDataSource<DN_Parking>
displayedColumns: string[] = ["description", "premise"]
constructor(
private parkingService: ParkingService,
private premiseService: PremiseService,
private messageService: MessageService
) { }
async getParkings(): Promise<void> {
try {
this.messageService.add("Trying to load parkings")
this.parkings = await this.parkingService.getParkings()
this.messageService.add("Parkings loaded")
this.messageService.add("Trying to load premises")
this.premises = await this.premiseService.getPremises()
this.messageService.add(`Premises loaded: ${ JSON.stringify(this.premises, undefined, 4) }`)
const premisesDict = new Map<number, Premise>()
for (let p of this.premises) {
premisesDict.set(p.id, p)
}
for (let p of this.parkings) {
this.dnParkings.push({
parking: p,
premise: premisesDict.get(p.premise)
})
}
this.dataSource = new MatTableDataSource<DN_Parking>(this.dnParkings)
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
ngOnInit(): void {
this.messageService.add("MyParkingsComponent.ngOnInit")
this.getParkings()
}
}

View File

@ -0,0 +1,43 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
<span>Meine Häuser</span>
<span class="spacer"></span>
<a mat-button routerLink="/premise">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.description}}</td>
</ng-container>
<ng-container matColumnDef="street">
<th mat-header-cell *matHeaderCellDef>Strasse</th>
<td mat-cell *matCellDef="let element">{{element.street}}</td>
</ng-container>
<ng-container matColumnDef="zip">
<th mat-header-cell *matHeaderCellDef>PLZ</th>
<td mat-cell *matCellDef="let element">{{element.zip}}</td>
</ng-container>
<ng-container matColumnDef="city">
<th mat-header-cell *matHeaderCellDef>Ort</th>
<td mat-cell *matCellDef="let element">{{element.city}}</td>
</ng-container>
<ng-container matColumnDef="minusArea">
<th mat-header-cell *matHeaderCellDef>Minus-Fläche</th>
<td mat-cell *matCellDef="let element">{{element.minus_area | number:'1.2-2'}}</td>
</ng-container>
<ng-container matColumnDef="account">
<th mat-header-cell *matHeaderCellDef>Betriebskostenkonto</th>
<td mat-cell *matCellDef="let element">{{element.account}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/premise/', row.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

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