59 Commits

Author SHA1 Message Date
f7eca3844d fix concerning FormControl 2022-04-01 16:31:43 +02:00
9a1e3231d6 remove broken sorting code 2022-04-01 15:42:37 +02:00
0075416fc3 forgotten change 2022-04-01 15:26:32 +02:00
47e96c10b1 forgotten config fix 2022-04-01 15:08:32 +02:00
3e32613a78 better handling of presets 2022-04-01 15:05:06 +02:00
74d0a6084b sorting not yet working 2022-04-01 14:25:58 +02:00
ac1636c162 sorting 2022-04-01 13:59:31 +02:00
d0771be1d1 Abrechnungsskript 2022-04-01 12:04:33 +02:00
452b4f69bd output dir 2022-04-01 10:59:52 +02:00
4553272c42 format of output 2022-03-31 22:30:09 +02:00
0d4e33505e account statement stuff 2022-03-31 21:40:49 +02:00
2be6a6e140 some forgotten details 2022-03-31 16:19:36 +02:00
4391cc1d8b account entry reference stuff 2022-03-31 16:07:00 +02:00
b346cc07d0 merged 2022-03-31 15:31:53 +02:00
169795c16e account entry reference stuff 2022-03-31 15:30:22 +02:00
25f6de1c11 Kontoauszug 2022-02-13 12:30:10 +01:00
1c6fe0dd16 account statement view 2022-02-11 20:33:25 +01:00
2a0bda012b year is number 2022-02-05 18:05:50 +01:00
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
57 changed files with 1596 additions and 271 deletions

9
.gitignore vendored
View File

@ -3,4 +3,11 @@ ENV
api/config/dbconfig.ini api/config/dbconfig.ini
api/config/authservice.pub api/config/authservice.pub
cli/config/dbconfig.ini cli/config/dbconfig.ini
cli/output/
*~
.*~
.vscode/
cli/*.tex
cli/*.log
cli/*.pdf
cli/*.aux

View File

@ -88,4 +88,43 @@
$ref: '#/components/schemas/tenant_with_saldo' $ref: '#/components/schemas/tenant_with_saldo'
security: security:
- jwt: ['secret'] - 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']

View File

@ -36,11 +36,28 @@ def get_account_saldo(user, token_info, accountId=None):
def get_tenant_with_saldo(user, token_info): def get_tenant_with_saldo(user, token_info):
return dbGetMany(user, token_info, { return dbGetMany(user, token_info, {
"statement": """ "statement": """
SELECT t.firstname, t.lastname, t.address1, sum(a.amount) AS saldo SELECT t.id, t.firstname, t.lastname, t.address1, sum(a.amount) AS saldo
FROM tenant_t t, account_entry_t a FROM tenant_t t LEFT OUTER JOIN account_entry_t a ON a.account = t.account
WHERE a.account = t.account GROUP BY t.id, t.firstname, t.lastname, t.address1
GROUP BY t.firstname, t.lastname, t.address1
""", """,
"params": () "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

@ -269,6 +269,9 @@ SELECT
,account ,account
FROM tenant_t FROM tenant_t
WHERE account = %s WHERE account = %s
ORDER BY
lastname
,firstname
""", """,
"params": (accountId, ) "params": (accountId, )
} }
@ -283,6 +286,8 @@ SELECT
,street ,street
,zip ,zip
,city ,city
,minus_area
,account
FROM premise_t FROM premise_t
ORDER BY ORDER BY
description description
@ -298,6 +303,8 @@ def insert_premise(user, token_info, **args):
v_street = body["street"] v_street = body["street"]
v_zip = body["zip"] v_zip = body["zip"]
v_city = body["city"] v_city = body["city"]
v_minus_area = body["minus_area"]
v_account = body["account"]
return dbInsert(user, token_info, { return dbInsert(user, token_info, {
"statement": """ "statement": """
INSERT INTO premise_t INSERT INTO premise_t
@ -306,11 +313,15 @@ INSERT INTO premise_t
,street ,street
,zip ,zip
,city ,city
,minus_area
,account
) VALUES ( ) VALUES (
%s %s
,%s ,%s
,%s ,%s
,%s ,%s
,%s
,%s
) )
RETURNING * RETURNING *
""", """,
@ -319,6 +330,8 @@ INSERT INTO premise_t
,v_street ,v_street
,v_zip ,v_zip
,v_city ,v_city
,v_minus_area
,v_account
] ]
}) })
except KeyError as e: except KeyError as e:
@ -335,6 +348,8 @@ SELECT
,street ,street
,zip ,zip
,city ,city
,minus_area
,account
FROM premise_t FROM premise_t
WHERE id = %s WHERE id = %s
""", """,
@ -349,6 +364,7 @@ def update_premise(user, token_info, premiseId=None, **args):
v_street = body["street"] v_street = body["street"]
v_zip = body["zip"] v_zip = body["zip"]
v_city = body["city"] v_city = body["city"]
v_minus_area = body["minus_area"]
return dbUpdate(user, token_info, { return dbUpdate(user, token_info, {
"statement": """ "statement": """
UPDATE premise_t UPDATE premise_t
@ -357,6 +373,7 @@ UPDATE premise_t
,street = %s ,street = %s
,zip = %s ,zip = %s
,city = %s ,city = %s
,minus_area = %s
WHERE id = %s WHERE id = %s
RETURNING * RETURNING *
""", """,
@ -365,6 +382,7 @@ UPDATE premise_t
v_street, v_street,
v_zip, v_zip,
v_city, v_city,
v_minus_area,
premiseId premiseId
] ]
}) })
@ -373,6 +391,26 @@ UPDATE premise_t
raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e)) raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e))
def get_premise_by_account(user, token_info, accountId=None):
return dbGetMany(user, token_info, {
"statement": """
SELECT
id
,description
,street
,zip
,city
,minus_area
,account
FROM premise_t
WHERE account = %s
ORDER BY
description
""",
"params": (accountId, )
}
)
def get_flats(user, token_info): def get_flats(user, token_info):
return dbGetMany(user, token_info, { return dbGetMany(user, token_info, {
"statement": """ "statement": """
@ -484,6 +522,9 @@ SELECT
,flat_no ,flat_no
FROM flat_t FROM flat_t
WHERE premise = %s WHERE premise = %s
ORDER BY
premise
,description
""", """,
"params": (premiseId, ) "params": (premiseId, )
} }
@ -651,6 +692,9 @@ SELECT
,flat ,flat
FROM overhead_advance_flat_mapping_t FROM overhead_advance_flat_mapping_t
WHERE overhead_advance = %s WHERE overhead_advance = %s
ORDER BY
overhead_advance
,flat
""", """,
"params": (overhead_advanceId, ) "params": (overhead_advanceId, )
} }
@ -665,6 +709,9 @@ SELECT
,flat ,flat
FROM overhead_advance_flat_mapping_t FROM overhead_advance_flat_mapping_t
WHERE flat = %s WHERE flat = %s
ORDER BY
overhead_advance
,flat
""", """,
"params": (flatId, ) "params": (flatId, )
} }
@ -761,6 +808,9 @@ SELECT
,premise ,premise
FROM parking_t FROM parking_t
WHERE premise = %s WHERE premise = %s
ORDER BY
premise
,description
""", """,
"params": (premiseId, ) "params": (premiseId, )
} }
@ -773,6 +823,7 @@ SELECT
id id
,description ,description
,premise ,premise
,area
FROM commercial_premise_t FROM commercial_premise_t
ORDER BY ORDER BY
premise premise
@ -787,21 +838,25 @@ def insert_commercial_premise(user, token_info, **args):
body = args["body"] body = args["body"]
v_description = body["description"] v_description = body["description"]
v_premise = body["premise"] v_premise = body["premise"]
v_area = body["area"]
return dbInsert(user, token_info, { return dbInsert(user, token_info, {
"statement": """ "statement": """
INSERT INTO commercial_premise_t INSERT INTO commercial_premise_t
( (
description description
,premise ,premise
,area
) VALUES ( ) VALUES (
%s %s
,%s ,%s
,%s
) )
RETURNING * RETURNING *
""", """,
"params": [ "params": [
v_description v_description
,v_premise ,v_premise
,v_area
] ]
}) })
except KeyError as e: except KeyError as e:
@ -816,6 +871,7 @@ SELECT
id id
,description ,description
,premise ,premise
,area
FROM commercial_premise_t FROM commercial_premise_t
WHERE id = %s WHERE id = %s
""", """,
@ -828,18 +884,21 @@ def update_commercial_premise(user, token_info, commercial_premiseId=None, **arg
body = args["body"] body = args["body"]
v_description = body["description"] v_description = body["description"]
v_premise = body["premise"] v_premise = body["premise"]
v_area = body["area"]
return dbUpdate(user, token_info, { return dbUpdate(user, token_info, {
"statement": """ "statement": """
UPDATE commercial_premise_t UPDATE commercial_premise_t
SET SET
description = %s description = %s
,premise = %s ,premise = %s
,area = %s
WHERE id = %s WHERE id = %s
RETURNING * RETURNING *
""", """,
"params": [ "params": [
v_description, v_description,
v_premise, v_premise,
v_area,
commercial_premiseId commercial_premiseId
] ]
}) })
@ -855,8 +914,12 @@ SELECT
id id
,description ,description
,premise ,premise
,area
FROM commercial_premise_t FROM commercial_premise_t
WHERE premise = %s WHERE premise = %s
ORDER BY
premise
,description
""", """,
"params": (premiseId, ) "params": (premiseId, )
} }
@ -988,6 +1051,9 @@ SELECT
,enddate ,enddate
FROM tenancy_t FROM tenancy_t
WHERE tenant = %s WHERE tenant = %s
ORDER BY
description
,startdate
""", """,
"params": (tenantId, ) "params": (tenantId, )
} }
@ -1007,6 +1073,9 @@ SELECT
,enddate ,enddate
FROM tenancy_t FROM tenancy_t
WHERE flat = %s WHERE flat = %s
ORDER BY
description
,startdate
""", """,
"params": (flatId, ) "params": (flatId, )
} }
@ -1026,6 +1095,9 @@ SELECT
,enddate ,enddate
FROM tenancy_t FROM tenancy_t
WHERE parking = %s WHERE parking = %s
ORDER BY
description
,startdate
""", """,
"params": (parkingId, ) "params": (parkingId, )
} }
@ -1045,6 +1117,9 @@ SELECT
,enddate ,enddate
FROM tenancy_t FROM tenancy_t
WHERE commercial_premise = %s WHERE commercial_premise = %s
ORDER BY
description
,startdate
""", """,
"params": (commercial_premiseId, ) "params": (commercial_premiseId, )
} }
@ -1240,6 +1315,7 @@ def get_account_entry_categorys(user, token_info):
SELECT SELECT
id id
,description ,description
,considerMinusArea
,overhead_relevant ,overhead_relevant
FROM account_entry_category_t FROM account_entry_category_t
ORDER BY ORDER BY
@ -1253,21 +1329,25 @@ def insert_account_entry_category(user, token_info, **args):
try: try:
body = args["body"] body = args["body"]
v_description = body["description"] v_description = body["description"]
v_considerMinusArea = body["considerMinusArea"]
v_overhead_relevant = body["overhead_relevant"] v_overhead_relevant = body["overhead_relevant"]
return dbInsert(user, token_info, { return dbInsert(user, token_info, {
"statement": """ "statement": """
INSERT INTO account_entry_category_t INSERT INTO account_entry_category_t
( (
description description
,considerMinusArea
,overhead_relevant ,overhead_relevant
) VALUES ( ) VALUES (
%s %s
,%s ,%s
,%s
) )
RETURNING * RETURNING *
""", """,
"params": [ "params": [
v_description v_description
,v_considerMinusArea
,v_overhead_relevant ,v_overhead_relevant
] ]
}) })
@ -1282,6 +1362,7 @@ def get_account_entry_category(user, token_info, account_entry_categoryId=None):
SELECT SELECT
id id
,description ,description
,considerMinusArea
,overhead_relevant ,overhead_relevant
FROM account_entry_category_t FROM account_entry_category_t
WHERE id = %s WHERE id = %s
@ -1300,11 +1381,13 @@ SELECT
,description ,description
,account ,account
,created_at ,created_at
,fiscal_year
,amount ,amount
,document_no
,account_entry_category ,account_entry_category
FROM account_entry_t FROM account_entry_t
ORDER BY ORDER BY
amount created_at
""", """,
"params": () "params": ()
} }
@ -1316,7 +1399,9 @@ def insert_account_entry(user, token_info, **args):
v_description = body["description"] v_description = body["description"]
v_account = body["account"] v_account = body["account"]
v_created_at = body["created_at"] v_created_at = body["created_at"]
v_fiscal_year = body["fiscal_year"]
v_amount = body["amount"] v_amount = body["amount"]
v_document_no = body["document_no"]
v_account_entry_category = body["account_entry_category"] v_account_entry_category = body["account_entry_category"]
return dbInsert(user, token_info, { return dbInsert(user, token_info, {
"statement": """ "statement": """
@ -1325,7 +1410,9 @@ INSERT INTO account_entry_t
description description
,account ,account
,created_at ,created_at
,fiscal_year
,amount ,amount
,document_no
,account_entry_category ,account_entry_category
) VALUES ( ) VALUES (
%s %s
@ -1333,6 +1420,8 @@ INSERT INTO account_entry_t
,%s ,%s
,%s ,%s
,%s ,%s
,%s
,%s
) )
RETURNING * RETURNING *
""", """,
@ -1340,7 +1429,9 @@ INSERT INTO account_entry_t
v_description v_description
,v_account ,v_account
,v_created_at ,v_created_at
,v_fiscal_year
,v_amount ,v_amount
,v_document_no
,v_account_entry_category ,v_account_entry_category
] ]
}) })
@ -1357,7 +1448,9 @@ SELECT
,description ,description
,account ,account
,created_at ,created_at
,fiscal_year
,amount ,amount
,document_no
,account_entry_category ,account_entry_category
FROM account_entry_t FROM account_entry_t
WHERE id = %s WHERE id = %s
@ -1376,10 +1469,14 @@ SELECT
,description ,description
,account ,account
,created_at ,created_at
,fiscal_year
,amount ,amount
,document_no
,account_entry_category ,account_entry_category
FROM account_entry_t FROM account_entry_t
WHERE account = %s WHERE account = %s
ORDER BY
created_at
""", """,
"params": (accountId, ) "params": (accountId, )
} }
@ -1393,10 +1490,14 @@ SELECT
,description ,description
,account ,account
,created_at ,created_at
,fiscal_year
,amount ,amount
,document_no
,account_entry_category ,account_entry_category
FROM account_entry_t FROM account_entry_t
WHERE account_entry_category = %s WHERE account_entry_category = %s
ORDER BY
created_at
""", """,
"params": (account_entry_categoryId, ) "params": (account_entry_categoryId, )
} }
@ -1411,6 +1512,8 @@ SELECT
,tenant ,tenant
,note ,note
FROM note_t FROM note_t
ORDER BY
created_at
""", """,
"params": () "params": ()
} }
@ -1474,6 +1577,8 @@ SELECT
,note ,note
FROM note_t FROM note_t
WHERE tenant = %s WHERE tenant = %s
ORDER BY
created_at
""", """,
"params": (tenantId, ) "params": (tenantId, )
} }

View File

@ -129,6 +129,14 @@ SELECT
#end for #end for
FROM ${table.name}_t FROM ${table.name}_t
WHERE ${column.name} = %s 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, ) "params": (${column.name}Id, )
} }

View File

@ -299,6 +299,28 @@ paths:
$ref: '#/components/schemas/premise' $ref: '#/components/schemas/premise'
security: security:
- jwt: ['secret'] - jwt: ['secret']
/v1/premises/account/{accountId}:
get:
tags: [ "premise", "account" ]
summary: Return premise by $account
operationId: methods.get_premise_by_account
parameters:
- name: accountId
in: path
required: true
schema:
type: integer
responses:
'200':
description: premise response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/premise'
security:
- jwt: ['secret']
/v1/flats: /v1/flats:
get: get:
tags: [ "flat" ] tags: [ "flat" ]
@ -1509,6 +1531,45 @@ paths:
$ref: '#/components/schemas/tenant_with_saldo' $ref: '#/components/schemas/tenant_with_saldo'
security: security:
- jwt: ['secret'] - 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']
components: components:
@ -1583,6 +1644,10 @@ components:
type: string type: string
city: city:
type: string type: string
minus_area:
type: number
account:
type: integer
flat: flat:
description: flat description: flat
type: object type: object
@ -1651,6 +1716,9 @@ components:
premise: premise:
type: integer type: integer
nullable: true nullable: true
area:
type: number
nullable: true
tenancy: tenancy:
description: tenancy description: tenancy
type: object type: object
@ -1713,6 +1781,8 @@ components:
type: integer type: integer
description: description:
type: string type: string
considerMinusArea:
type: boolean
overhead_relevant: overhead_relevant:
type: boolean type: boolean
account_entry: account_entry:
@ -1727,8 +1797,13 @@ components:
type: integer type: integer
created_at: created_at:
type: string type: string
fiscal_year:
type: integer
amount: amount:
type: number type: number
document_no:
type: integer
nullable: true
account_entry_category: account_entry_category:
type: integer type: integer
note: note:

View File

@ -5,6 +5,6 @@ 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) 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}

54
cli/AccountStatement.py Normal file
View File

@ -0,0 +1,54 @@
from db import dbGetMany, dbGetOne
from loguru import logger
import datetime
import iso8601
from utils import getParam
from Cheetah.Template import Template
def perform(dbh, params):
year = getParam(params, 'year', datetime.datetime.today().year)
accountEntries = dbGetMany(
dbh,
{
"statement": "SELECT * FROM account_statement_v WHERE fiscal_year = %(year)s",
"params": {
'year': year
}
}
)
overview = dbGetMany(
dbh,
{
"statement": "select sum(amount), category from account_statement_v where fiscal_year = %(year)s group by category",
"params": {
'year': year
}
}
)
sum_related = dbGetOne(
dbh,
{
"statement": "select coalesce(sum(amount), 0::numeric(10,2)) as sum from account_statement_v where fiscal_year = %(year)s and category != 'nicht abrechenbare Positionen'",
"params": {
'year': year
}
}
)
sum_unrelated = dbGetOne(
dbh,
{
"statement": "select coalesce(sum(amount), 0::numeric(10,2)) as sum from account_statement_v where fiscal_year = %(year)s and category = 'nicht abrechenbare Positionen'",
"params": {
'year': year
}
}
)
template = getParam(params, 'template', 'accountStatement.tmpl')
input = { 'year': year, 'entries': accountEntries, 'overview': overview, 'related': sum_related, 'unrelated': sum_unrelated }
tmpl = Template(file=template, searchList=[ input ])
print(tmpl)

51
cli/AccountStatement.sh Executable file
View File

@ -0,0 +1,51 @@
#!/bin/bash
SHOW="0"
PRINT="0"
while getopts "y:sphP:" flag
do
case "${flag}" in
h)
echo "y ... year for statement";
echo "s ... show output using evince";
echo "p ... print output";
echo "P ... printer name";
exit 1;
;;
y)
YEAR=${OPTARG}
;;
s)
SHOW="1"
;;
p)
PRINT="1"
;;
P)
PRINTER="-P ${OPTARG}"
;;
esac
done
if [ "$YEAR" = "" ]; then
echo "give year for statement as argument"
exit 1
fi
python3.10 hv2cli.py -o AccountStatement -p '{"year":'$YEAR'}' > ./output/$YEAR.tex
pushd ./output
pdflatex $YEAR.tex
pdflatex $YEAR.tex
pdflatex $YEAR.tex
if [ "$SHOW" = "1" ]; then
evince $YEAR.pdf
fi
if [ "$PRINT" = "1" ]; then
lpr $PRINTER $YEAR.pdf
fi
popd

View File

@ -1,10 +1,21 @@
from db import dbGetMany, dbGetOne from db import dbGetMany, dbGetOne
from loguru import logger from loguru import logger
from decimal import Decimal from decimal import Decimal
import datetime
import iso8601
def perform(dbh, params): def perform(dbh, params):
createdAt = params['created_at'] 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": () }) tenants = dbGetMany(dbh, { "statement": "SELECT * FROM tenant_t", "params": () })
for tenant in tenants: for tenant in tenants:
@ -96,11 +107,11 @@ def perform(dbh, params):
accountEntry = dbGetOne(dbh, { accountEntry = dbGetOne(dbh, {
"statement": """ "statement": """
INSERT INTO account_entry_t INSERT INTO account_entry_t
(description, account, created_at, amount, account_entry_category) (description, account, created_at, fiscal_year, amount, account_entry_category)
VALUES (%s, %s, %s, %s, (SELECT id FROM account_entry_category_t WHERE description = %s)) VALUES (%s, %s, %s, %s, %s, (SELECT id FROM account_entry_category_t WHERE description = %s))
RETURNING id RETURNING id
""", """,
"params": (request['description'], request['account'], request['created_at'], request['amount'], request['category']) "params": (request['description'], request['account'], request['created_at'], year, request['amount'], request['category'])
} }
) )
logger.info(f" account entry entered with id {accountEntry['id']}") 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 (coalesce(sum(ae.amount), 0::numeric(10,2)) * -1) 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 where fiscal_year = %(year)s) and
ae.fiscal_year = %(year)s and
p.id = %(premise)s
group by category, considerminusarea
union
select 120 as sum,
'16. 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"./output/{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"./output/{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))

55
cli/OverheadAccounts.sh Executable file
View File

@ -0,0 +1,55 @@
#!/bin/bash
PRINT="0"
while getopts "y:phP:" flag
do
case "${flag}" in
h)
echo "y ... year for statement";
echo "p ... print output";
echo "P ... printer name";
exit 1;
;;
y)
YEAR=${OPTARG}
;;
p)
PRINT="1"
;;
P)
PRINTER="-P ${OPTARG}"
;;
esac
done
if [ "$YEAR" = "" ]; then
echo "give year for statement as argument"
exit 1
fi
python3.10 hv2cli.py -o OverheadAccounts -p '{"year":'$YEAR'}'
pushd ./output
for I in letter-*.tex; do
pdflatex $I
pdflatex $I
pdflatex $I
done
for I in overview-*.tex; do
pdflatex $I
pdflatex $I
pdflatex $I
done
if [ "$PRINT" = "1" ]; then
for I in letter-*.pdf; do
lpr $PRINTER $I
done
for I in overview-*.pdf; do
lpr $PRINTER $I
done
fi
popd

59
cli/accountStatement.tmpl Normal file
View File

@ -0,0 +1,59 @@
\documentclass[10pt]{article}
\usepackage{german}
\usepackage{eurosym}
\usepackage[a4paper, landscape=true, left=2cm, right=2cm, top=2cm]{geometry}
\usepackage{icomma}
\usepackage{longtable}
\usepackage{color}
\setlength{\parindent}{0pt}
\begin{document}
\subsection*{Account Statement $year}
\begin{longtable}{|r|r|p{6cm}|r|r|r|p{3cm}|r|p{5cm}|}
\hline \textcolor{blue}{\bf{ID}} & \textcolor{blue}{\bf{Date}} & \textcolor{blue}{\bf{Description}} & \textcolor{blue}{\bf{Amount}} & \textcolor{blue}{\bf{DocNo}} & \textcolor{blue}{\bf{Year}} & \textcolor{blue}{\bf{Category}} & \textcolor{blue}{\bf{IsRef}} & \textcolor{blue}{\bf{BaseAccount}} \\ \hline \hline
\endfirsthead
\hline \textcolor{blue}{\bf{ID}} & \textcolor{blue}{\bf{Date}} & \textcolor{blue}{\bf{Description}} & \textcolor{blue}{\bf{Amount}} & \textcolor{blue}{\bf{DocNo}} & \textcolor{blue}{\bf{Year}} & \textcolor{blue}{\bf{Category}} & \textcolor{blue}{\bf{IsRef}} & \textcolor{blue}{\bf{BaseAccount}} \\ \hline \hline
\endhead
\multicolumn{9}{r}{\em{to be continued}}
\endfoot
\endlastfoot
#for $entry in $entries
\tt{$entry['id']} & \tt{$entry['created_at']} & \tt{$entry['description']} & #slurp
#if $entry['amount'] <= 0
\textcolor{red}{#slurp
#end if
\tt{$entry['amount']} \,\euro{}#slurp
#if $entry['amount'] <= 0
}#slurp
#end if
& \tt{$entry['document_no']} & \tt{$entry['fiscal_year']} & \tt{$entry['category']} & \tt{$entry['is_reference']} & \tt{{$entry['base_account'].replace('_', ' ') }} \\ \hline
#end for
\end{longtable}
\pagebreak
\subsection*{Overviews}
\begin{tabular}{|l|r|} \hline
\hline \textcolor{blue}{\bf{category}} & \textcolor{blue}{\bf{sum}} \\
#for $entry in $overview
\hline \tt{$entry['category']} & #slurp
#if $entry['sum'] <= 0
\textcolor{red}{#slurp
#end if
\tt{$entry['sum']} \,\euro{}#slurp
#if $entry['sum'] <= 0
}#slurp
#end if
\\
#end for
\hline \hline related & \tt{$related['sum']} \,\euro{} \\
\hline unrelated & \tt{$unrelated['sum']} \,\euro{} \\
\hline \hline
\end{tabular}
\end{document}

View File

@ -0,0 +1,47 @@
\documentclass[12pt]{article}
\renewcommand{\familydefault}{\sfdefault}
\usepackage{german}
\usepackage{eurosym}
\usepackage[a4paper, left=2cm, right=2cm, top=2cm]{geometry}
\usepackage{icomma}
\setlength{\parindent}{0pt}
\begin{document}
\subsection*{Aufstellung}
über die Ermittlung der Betriebskosten gemäß §27 II. BV
\vspace{15mm}
\begin{tabular}{ll}
Objekt & $details['description'] \\
Jahr & $year \\
Eigent"umer & Nober Grundbesitz GmbH \\
\end{tabular}
\addvspace{1cm}
\begin{tabular}{|p{7cm}|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 \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
% \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
% \hline \multicolumn{4}{c}{ } \\
\hline Anteil pro Monat und m\textsuperscript{2} & \tt{#echo '%.10f' % $part_by_montharea #\,\euro{}} & & \\
\hline
\end{tabular}
\end{document}

View File

@ -12,7 +12,7 @@ def execDatabaseOperation(dbh, func, params):
cur = None cur = None
try: try:
with dbh.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur: with dbh.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
params["params"] = [ v if not v=='' else None for v in params["params"] ] # params["params"] = [ v if not v=='' else None for v in params["params"] ]
logger.debug("edo: {}".format(str(params))) logger.debug("edo: {}".format(str(params)))
return func(cur, params) return func(cur, params)
except psycopg2.Error as err: except psycopg2.Error as err:
@ -21,6 +21,7 @@ def execDatabaseOperation(dbh, func, params):
def _opGetMany(cursor, params): def _opGetMany(cursor, params):
#logger.warning(f"{params=}")
items = [] items = []
cursor.execute(params["statement"], params["params"]) cursor.execute(params["statement"], params["params"])
for itemObj in cursor: for itemObj in cursor:

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")

View File

@ -8,6 +8,36 @@ import argparse
import importlib import importlib
import sys 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_USER = ""
DB_PASS = "" DB_PASS = ""
DB_HOST = "" DB_HOST = ""
@ -19,33 +49,15 @@ 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('./config/dbconfig.ini') config.read(args.config)
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"]
DB_NAME = config["database"]["name"] DB_NAME = config["database"]["name"]
parser = argparse.ArgumentParser(description="hv2cli.py")
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")
args = parser.parse_args()
operation = args.operation
params = json.loads(args.params)
logLevel = args.verbosity
logger.remove() logger.remove()
logger.add(sys.stderr, colorize=True, level=logLevel) logger.add(sys.stderr, colorize=(not noColorize), level=logLevel)
dbh = None dbh = None

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}

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.',

View File

@ -32,7 +32,9 @@
{ "name": "description", "sqltype": "varchar(128)", "selector": 0, "unique": true }, { "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 }
] ]
}, },
{ {
@ -78,7 +80,8 @@
"name": "commercial_premise", "name": "commercial_premise",
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)", "selector": 1 }, { "name": "description", "sqltype": "varchar(128)", "selector": 1 },
{ "name": "premise", "sqltype": "integer", "foreignkey": true, "selector": 0 } { "name": "premise", "sqltype": "integer", "foreignkey": true, "selector": 0 },
{ "name": "area", "sqltype": "numeric(10,2)", "notnull": false }
], ],
"tableConstraints": [ "tableConstraints": [
"unique(description, premise)" "unique(description, premise)"
@ -126,7 +129,8 @@
"immutable": true, "immutable": true,
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(128)", "notnull": true, "selector": 0, "unique": true }, { "name": "description", "sqltype": "varchar(128)", "notnull": true, "selector": 0, "unique": true },
{ "name": "overhead_relevant", "sqltype": "boolean", "notnull": true, "default": "true" } { "name": "considerMinusArea", "sqltype": "boolean", "notnull": true, "default": true },
{ "name": "overhead_relevant", "sqltype": "boolean", "notnull": true, "default": true }
] ]
}, },
{ {
@ -135,8 +139,10 @@
"columns": [ "columns": [
{ "name": "description", "sqltype": "varchar(1024)", "notnull": true }, { "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, "selector": 0 }, { "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 } { "name": "account_entry_category", "sqltype": "integer", "notnull": true, "foreignkey": true }
], ],
"tableConstraints": [ "tableConstraints": [
@ -147,7 +153,7 @@
"name": "note", "name": "note",
"immutable": true, "immutable": true,
"columns": [ "columns": [
{ "name": "created_at", "sqltype": "timestamp", "notnull": true, "default": "now()" }, { "name": "created_at", "sqltype": "timestamp", "notnull": true, "default": "now()", "selector": 0 },
{ "name": "tenant", "sqltype": "integer", "notnull": true, "foreignkey": true }, { "name": "tenant", "sqltype": "integer", "notnull": true, "foreignkey": true },
{ "name": "note", "sqltype": "varchar(4096)", "notnull": true } { "name": "note", "sqltype": "varchar(4096)", "notnull": true }
] ]

View File

@ -0,0 +1,23 @@
create or replace view account_statement_v as
select ae.id as id,
ae.description as description,
ae.created_at::timestamp::date as created_at,
ae.amount as amount,
ae.document_no as document_no,
ae.fiscal_year as fiscal_year,
aec.description as category,
ac.description as account,
ae.is_reference as is_reference,
bac.description as base_account
from joined_account_entry_v ae,
account_entry_category_t aec,
account_t ac,
account_t bac
where ae.account_entry_category = aec.id and
ae.account = ac.id and
ae.base_account = bac.id and
ac.id = 1000
order by created_at;
grant select on account_statement_v to hv2;

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;

126
schema/changes02.sql Normal file
View File

@ -0,0 +1,126 @@
CREATE TABLE account_entry_reference_t (
id serial not null primary key,
account integer not null references account_t (id),
account_entry integer not null references account_entry_t (id)
);
CREATE OR REPLACE VIEW joined_account_entry_v AS
SELECT ae.id as id,
ae.description as description,
ae.account as account,
ae.created_at as created_at,
ae.amount as amount,
ae.account_entry_category as account_entry_category,
ae.document_no as document_no,
ae.fiscal_year as fiscal_year,
false as is_reference,
0 as base_account
FROM account_entry_t ae
UNION
SELECT ae.id as id,
ae.description as description,
aer.account as account,
ae.created_at as created_at,
ae.amount as amount,
ae.account_entry_category as account_entry_category,
ae.document_no as document_no,
ae.fiscal_year as fiscal_year,
true as is_reference,
ae.account as base_account
FROM account_entry_t ae,
account_entry_reference_t aer
WHERE ae.id = aer.account_entry;
update account_t set description = 'fallback_account' where id = 33;
delete from account_t where id = 32;
insert into account_t (id, description) values (1000, 'ledger');
update account_entry_t set amount = amount * -1.0 where account=33;
update account_entry_t set amount = amount * -1.0 where account in (34,35,36,37);
DO $$
DECLARE
entry_id integer;
BEGIN
RAISE NOTICE 'Start migration';
FOR entry_id IN
SELECT id
FROM account_entry_t
WHERE account IN (33,34,35,36,37)
LOOP
RAISE NOTICE 'About to migrate entry %', entry_id;
INSERT INTO account_entry_reference_t (account, account_entry) VALUES (1000, entry_id);
END LOOP;
END;
$$;
update account_entry_t
set description = 'Miete ' || extract(year from created_at)::text || ' ' || to_char(to_date(extract(month from created_at)::text, 'MM'), 'Month')
where account_entry_category = 2 and description = 'Miete';
DO $$
DECLARE
entry RECORD;
tenant RECORD;
BEGIN
RAISE NOTICE 'Start migration of tenant accounts';
FOR entry IN
SELECT id, description, account
FROM account_entry_t
WHERE account_entry_category = 2
LOOP
RAISE NOTICE 'About to migrate entry %', entry.id;
INSERT INTO account_entry_reference_t (account, account_entry) VALUES (1000, entry.id);
SELECT *
INTO tenant
FROM tenant_t
WHERE account = entry.account;
RAISE NOTICE 'Tenant: % %', tenant.firstname, tenant.lastname;
UPDATE account_entry_t
SET description = description || ' ' || tenant.firstname || ' ' || tenant.lastname
WHERE id = entry.id;
END LOOP;
END;
$$;
CREATE OR REPLACE FUNCTION maintain_ledger()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
tenant RECORD;
adjusted_description text;
BEGIN
IF ((NEW.description = 'Miete') AND (NEW.account_entry_category = 2)) THEN
SELECT firstname, lastname
INTO tenant
FROM tenant_t
WHERE account = NEW.account;
adjusted_description := 'Miete ' || extract(year from NEW.created_at)::text || ' ' || to_char(to_date(extract(month from NEW.created_at)::text, 'MM'), 'Month') || tenant.firstname || ' ' || tenant.lastname;
UPDATE account_entry_t
SET description = adjusted_description
WHERE id = NEW.id;
END IF;
INSERT INTO account_entry_reference_t (account, account_entry) VALUES (1000, NEW.id);
RETURN NEW;
END;
$$;
CREATE TRIGGER maintain_ledger_trigger
AFTER INSERT ON account_entry_t
FOR EACH ROW
WHEN (NEW.account != 1000 AND NEW.account_entry_category NOT IN (3, 4, 29))
EXECUTE FUNCTION maintain_ledger();
grant select, update on account_entry_reference_t_id_seq to hv2;
grant insert on account_entry_reference_t to hv2;
grant update on account_entry_t to hv2;

View File

@ -39,6 +39,8 @@ CREATE TABLE premise_t (
,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, INSERT, UPDATE ON premise_t TO hv2;
@ -90,6 +92,7 @@ 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) ,unique(description, premise)
); );
@ -137,7 +140,8 @@ GRANT SELECT, UPDATE ON tenancy_fee_mapping_t_id_seq TO hv2;
CREATE TABLE account_entry_category_t ( CREATE TABLE account_entry_category_t (
id serial not null primary key id serial not null primary key
,description varchar(128) not null unique ,description varchar(128) not null unique
,overhead_relevant boolean not null default true ,considerMinusArea boolean not null default True
,overhead_relevant boolean not null default True
); );
GRANT SELECT, INSERT ON account_entry_category_t TO hv2; GRANT SELECT, INSERT ON account_entry_category_t TO hv2;
@ -145,10 +149,12 @@ GRANT SELECT, UPDATE ON account_entry_category_t_id_seq TO hv2;
CREATE TABLE account_entry_t ( CREATE TABLE account_entry_t (
id serial not null primary key id serial not null primary key
,description varchar(128) not null ,description varchar(1024) not null
,account integer not null references account_t (id) ,account integer not null references account_t (id)
,created_at timestamp not null default now() ,created_at timestamp not null default now()
,fiscal_year integer not null
,amount numeric(10,2) not null ,amount numeric(10,2) not null
,document_no integer unique
,account_entry_category integer not null references account_entry_category_t (id) ,account_entry_category integer not null references account_entry_category_t (id)
,unique(description, account, created_at) ,unique(description, account, created_at)
); );

View File

@ -1,24 +1,28 @@
<div id="firstBlock"> <div id="firstBlock">
<form (ngSubmit)="addAccountEntry()"> <form (ngSubmit)="addAccountEntry(accountEntryForm)" #accountEntryForm="ngForm">
<mat-form-field appearance="outline" id="addEntryfield"> <mat-form-field appearance="outline" id="addEntryfield">
<mat-label>Datum</mat-label> <mat-label>Datum</mat-label>
<input matInput name="createdAt" [(ngModel)]="newAccountEntry.created_at" [matDatepicker]="createdAtPicker"/> <input matInput ngModel name="createdAt" [matDatepicker]="createdAtPicker"/>
<mat-datepicker-toggle matSuffix [for]="createdAtPicker"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="createdAtPicker"></mat-datepicker-toggle>
<mat-datepicker #createdAtPicker></mat-datepicker> <mat-datepicker #createdAtPicker></mat-datepicker>
</mat-form-field> </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"> <mat-form-field appearance="outline">
<mat-label>Kategorie</mat-label> <mat-label>Kategorie</mat-label>
<mat-select [(ngModel)]="newAccountEntry.account_entry_category" name="category" disabled="shallBeRentPayment"> <mat-select ngModel name="category" [formControl]="presetCategory" >
<mat-option *ngFor="let p of accountEntryCategories" [value]="p.id">{{p.description}}</mat-option> <mat-option *ngFor="let p of accountEntryCategories" [value]="p.id">{{p.description}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Betrag (€)</mat-label> <mat-label>Betrag (€)</mat-label>
<input matInput type="number" name="amount" [(ngModel)]="newAccountEntry.amount"/> <input matInput type="number" name="amount" ngModel/>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Beschreibung</mat-label> <mat-label>Beschreibung</mat-label>
<input matInput name="description" [(ngModel)]="newAccountEntry.description"/> <input matInput name="description" [formControl]="presetDescription" ngModel/>
</mat-form-field> </mat-form-field>
<button #addAccountEntryButton type="submit" mat-raised-button color="primary">Buchung speichern</button> <button #addAccountEntryButton type="submit" mat-raised-button color="primary">Buchung speichern</button>
</form> </form>
@ -29,23 +33,31 @@ Saldo: {{saldo?.saldo | number:'1.2-2'}} €
<div id="secondBlock"> <div id="secondBlock">
<table mat-table [dataSource]="accountEntriesDataSource" #zftable> <table mat-table [dataSource]="accountEntriesDataSource" #zftable>
<ng-container matColumnDef="createdAt"> <ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef>Datum</th> <th mat-header-cell *matHeaderCellDef >Datum</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.created_at | date}}</td> <td mat-cell *matCellDef="let element">{{element.rawAccountEntry.created_at | date}}</td>
</ng-container> </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"> <ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Beschreibung</th> <th mat-header-cell *matHeaderCellDef>Beschreibung</th>
<td mat-cell *matCellDef="let element">{{element.rawAccountEntry.description}}</td> <td mat-cell *matCellDef="let element">{{element.rawAccountEntry.description}}</td>
</ng-container> </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"> <ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>Betrag</th> <th mat-header-cell *matHeaderCellDef>Betrag</th>
<td mat-cell *matCellDef="let element" class="rightaligned">{{element.rawAccountEntry.amount | number:'1.2-2'}} €</td> <td mat-cell *matCellDef="let element" class="rightaligned">{{element.rawAccountEntry.amount | number:'1.2-2'}} €</td>
</ng-container> </ng-container>
<ng-container matColumnDef="category"> <ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>Kategorie</th> <th mat-header-cell *matHeaderCellDef >Kategorie</th>
<td mat-cell *matCellDef="let element">{{element.accountEntryCategory}}</td> <td mat-cell *matCellDef="let element">{{element.accountEntryCategory}}</td>
</ng-container> </ng-container>
<ng-container matColumnDef="overhead_relevant"> <ng-container matColumnDef="overhead_relevant">
<th mat-header-cell *matHeaderCellDef>BK relevant</th> <th mat-header-cell *matHeaderCellDef >BK relevant</th>
<td mat-cell *matCellDef="let element">{{element.overheadRelevant}}</td> <td mat-cell *matCellDef="let element">{{element.overheadRelevant}}</td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="accountEntriesDisplayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="accountEntriesDisplayedColumns"></tr>

View File

@ -1,12 +1,14 @@
import { ViewFlags } from '@angular/compiler/src/core'; import { ViewFlags } from '@angular/compiler/src/core';
import { Component, Input, OnInit, OnChanges, ViewChild } from '@angular/core'; import { Component, Input, OnInit, OnChanges, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { MatExpansionPanel } from '@angular/material/expansion'; import { MatExpansionPanel } from '@angular/material/expansion';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { AccountEntryCategoryService, AccountEntryService, AccountService } from '../data-object-service'; import { AccountEntryCategoryService, AccountEntryService, AccountService } from '../data-object-service';
import { Account, AccountEntry, AccountEntryCategory, NULL_AccountEntry } from '../data-objects'; import { Account, AccountEntry, AccountEntryCategory, NULL_AccountEntry } from '../data-objects';
import { ErrorDialogService } from '../error-dialog.service';
import { ExtApiService } from '../ext-data-object-service'; import { ExtApiService } from '../ext-data-object-service';
import { Saldo } from '../ext-data-objects'; import { Saldo, UniqueNumber } from '../ext-data-objects';
import { MessageService } from '../message.service'; import { MessageService } from '../message.service';
@ -29,25 +31,28 @@ export class AccountComponent implements OnInit {
@Input() shallBeRentPayment: boolean @Input() shallBeRentPayment: boolean
@ViewChild('addAccountEntryButton') addAccountEntryButton: MatButton @ViewChild('addAccountEntryButton') addAccountEntryButton: MatButton
account: Account account: Account
accountEntries: DN_AccountEntry[] accountEntries: DN_AccountEntry[]
accountEntriesDataSource: MatTableDataSource<DN_AccountEntry> accountEntriesDataSource: MatTableDataSource<DN_AccountEntry>
accountEntriesDisplayedColumns: string[] = [ "description", "amount", "createdAt", "category", "overhead_relevant" ] accountEntriesDisplayedColumns: string[] = [ "description", "document_no", "amount", "createdAt", "fiscalYear", "category", "overhead_relevant" ]
saldo: Saldo saldo: Saldo
accountEntryCategories: AccountEntryCategory[] accountEntryCategories: AccountEntryCategory[]
accountEntryCategoriesMap: Map<number, AccountEntryCategory> accountEntryCategoriesMap: Map<number, AccountEntryCategory>
accountEntryCategoriesInverseMap: Map<string, AccountEntryCategory> accountEntryCategoriesInverseMap: Map<string, AccountEntryCategory>
newAccountEntry: AccountEntry = NULL_AccountEntry presetFiscalYear: FormControl
presetCategory: FormControl
presetDescription: FormControl
constructor( constructor(
private accountService: AccountService, private accountService: AccountService,
private accountEntryService: AccountEntryService, private accountEntryService: AccountEntryService,
private extApiService: ExtApiService, private extApiService: ExtApiService,
private accountEntryCategoryService: AccountEntryCategoryService, private accountEntryCategoryService: AccountEntryCategoryService,
private messageService: MessageService private messageService: MessageService,
private errorDialogService: ErrorDialogService
) { } ) { }
@ -87,20 +92,35 @@ export class AccountComponent implements OnInit {
} }
} }
async addAccountEntry(): Promise<void> { async addAccountEntry(formData: any): Promise<void> {
try { try {
this.addAccountEntryButton.disabled = true this.addAccountEntryButton.disabled = true
this.newAccountEntry.account = this.account.id this.messageService.add(`${JSON.stringify(formData.value, undefined, 4)}`)
this.messageService.add(`addAccountEntry: ${ JSON.stringify(this.newAccountEntry, undefined, 4) }`) let uniquenumber: UniqueNumber = await this.extApiService.getUniqueNumber();
this.newAccountEntry = await this.accountEntryService.postAccountEntry(this.newAccountEntry) this.messageService.add(`Got unique number as document_no: ${uniquenumber.number}`)
this.messageService.add(`New accountEntry created: ${this.newAccountEntry.id}`) let newAccountEntry: AccountEntry = {
this.newAccountEntry = { 'account': this.account.id, 'amount': undefined, 'created_at': '', 'description': '', 'id': 0, 'account_entry_category': 0 } description: this.presetDescription.value,
this.getAccountEntries() account: this.account.id,
} catch (err) { created_at: formData.value.createdAt,
this.messageService.add(`Error in addAccountEntry: ${JSON.stringify(err, undefined, 4)}`) fiscal_year: this.presetFiscalYear.value,
} finally { amount: formData.value.amount,
this.addAccountEntryButton.disabled = false id: 0,
document_no: uniquenumber.number,
account_entry_category: this.presetCategory.value
} }
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> { async getAccountEntryCategories(): Promise<void> {
@ -120,13 +140,29 @@ export class AccountComponent implements OnInit {
} }
private async init(): Promise<void> { private async init(): Promise<void> {
this.messageService.add(`AccountComponent.init start, account: ${this.selectedAccountId}`)
let currentDate = new Date()
let y = currentDate.getFullYear()
this.presetFiscalYear = new FormControl(y)
this.messageService.add(`AccountComponent.init a, account: ${this.selectedAccountId}`)
await this.getAccountEntryCategories()
if (this.shallBeRentPayment) {
this.messageService.add(`AccountComponent.init b, account: ${this.selectedAccountId}`)
this.presetCategory = new FormControl(this.accountEntryCategoriesInverseMap.get('Mietzahlung').id)
this.messageService.add(`AccountComponent.init c, account: ${this.selectedAccountId}`)
this.presetDescription = new FormControl("Miete")
this.messageService.add(`shall be rentpayment`)
this.messageService.add(`AccountComponent.init d, account: ${this.selectedAccountId}`)
} else {
this.presetCategory = new FormControl()
this.presetDescription = new FormControl()
}
this.messageService.add(`AccountComponent.init, account: ${this.selectedAccountId}`) this.messageService.add(`AccountComponent.init, account: ${this.selectedAccountId}`)
this.getAccount() this.getAccount()
await this.getAccountEntryCategories()
if (this.shallBeRentPayment) {
this.messageService.add('shall be rentpayment')
this.newAccountEntry.account_entry_category = this.accountEntryCategoriesInverseMap.get('Mietzahlung').id
}
} }
ngOnInit(): void { ngOnInit(): void {

View File

@ -19,6 +19,7 @@ import { FeeListComponent } from './fee-list/fee-list.component';
import { FeeDetailsComponent } from './fee-details/fee-details.component'; import { FeeDetailsComponent } from './fee-details/fee-details.component';
import { EnterPaymentComponent } from './enter-payment/enter-payment.component'; import { EnterPaymentComponent } from './enter-payment/enter-payment.component';
import { HomeComponent } from './home/home.component'; import { HomeComponent } from './home/home.component';
import { LedgerComponent } from './ledger/ledger.component';
const routes: Routes = [ const routes: Routes = [
@ -44,6 +45,7 @@ const routes: Routes = [
{ path: 'fee/:id', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] }, { path: 'fee/:id', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'fee', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] }, { path: 'fee', component: FeeDetailsComponent, canActivate: [ AuthGuardService ] },
{ path: 'enterPayment', component: EnterPaymentComponent, canActivate: [ AuthGuardService ] }, { path: 'enterPayment', component: EnterPaymentComponent, canActivate: [ AuthGuardService ] },
{ path: 'ledger', component: LedgerComponent, canActivate: [ AuthGuardService ] },
{ path: 'home', component: HomeComponent }, { path: 'home', component: HomeComponent },
{ path: 'logout', component: LogoutComponent }, { path: 'logout', component: LogoutComponent },
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },

View File

@ -47,7 +47,10 @@ import { AccountComponent } from './account/account.component';
import { NoteComponent } from './note/note.component' import { NoteComponent } from './note/note.component'
import { MatMomentDateModule, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; import { MatMomentDateModule, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';
import { EnterPaymentComponent } from './enter-payment/enter-payment.component'; import { EnterPaymentComponent } from './enter-payment/enter-payment.component';
import { HomeComponent } from './home/home.component' import { HomeComponent } from './home/home.component';
import { LedgerComponent } from './ledger/ledger.component';
import { ErrorDialogComponent } from './error-dialog/error-dialog.component'
import { MatSortModule } from '@angular/material/sort';
registerLocaleData(localeDe) registerLocaleData(localeDe)
@ -76,7 +79,9 @@ registerLocaleData(localeDe)
AccountComponent, AccountComponent,
NoteComponent, NoteComponent,
EnterPaymentComponent, EnterPaymentComponent,
HomeComponent HomeComponent,
LedgerComponent,
ErrorDialogComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -98,7 +103,8 @@ registerLocaleData(localeDe)
MatSelectModule, MatSelectModule,
MatDatepickerModule, MatDatepickerModule,
MatNativeDateModule, MatNativeDateModule,
MatExpansionModule MatExpansionModule,
MatSortModule
], ],
exports: [ exports: [
MatMomentDateModule MatMomentDateModule

View File

@ -22,6 +22,10 @@
<mat-option *ngFor="let p of premises" [value]="p.id">{{p.description}}</mat-option> <mat-option *ngFor="let p of premises" [value]="p.id">{{p.description}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </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> </div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button> <button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form> </form>

View File

@ -1,5 +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.3.96:8080";
export const serviceBaseUrl = "http://localhost:8080" // export const serviceBaseUrl = "http://localhost:8080"
export const authserviceBaseUrl = "https://authservice.hottis.de" export const authserviceBaseUrl = "https://authservice.hottis.de"
export const applicationId = "hv2" export const applicationId = "hv2"

View File

@ -130,6 +130,11 @@ export class PremiseService {
} }
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()
}
} }

View File

@ -52,6 +52,8 @@ export interface Premise {
street: string street: string
zip: string zip: string
city: string city: string
minus_area: number
account: number
} }
export const NULL_Premise: Premise = { export const NULL_Premise: Premise = {
id: 0 id: 0
@ -59,6 +61,8 @@ export const NULL_Premise: Premise = {
,street: '' ,street: ''
,zip: '' ,zip: ''
,city: '' ,city: ''
,minus_area: undefined
,account: undefined
} }
export interface Flat { export interface Flat {
@ -117,11 +121,13 @@ export interface CommercialPremise {
id: number id: number
description: string description: string
premise: number premise: number
area: number
} }
export const NULL_CommercialPremise: CommercialPremise = { export const NULL_CommercialPremise: CommercialPremise = {
id: 0 id: 0
,description: '' ,description: ''
,premise: undefined ,premise: undefined
,area: undefined
} }
export interface Tenancy { export interface Tenancy {
@ -176,11 +182,13 @@ export const NULL_TenancyFeeMapping: TenancyFeeMapping = {
export interface AccountEntryCategory { export interface AccountEntryCategory {
id: number id: number
description: string description: string
considerMinusArea: boolean
overhead_relevant: boolean overhead_relevant: boolean
} }
export const NULL_AccountEntryCategory: AccountEntryCategory = { export const NULL_AccountEntryCategory: AccountEntryCategory = {
id: 0 id: 0
,description: '' ,description: ''
,considerMinusArea: false
,overhead_relevant: false ,overhead_relevant: false
} }
@ -189,7 +197,9 @@ export interface AccountEntry {
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 account_entry_category: number
} }
export const NULL_AccountEntry: AccountEntry = { export const NULL_AccountEntry: AccountEntry = {
@ -197,7 +207,9 @@ export const NULL_AccountEntry: AccountEntry = {
,description: '' ,description: ''
,account: undefined ,account: undefined
,created_at: '' ,created_at: ''
,fiscal_year: undefined
,amount: undefined ,amount: undefined
,document_no: undefined
,account_entry_category: undefined ,account_entry_category: undefined
} }

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

@ -6,8 +6,8 @@ import { MessageService } from './message.service';
import { serviceBaseUrl } from './config'; import { serviceBaseUrl } from './config';
import { Fee, OverheadAdvance } from './data-objects'; import { Account, Fee, OverheadAdvance } from './data-objects';
import { Saldo, Tenant_with_Saldo } from './ext-data-objects'; import { Saldo, Tenant_with_Saldo, UniqueNumber } from './ext-data-objects';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@ -19,6 +19,11 @@ export class ExtApiService {
return this.http.get<OverheadAdvance[]>(`${serviceBaseUrl}/v1/overhead_advances/flat/${id}`).toPromise() 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[]> { async getFeeByTenancies(id: number): Promise<Fee[]> {
this.messageService.add(`ExtApiService: get fees by flat ${id}`); this.messageService.add(`ExtApiService: get fees by flat ${id}`);
return this.http.get<Fee[]>(`${serviceBaseUrl}/v1/fees/tenancy/${id}`).toPromise() return this.http.get<Fee[]>(`${serviceBaseUrl}/v1/fees/tenancy/${id}`).toPromise()
@ -33,4 +38,9 @@ export class ExtApiService {
this.messageService.add("ExtApiService: get tenants with saldo"); this.messageService.add("ExtApiService: get tenants with saldo");
return this.http.get<Tenant_with_Saldo[]>(`${serviceBaseUrl}/v1/tenants/saldo`).toPromise() 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

@ -9,4 +9,8 @@ export interface Tenant_with_Saldo {
lastname: string lastname: string
address1: string address1: string
saldo: number saldo: number
}
export interface UniqueNumber {
number: number
} }

View File

@ -0,0 +1,10 @@
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Buchführung
</mat-card-title>
</mat-card-header>
<mat-card-content>
<app-account #fallbackAccountComponent [selectedAccountId]="fallbackAccountId" [shallBeRentPayment]="false"></app-account>
</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,41 @@
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 {
fallbackAccount: Account
fallbackAccountId: number
@ViewChild('fallbackAccountComponent') fallbackAccountComponent: AccountComponent
constructor(
private extApiService: ExtApiService,
private messageService: MessageService
) { }
async getAccount(): Promise<void> {
try {
this.messageService.add("Trying to load fallback account")
this.fallbackAccount = await this.extApiService.getAccountByDescription('fallback_account')
this.messageService.add("Account loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async ngOnInit(): Promise<void> {
await this.getAccount()
this.fallbackAccountId = this.fallbackAccount.id
}
}

View File

@ -19,8 +19,13 @@
<th mat-header-cell *matHeaderCellDef>Haus</th> <th mat-header-cell *matHeaderCellDef>Haus</th>
<td mat-cell *matCellDef="let element">{{element.premise.description}}</td> <td mat-cell *matCellDef="let element">{{element.premise.description}}</td>
</ng-container> </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-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/commercialunit/', row.commercialPremise.id]"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/commercialunit/', row.commercialPremise.id]"></tr>
</table> </table>
</div> </div>
</mat-card-content> </mat-card-content>

View File

@ -21,7 +21,7 @@ export class MyCommercialUnitsComponent implements OnInit {
dnCommercialPremises: DN_CommercialPremise[] = [] dnCommercialPremises: DN_CommercialPremise[] = []
dataSource: MatTableDataSource<DN_CommercialPremise> dataSource: MatTableDataSource<DN_CommercialPremise>
displayedColumns: string[] = ["description", "premise"] displayedColumns: string[] = ["description", "premise", "area"]
constructor( constructor(
private commercialPremiseService: CommercialPremiseService, private commercialPremiseService: CommercialPremiseService,

View File

@ -26,6 +26,14 @@
<th mat-header-cell *matHeaderCellDef>Ort</th> <th mat-header-cell *matHeaderCellDef>Ort</th>
<td mat-cell *matCellDef="let element">{{element.city}}</td> <td mat-cell *matCellDef="let element">{{element.city}}</td>
</ng-container> </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-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/premise/', row.id]"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/premise/', row.id]"></tr>
</table> </table>

View File

@ -13,7 +13,8 @@ export class MyPremisesComponent implements OnInit {
premises: Premise[] premises: Premise[]
dataSource: MatTableDataSource<Premise> dataSource: MatTableDataSource<Premise>
displayedColumns: string[] = [ "description", "street", "zip", "city" ] displayedColumns: string[] = [ "description", "street", "zip", "city", "minusArea", "account" ]
constructor(private premiseService: PremiseService, private messageService: MessageService) { } constructor(private premiseService: PremiseService, private messageService: MessageService) { }

View File

@ -20,6 +20,8 @@
<a mat-list-item href="/fees">Mietsätze</a> <a mat-list-item href="/fees">Mietsätze</a>
</mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated"> </mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/premises">Meine Häuser</a> <a mat-list-item href="/premises">Meine Häuser</a>
</mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/ledger">Buchführung</a>
</mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated"> </mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/logout">Abmelden</a> <a mat-list-item href="/logout">Abmelden</a>
</mat-nav-list> </mat-nav-list>

View File

@ -9,31 +9,64 @@
</mat-card-subtitle> </mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<div> <mat-accordion>
<form (ngSubmit)="savePremise()"> <mat-expansion-panel (opened)="collapseDetails = true"
<div> (closed)="collapseDetails = false"
<mat-form-field appearance="outline"> [expanded]="premise.id == 0">
<mat-label>Beschreibung</mat-label> <mat-expansion-panel-header>
<input matInput name="description" [(ngModel)]="premise.description"/> <mat-panel-title>
</mat-form-field> Details
</div><div> </mat-panel-title>
<mat-form-field appearance="outline"> <mat-panel-description>
<mat-label>Strasse</mat-label> </mat-panel-description>
<input matInput name="street" [(ngModel)]="premise.street"/> </mat-expansion-panel-header>
</mat-form-field> <form (ngSubmit)="savePremise()">
</div><div> <div>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>PLZ</mat-label> <mat-label>Beschreibung</mat-label>
<input matInput name="zip" [(ngModel)]="premise.zip"/> <input matInput name="description" [(ngModel)]="premise.description"/>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> </div><div>
<mat-label>Ort</mat-label> <mat-form-field appearance="outline">
<input matInput name="city" [(ngModel)]="premise.city"/> <mat-label>Strasse</mat-label>
</mat-form-field> <input matInput name="street" [(ngModel)]="premise.street"/>
</div> </mat-form-field>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button> </div><div>
</form> <mat-form-field appearance="outline">
</div> <mat-label>PLZ</mat-label>
<input matInput name="zip" [(ngModel)]="premise.zip"/>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Ort</mat-label>
<input matInput name="city" [(ngModel)]="premise.city"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>Minus-Fläche</mat-label>
<input type="number" matInput name="minusArea" [(ngModel)]="premise.minus_area"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline" *ngIf="premise.account">
<mat-label>Betriebskostenkonto</mat-label>
<input matInput name="account" [readonly]="true" [(ngModel)]="premise.account"/>
</mat-form-field>
</div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form>
</mat-expansion-panel>
<mat-expansion-panel (opened)="collapseOverheadAccount = true"
(closed)="collapseOverheadAccount = false">
<mat-expansion-panel-header>
<mat-panel-title>
Betriebskostenkonto
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<app-account #incomeAccountComponent [selectedAccountId]="overheadAccountId" [shallBeRentPayment]="false"></app-account>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</section> </section>

View File

@ -1,8 +1,9 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { MatExpansionPanel } from '@angular/material/expansion';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { PremiseService } from '../data-object-service'; import { AccountService, PremiseService } from '../data-object-service';
import { Premise } from '../data-objects'; import { Account, NULL_Premise, Premise } from '../data-objects';
import { MessageService } from '../message.service'; import { MessageService } from '../message.service';
@Component({ @Component({
@ -12,18 +13,19 @@ import { MessageService } from '../message.service';
}) })
export class PremiseDetailsComponent implements OnInit { export class PremiseDetailsComponent implements OnInit {
collapseDetails: boolean = false
collapseOverheadAccount: boolean = false
@ViewChild('submitButton') submitButton: MatButton @ViewChild('submitButton') submitButton: MatButton
premise: Premise = { premise: Premise = NULL_Premise
id: 0,
description: '', overheadAccount: Account
street: '', overheadAccountId: number
zip: '',
city: ''
}
constructor( constructor(
private premiseService: PremiseService, private premiseService: PremiseService,
private accountService: AccountService,
private messageService: MessageService, private messageService: MessageService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
@ -34,6 +36,8 @@ export class PremiseDetailsComponent implements OnInit {
const id = +this.route.snapshot.paramMap.get('id') const id = +this.route.snapshot.paramMap.get('id')
if (id != 0) { if (id != 0) {
this.premise = await this.premiseService.getPremise(id) this.premise = await this.premiseService.getPremise(id)
this.overheadAccount = await this.accountService.getAccount(this.premise.account)
this.overheadAccountId = this.overheadAccount.id
} }
} catch (err) { } catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4)) this.messageService.add(JSON.stringify(err, undefined, 4))
@ -47,6 +51,12 @@ export class PremiseDetailsComponent implements OnInit {
this.messageService.add(JSON.stringify(this.premise, undefined, 4)) this.messageService.add(JSON.stringify(this.premise, undefined, 4))
if (this.premise.id == 0) { if (this.premise.id == 0) {
this.messageService.add("about to insert new premise") this.messageService.add("about to insert new premise")
this.overheadAccount = {
"id": 0,
"description": `overhead_account_${this.premise.description}`
}
this.overheadAccount = await this.accountService.postAccount(this.overheadAccount)
this.premise.account = this.overheadAccount.id
this.premise = await this.premiseService.postPremise(this.premise) this.premise = await this.premiseService.postPremise(this.premise)
this.messageService.add(`Successfully added premises with id ${this.premise.id}`) this.messageService.add(`Successfully added premises with id ${this.premise.id}`)
} else { } else {

View File

@ -8,7 +8,8 @@
<mat-card-content> <mat-card-content>
<mat-accordion> <mat-accordion>
<mat-expansion-panel (opened)="collapseTenantDetails = true" <mat-expansion-panel (opened)="collapseTenantDetails = true"
(closed)="collapseTenantDetails = false"> (closed)="collapseTenantDetails = false"
[expanded]="tenant.id == 0">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title *ngIf="!collapseTenantDetails"> <mat-panel-title *ngIf="!collapseTenantDetails">
Details Details
@ -68,18 +69,18 @@
<input matInput name="iban" [(ngModel)]="tenant.iban"/> <input matInput name="iban" [(ngModel)]="tenant.iban"/>
</mat-form-field> </mat-form-field>
</div> </div>
<!--
<div> <div>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline" *ngIf="tenant.account">
<mat-label>Account ID</mat-label> <mat-label>Account ID</mat-label>
<input matInput name="account_id" [readonly]="true" [ngModel]="account.id"/> <input matInput name="account_id" [readonly]="true" [ngModel]="tenant.account"/>
</mat-form-field> </mat-form-field>
<!--
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Account Description</mat-label> <mat-label>Account Description</mat-label>
<input matInput name="account_desc" [readonly]="true" [ngModel]="account.description"/> <input matInput name="account_desc" [readonly]="true" [ngModel]="account.description"/>
</mat-form-field> </mat-form-field>
</div> -->
--> </div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button> <button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form> </form>
</div> </div>

View File

@ -90,11 +90,18 @@ export class TenantDetailsComponent implements OnInit {
async getTenant(): Promise<void> { async getTenant(): Promise<void> {
try { try {
const id = +this.route.snapshot.paramMap.get('id') const id = +this.route.snapshot.paramMap.get('id')
this.messageService.add(`getTenant, id=${id}`)
if (id != 0) { if (id != 0) {
this.messageService.add("getTenant, not-0-branch")
this.tenantId = id this.tenantId = id
this.tenant = await this.tenantService.getTenant(id) this.tenant = await this.tenantService.getTenant(id)
this.account = await this.accountService.getAccount(this.tenant.account) this.account = await this.accountService.getAccount(this.tenant.account)
this.getTenancies() this.getTenancies()
} else {
this.messageService.add("getTenant, 0-branch")
this.tenant = NULL_Tenant
this.account = NULL_Account
this.tenancies = []
} }
} catch (err) { } catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4)) this.messageService.add(JSON.stringify(err, undefined, 4))