69 Commits

Author SHA1 Message Date
07dff73152 add contracts 2023-01-02 14:46:59 +01:00
10b7f89831 add analysis scripts 2022-09-18 20:15:16 +02:00
69005569aa fix tmpl 2022-05-30 15:23:12 +02:00
0e6ee3b2c5 nothing 2022-05-06 23:59:47 +02:00
3a56c4fa0f tenant overview added 2022-04-25 14:44:56 +02:00
b79ef11401 consider category which are 0 for one premise but not for the other 2022-04-25 12:22:39 +02:00
18cd0eeebc changes in account statement output 2022-04-04 14:44:44 +02:00
06e8a2f4bf Merge branch 'master' of ssh://repo.hottis.de:2922/hv2/hv2-all-in-one 2022-04-03 18:44:54 +02:00
b1b81eeb07 error analysis 2022-04-03 18:44:43 +02:00
fcd2633c72 select account to work on 2022-04-03 14:43:17 +02:00
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
80 changed files with 21012 additions and 327 deletions

12
.gitignore vendored
View File

@ -3,4 +3,14 @@ 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
.DS_Store
.Rproj.user
.venv

View File

@ -11,19 +11,6 @@ ARG CONF_DIR="${APP_DIR}/config"
RUN \ RUN \
apt update && \ apt update && \
apt install -y postgresql-client-common && \ apt install -y postgresql-client-common && \
pip3 install psycopg2 && \
pip3 install dateparser && \
pip3 install connexion && \
pip3 install connexion[swagger-ui] && \
pip3 install uwsgi && \
pip3 install flask-cors && \
pip3 install python-jose[cryptography] && \
pip3 install loguru && \
pip3 install Cheetah3
RUN \
mkdir -p ${APP_DIR} && \ mkdir -p ${APP_DIR} && \
mkdir -p ${CONF_DIR} && \ mkdir -p ${CONF_DIR} && \
useradd -d ${APP_DIR} -u 1000 user useradd -d ${APP_DIR} -u 1000 user
@ -31,11 +18,15 @@ RUN \
COPY *.py ${APP_DIR}/ COPY *.py ${APP_DIR}/
COPY openapi.yaml ${APP_DIR}/ COPY openapi.yaml ${APP_DIR}/
COPY methods.py ${APP_DIR}/ COPY methods.py ${APP_DIR}/
COPY requirements.txt ${APP_DIR}/
COPY server.ini ${CONF_DIR}/ COPY server.ini ${CONF_DIR}/
WORKDIR ${APP_DIR} WORKDIR ${APP_DIR}
VOLUME ${CONF_DIR} VOLUME ${CONF_DIR}
RUN \
pip3 install -r requirements.txt
USER 1000:1000 USER 1000:1000
EXPOSE 5000 EXPOSE 5000

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,8 +1577,110 @@ SELECT
,note ,note
FROM note_t FROM note_t
WHERE tenant = %s WHERE tenant = %s
ORDER BY
created_at
""", """,
"params": (tenantId, ) "params": (tenantId, )
} }
) )
def get_contracts(user, token_info):
return dbGetMany(user, token_info, {
"statement": """
SELECT
id
,supplier
,content
,identifier
,notes
FROM contract_t
ORDER BY
supplier
,content
""",
"params": ()
}
)
def insert_contract(user, token_info, **args):
try:
body = args["body"]
v_supplier = body["supplier"]
v_content = body["content"]
v_identifier = body["identifier"]
v_notes = body["notes"]
return dbInsert(user, token_info, {
"statement": """
INSERT INTO contract_t
(
supplier
,content
,identifier
,notes
) VALUES (
%s
,%s
,%s
,%s
)
RETURNING *
""",
"params": [
v_supplier
,v_content
,v_identifier
,v_notes
]
})
except KeyError as e:
logger.warning("insert_contract: parameter missing: {}".format(e))
raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e))
def get_contract(user, token_info, contractId=None):
return dbGetOne(user, token_info, {
"statement": """
SELECT
id
,supplier
,content
,identifier
,notes
FROM contract_t
WHERE id = %s
""",
"params": (contractId, )
}
)
def update_contract(user, token_info, contractId=None, **args):
try:
body = args["body"]
v_supplier = body["supplier"]
v_content = body["content"]
v_identifier = body["identifier"]
v_notes = body["notes"]
return dbUpdate(user, token_info, {
"statement": """
UPDATE contract_t
SET
supplier = %s
,content = %s
,identifier = %s
,notes = %s
WHERE id = %s
RETURNING *
""",
"params": [
v_supplier,
v_content,
v_identifier,
v_notes,
contractId
]
})
except KeyError as e:
logger.warning("update_contract: parameter missing: {}".format(e))
raise werkzeug.exceptions.UnprocessableEntity("parameter missing: {}".format(e))

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" ]
@ -1418,6 +1440,92 @@ paths:
$ref: '#/components/schemas/note' $ref: '#/components/schemas/note'
security: security:
- jwt: ['secret'] - jwt: ['secret']
/v1/contracts:
get:
tags: [ "contract" ]
summary: Return all normalized contracts
operationId: methods.get_contracts
responses:
'200':
description: contracts response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/contract'
security:
- jwt: ['secret']
post:
tags: [ "contract" ]
summary: Insert a contract
operationId: methods.insert_contract
requestBody:
description: contract
content:
application/json:
schema:
$ref: '#/components/schemas/contract'
responses:
'200':
description: contract successfully inserted
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/contract'
security:
- jwt: ['secret']
/v1/contracts/{contractId}:
get:
tags: [ "contract" ]
summary: Return the normalized contract with given id
operationId: methods.get_contract
parameters:
- name: contractId
in: path
required: true
schema:
type: integer
responses:
'200':
description: contract response
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/contract'
security:
- jwt: ['secret']
put:
tags: [ "contract" ]
summary: Update a contract
operationId: methods.update_contract
parameters:
- name: contractId
in: path
required: true
schema:
type: integer
requestBody:
description: contract
content:
application/json:
schema:
$ref: '#/components/schemas/contract'
responses:
'200':
description: contract successfully inserted
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/contract'
security:
- jwt: ['secret']
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# ATTENTION: This file will not be parsed by Cheetah # ATTENTION: This file will not be parsed by Cheetah
@ -1509,6 +1617,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 +1730,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 +1802,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 +1867,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 +1883,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:
@ -1743,6 +1904,21 @@ components:
type: integer type: integer
note: note:
type: string type: string
contract:
description: contract
type: object
properties:
id:
type: integer
supplier:
type: string
content:
type: string
identifier:
type: string
notes:
type: string
nullable: true
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# ATTENTION: This file will not be parsed by Cheetah # ATTENTION: This file will not be parsed by Cheetah

40
api/requirements.txt Normal file
View File

@ -0,0 +1,40 @@
attrs==22.2.0
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==2.1.1
Cheetah3==3.2.6.post1
click==8.1.3
clickclick==20.10.2
connexion==2.14.1
cryptography==39.0.0
dateparser==1.1.5
ecdsa==0.18.0
Flask==2.2.2
Flask-Cors==3.0.10
idna==3.4
inflection==0.5.1
itsdangerous==2.1.2
Jinja2==3.1.2
jsonschema==4.17.3
loguru==0.6.0
MarkupSafe==2.1.1
packaging==22.0
psycopg2==2.9.5
pyasn1==0.4.8
pycparser==2.21
pyrsistent==0.19.3
python-dateutil==2.8.2
python-jose==3.3.0
pytz==2022.7
pytz-deprecation-shim==0.1.0.post0
PyYAML==6.0
regex==2022.10.31
requests==2.28.1
rsa==4.9
six==1.16.0
swagger-ui-bundle==0.0.9
tzdata==2022.7
tzlocal==4.2
urllib3==1.26.13
uWSGI==2.0.21
Werkzeug==2.2.2

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}

108
cli/AccountStatement.py Normal file
View File

@ -0,0 +1,108 @@
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
}
}
)
sum_total = dbGetOne(
dbh,
{
"statement": "select sum(amount) as sum from account_statement_v where fiscal_year = %(year)s",
"params": {
'year': year
}
}
)
sum_error = dbGetMany(
dbh,
{
"statement": """
select coalesce(sum(amount), 0::numeric(10,2)) as sum, 1 as error, 'booked in %(year1)s, accounted in %(year0)s' as remark from account_statement_v where fiscal_year = %(year0)s and extract(year from created_at) = %(year1)s
union
select coalesce(sum(amount), 0::numeric(10,2)) as sum, 2 as error, 'booked in %(year0)s, accounted in %(year1)s' as remark from account_statement_v where fiscal_year = %(year1)s and extract(year from created_at) = %(year0)s
union
select coalesce(sum(amount), 0::numeric(10,2)) as sum, 3 as error, 'booked in %(year2)s, accounted in %(year1)s' as remark from account_statement_v where fiscal_year = %(year1)s and extract(year from created_at) = %(year2)s
union
select coalesce(sum(amount), 0::numeric(10,2)) as sum, 4 as error, 'booked in %(year1)s, accounted in %(year2)s' as remark from account_statement_v where fiscal_year = %(year2)s and extract(year from created_at) = %(year1)s
order by error
""",
"params": {
'year0': year - 1,
'year1': year,
'year2': year + 1
}
}
)
count_entries = dbGetMany(
dbh,
{
"statement": "select extract(month from created_at) as month, count(*) as count from account_statement_v where extract(year from created_at) = %(year)s group by month",
"params": {
"year": year
}
}
)
raw_total = sum_total['sum']
logger.info(f"{raw_total=}")
err1 = sum_error[0]['sum']
logger.info(f"{err1=}")
err2 = sum_error[1]['sum']
logger.info(f"{err2=}")
err3 = sum_error[2]['sum']
logger.info(f"{err3=}")
err4 = sum_error[3]['sum']
logger.info(f"{err4=}")
adjusted_total = raw_total + err1 - err2 - err3 + err4
logger.info(f"{adjusted_total=}")
template = getParam(params, 'template', 'accountStatement.tmpl')
input = { 'year': year, 'entries': accountEntries, 'overview': overview, 'related': sum_related, 'unrelated': sum_unrelated, 'total': sum_total, 'errors': sum_error, 'adjusted_total' :adjusted_total, 'count_entries': count_entries }
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']}")

362
cli/OverheadAccounts.py Normal file
View File

@ -0,0 +1,362 @@
from db import dbGetMany, dbGetOne
import datetime
from loguru import logger
from decimal import *
from utils import getParam
from Cheetah.Template import Template
import xlsxwriter
from dateutil.relativedelta import relativedelta
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 account = p.account) 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,
fe.amount as fee,
fe.fee_type as fee_type
from tenant_t t,
premise_t p,
flat_t f,
tenancy_t ty,
tenancy_fee_mapping_t tyfm,
fee_t fe
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 and
tyfm.tenancy = ty.id and
tyfm.fee = fe.id
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
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
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']
delta = relativedelta((tenant['enddate'] or datetime.date(year, 12, 31)), (tenant['startdate'] if (tenant['startdate'].year == year) else datetime.date(year, 1, 1)))
tenant['rent_time'] = delta.months if delta.days == 0 else delta.months + 1
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))
if printOverviews:
tenantsOverviewPrefix = getParam(params, 'tenantsOverviewPrefix', 'tenantsOverview')
tenantsOverviewSuffix = getParam(params, 'tenantsOverviewSuffix', 'xlsx')
logger.debug(f"Processing letters: {letters}")
outputFile = f"./output/{tenantsOverviewPrefix}-{year}.{tenantsOverviewSuffix}"
workbook = xlsxwriter.Workbook(outputFile)
worksheet = workbook.add_worksheet()
worksheet.write(0, 0, 'id')
worksheet.write(0, 1, 'lastname')
worksheet.write(0, 2, 'firstname')
worksheet.write(0, 3, 'house')
worksheet.write(0, 4, 'overhead_part_by_montharea')
worksheet.write(0, 5, 'flat')
worksheet.write(0, 6, 'flat_area')
worksheet.write(0, 7, 'fee')
worksheet.write(0, 8, 'rent_time')
worksheet.write(0, 9, 'paid_total')
worksheet.write(0, 10, 'receivable_fee')
worksheet.write(0, 11, 'paid_overhead')
worksheet.write(0, 12, 'receivable_overhead')
worksheet.write(0, 13, 'unbalanced_overhead')
row = 1
for entry in letters:
worksheet.write(row, 0, entry['tenant']['tenant_id'])
worksheet.write(row, 1, entry['tenant']['tenant_lastname'])
worksheet.write(row, 2, entry['tenant']['tenant_firstname'])
worksheet.write(row, 3, entry['tenant']['house'])
worksheet.write(row, 4, houses[entry['tenant']['house_id']]['part_by_montharea'])
worksheet.write(row, 5, entry['tenant']['flat'])
worksheet.write(row, 6, entry['tenant']['flat_area'])
worksheet.write(row, 7, entry['tenant']['fee'])
worksheet.write(row, 8, entry['tenant']['rent_time'])
worksheet.write(row, 9, entry['tenant']['paid_total'])
worksheet.write(row, 10, entry['tenant']['receivable_fee'])
worksheet.write(row, 11, entry['tenant']['paid_overhead'])
worksheet.write(row, 12, entry['receivable_overhead'])
worksheet.write(row, 13, entry['unbalanced_overhead'])
row += 1
workbook.close()

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

0
cli/accountStatement Normal file
View File

View File

@ -0,0 +1,83 @@
\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 total & \tt{$total['sum']} \,\euro{} \\
\hline \hline
\end{tabular}
\pagebreak
\subsection*{Errors}
\begin{tabular}{|r|r|l|} \hline
\hline \textcolor{blue}{\bf{type}} & \textcolor{blue}{\bf{sum}} & \textcolor{blue}{\bf{remark}} \\
#for $entry in $errors
\hline \tt{$entry['error']} & \tt{$entry['sum']} \,\euro{} & \tt{$entry['remark']} \\
#end for
\hline \tt{0} & \tt{$adjusted_total} \,\euro{} & \tt{adjusted total} \\
\hline \hline
\end{tabular}
\pagebreak
\subsection*{Counts}
\begin{tabular}{|r|r|} \hline
\hline \textcolor{blue}{\bf{Month}} & \textcolor{blue}{\bf{Count}} \\
#for $entry in $count_entries
\hline \tt{$entry['month']} & \tt{$entry['count']} \\
#end for
\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

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 $unbalanced_overhead < 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

@ -0,0 +1,236 @@
source("~/.active-rstudio-document")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
View(amounts)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
View(amounts)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
print(amounts)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
print(amounts)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
print(amounts)
print(amounts, digits=2)
print(amounts, width=100)
print(amounts, width=10)
print(amounts, width=100)
print(amounts, digits=10)
options(pillar.sigfig=7)
print(amounts)
options(pillar.sigfig=10)
print(amounts)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
library(tidyverse)
flextable::flextable
library(table)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
sum(amounts$fee)
sum(amounts$fee) + sum(amounts$overhead)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
sum(amounts$Betriebskosten)
sum(amounts$Betriebskosten, na.rm = TRUE)
sum(amounts$Betriebskosten, na.rm = FALSE)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
sum(amounts$Betriebskosten)
sum(amounts$Betriebskosten, na.rm = T)
sum(amounts$Betriebskosten, na.omit())
sum(amounts$Betriebskosten
)
sum(amounts$Betriebskosten)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
amounts$Betriebskosten
amounts$Betriebskosten[is.na(.)]
amounts$Betriebskosten[is.na()]
amounts$Betriebskosten[NA]
amounts$Betriebskosten[1]
amounts$Betriebskosten[is.na()]
amounts$Betriebskosten[is.na]
d <- amounts$Betriebskosten
d
d[is.na(d)]
d[is.na(d)] <- 0
d
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
View(expense)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
expense
expense[overhead_relevant==T]
expense[expense$overhead_relevant == T]
expense[expense$overhead_relevant == TRUE]
expense[expense$overhead_relevant]
expense %>% filter(overhead_relevant == TRUE)
source("~/Workspace/hv2-all-in-one/r-scripts/hv2-analysis/income.R")
View(expense)
knitr::opts_chunk$set(echo = TRUE)
total_fee <- sum(income$Miete)
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse, warn.conflicts = FALSE)
library(DBI, warn.conflicts = FALSE)
library(tidyr, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
YEAR <- 2021
HOME <- Sys.getenv("HOME")
Sys.setenv(PGHOST = "db.mainscnt.eu",
PGDATABASE = "hv2",
PGPORT = 5432,
PGUSER = "wn",
PGSSLMODE = "verify-ca",
PGSSLKEY = paste(HOME, "/keys/psql/wn-postgresql-client-2.key", sep=""),
PGSSLCERT = paste(HOME, "/keys/psql/wn-postgresql-client-2.crt", sep=""),
PGSSLROOTCERT = paste(HOME, "/keys/psql/postgres-ca.crt", sep=""))
con <- dbConnect(RPostgres::Postgres())
res <- dbSendQuery(con, "
select f.description as flat,
p.description as premise,
aec.description as category,
sum(ae.amount) as amount
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_t a,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
ty.flat = f.id and
ty.tenant = t.id and
a.id = t.account and
ae.account = a.id and
aec.id = ae.account_entry_category and
ae.fiscal_year = $1 and
aec.description in ('Mietzahlung', 'Mietforderung', 'Betriebskostenforderung', 'Betriebskostenausgleich')
group by p.description,
f.description,
aec.description;
")
dbBind(res, list(YEAR))
income <- dbFetch(res)
dbClearResult(res)
income <- income %>%
pivot_wider(names_from = category, values_from = amount) %>%
mutate(Miete := -1 * Mietforderung) %>%
mutate(Betriebskosten := Mietzahlung - Miete + Betriebskostenausgleich) %>%
select(flat, premise, Miete, Betriebskosten) %>%
arrange(premise, flat) %>%
mutate(across(where(is.numeric), ~num(., digits=2))) %>%
mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
knitr::kable(income)
total_fee <- sum(income$Miete)
total_overhead <- sum(income$Betriebskosten)
total_income <- total_fee + total_overhead
View(income)
knitr::opts_chunk$set(echo = TRUE)
library(tidyverse, warn.conflicts = FALSE)
library(DBI, warn.conflicts = FALSE)
library(tidyr, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
YEAR <- 2021
HOME <- Sys.getenv("HOME")
Sys.setenv(PGHOST = "db.mainscnt.eu",
PGDATABASE = "hv2",
PGPORT = 5432,
PGUSER = "wn",
PGSSLMODE = "verify-ca",
PGSSLKEY = paste(HOME, "/keys/psql/wn-postgresql-client-2.key", sep=""),
PGSSLCERT = paste(HOME, "/keys/psql/wn-postgresql-client-2.crt", sep=""),
PGSSLROOTCERT = paste(HOME, "/keys/psql/postgres-ca.crt", sep=""))
con <- dbConnect(RPostgres::Postgres())
res <- dbSendQuery(con, "
select f.description as flat,
p.description as premise,
aec.description as category,
sum(ae.amount) as amount
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_t a,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
ty.flat = f.id and
ty.tenant = t.id and
a.id = t.account and
ae.account = a.id and
aec.id = ae.account_entry_category and
ae.fiscal_year = $1 and
aec.description in ('Mietzahlung', 'Mietforderung', 'Betriebskostenforderung', 'Betriebskostenausgleich')
group by p.description,
f.description,
aec.description;
")
dbBind(res, list(YEAR))
income <- dbFetch(res)
dbClearResult(res)
income <- income %>%
pivot_wider(names_from = category, values_from = amount) %>%
mutate(Miete := -1 * Mietforderung) %>%
mutate(Betriebskosten := Mietzahlung - Miete + Betriebskostenausgleich) %>%
select(flat, premise, Miete, Betriebskosten) %>%
arrange(premise, flat) %>%
mutate(across(where(is.numeric), ~num(., digits=2))) %>%
mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
knitr::kable(income)
total_fee <- sum(income$Miete)
total_overhead <- sum(income$Betriebskosten)
total_income <- total_fee + total_overhead
toString(total_fee)
total_fee$value
total_fee[1]
total_fee[2]
total_fee[1]
t=total_fee
t
unnest(t)
a <- 6.4234
a
num(a, digits=2)
a
format(a, digits=2)
format(a, digits=3)
a<-100.4234234
a
format(a, digits=3)
format(a, nsmall=2)

View File

@ -0,0 +1,13 @@
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX

View File

@ -0,0 +1,173 @@
---
title: "HV2 Jahresabrechnung"
author: "Wolfgang Hottgenroth"
date: "2022-09-18"
output:
pdf_document: default
html_document: default
word_document: default
---
```{r setup, include=FALSE, echo=FALSE, message=FALSE}
knitr::opts_chunk$set(echo = FALSE, message=FALSE)
```
```{r}
library(tidyverse, warn.conflicts = FALSE)
library(DBI, warn.conflicts = FALSE)
library(tidyr, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
```
```{r}
YEAR <- 2021
HOME <- Sys.getenv("HOME")
Sys.setenv(PGHOST = "db.mainscnt.eu",
PGDATABASE = "hv2",
PGPORT = 5432,
PGUSER = "wn",
PGSSLMODE = "verify-ca",
PGSSLKEY = paste(HOME, "/keys/psql/wn-postgresql-client-2.key", sep=""),
PGSSLCERT = paste(HOME, "/keys/psql/wn-postgresql-client-2.crt", sep=""),
PGSSLROOTCERT = paste(HOME, "/keys/psql/postgres-ca.crt", sep=""))
con <- dbConnect(RPostgres::Postgres())
```
# Übersicht über die Einnahmen
```{r}
res <- dbSendQuery(con, "
select f.description as flat,
p.description as premise,
aec.description as category,
sum(ae.amount) as amount
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_t a,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
ty.flat = f.id and
ty.tenant = t.id and
a.id = t.account and
ae.account = a.id and
aec.id = ae.account_entry_category and
ae.fiscal_year = $1 and
aec.description in ('Mietzahlung', 'Mietforderung', 'Betriebskostenforderung', 'Betriebskostenausgleich')
group by p.description,
f.description,
aec.description;
")
dbBind(res, list(YEAR))
income <- dbFetch(res)
dbClearResult(res)
income <- income %>%
pivot_wider(names_from = category, values_from = amount) %>%
mutate(Miete := -1 * Mietforderung) %>%
mutate(Betriebskosten := Mietzahlung - Miete + Betriebskostenausgleich) %>%
select(flat, premise, Miete, Betriebskosten) %>%
arrange(premise, flat) %>%
# mutate(across(where(is.numeric), ~num(., digits=2))) %>%
mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
```
```{r}
knitr::kable(income)
```
# Übersicht über die Ausgaben
```{r}
res <- dbSendQuery(con, "
select aec.overhead_relevant as overhead_relevant,
aec.description as category,
sum(ae.amount) as amount
from account_entry_category_t aec,
account_entry_t ae
where aec.id = ae.account_entry_category and
aec.id not in (2, 3, 4, 29) and
ae.fiscal_year = $1
group by aec.overhead_relevant,
aec.description
")
dbBind(res, list(YEAR))
expense <- dbFetch(res)
dbClearResult(res)
expense <- expense %>%
arrange(overhead_relevant, category) %>%
# mutate(across(where(is.numeric), ~num(., digits=2))) %>%
mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
```
```{r}
knitr::kable(expense)
```
# Zusammenfassung
```{r}
total_fee <- sum(income$Miete)
total_overhead <- sum(income$Betriebskosten)
total_income <- total_fee + total_overhead
total_expense <- sum(expense$amount)
overhead_relevant_expense <- expense %>%
filter(overhead_relevant == TRUE)
total_overhead_relevant_expense <- sum(overhead_relevant_expense$amount)
total <- total_income + total_expense
overview.income <- as_tibble(
data.frame(
"Kategorie" = c("Mieteinnahmen", "Betriebskostenvorauszahlungen", "Einnahmen ingesamt"),
"Betrag" = c(total_fee, total_overhead, total_income)
)
)
overview.expense <- as_tibble(
data.frame(
"Kategorie" = c("Ausgaben insgesamt", "davon Betriebskostenausgaben"),
"Betrag" = c(-1 * total_expense, -1 * total_overhead_relevant_expense)
)
)
overview.gain <- as_tibble(
data.frame(
"Überschuss" = c(total)
)
)
```
## Einnahmen
```{r}
knitr::kable(overview.income)
```
## Ausgaben
```{r}
knitr::kable(overview.expense)
```
## Überschuss
```{r}
knitr::kable(overview.gain)
```

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,118 @@
library(tidyverse, warn.conflicts = FALSE)
library(DBI, warn.conflicts = FALSE)
library(tidyr, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)
# library(lubridate, warn.conflicts = FALSE)
# library(R.utils, warn.conflicts = FALSE)
YEAR <- 2021
HOME <- Sys.getenv("HOME")
Sys.setenv(PGHOST = "db.mainscnt.eu",
PGDATABASE = "hv2",
PGPORT = 5432,
PGUSER = "wn",
PGSSLMODE = "verify-ca",
PGSSLKEY = paste(HOME, "/keys/psql/wn-postgresql-client-2.key", sep=""),
PGSSLCERT = paste(HOME, "/keys/psql/wn-postgresql-client-2.crt", sep=""),
PGSSLROOTCERT = paste(HOME, "/keys/psql/postgres-ca.crt", sep=""))
con <- dbConnect(RPostgres::Postgres())
res <- dbSendQuery(con, "
select f.description as flat,
p.description as premise,
aec.description as category,
sum(ae.amount) as amount
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_t a,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
ty.flat = f.id and
ty.tenant = t.id and
a.id = t.account and
ae.account = a.id and
aec.id = ae.account_entry_category and
ae.fiscal_year = $1 and
aec.description in ('Mietzahlung', 'Mietforderung', 'Betriebskostenforderung', 'Betriebskostenausgleich')
group by p.description,
f.description,
aec.description;
")
dbBind(res, list(YEAR))
income <- dbFetch(res)
dbClearResult(res)
income <- income %>%
pivot_wider(names_from = category, values_from = amount) %>%
mutate(Miete := -1 * Mietforderung) %>%
mutate(Betriebskosten := Mietzahlung - Miete + Betriebskostenausgleich) %>%
select(flat, premise, Miete, Betriebskosten) %>%
arrange(premise, flat) %>%
mutate(across(where(is.numeric), ~num(., digits=2))) %>%
mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
total_fee <- sum(income$Miete)
total_overhead <- sum(income$Betriebskosten)
total_income <- total_fee + total_overhead
res <- dbSendQuery(con, "
select aec.overhead_relevant as overhead_relevant,
aec.description as category,
sum(ae.amount) as amount
from account_entry_category_t aec,
account_entry_t ae
where aec.id = ae.account_entry_category and
aec.id not in (2, 3, 4, 29) and
ae.fiscal_year = $1
group by aec.overhead_relevant,
aec.description
")
dbBind(res, list(YEAR))
expense <- dbFetch(res)
dbClearResult(res)
expense <- expense %>%
arrange(overhead_relevant, category) #%>%
#mutate(across(where(is.numeric), ~num(., digits=2))) #%>%
#mutate(across(where(is.numeric), ~replace(., is.na(.), 0)))
total_expense <- sum(expense$amount)
overhead_relevant_expense <- expense %>%
filter(overhead_relevant == TRUE)
total_overhead_relevant_expense <- sum(overhead_relevant_expense$amount)
print("Einkuenfte pro Wohnung")
print(income)
print("Einkuenfte Miete")
print(total_fee)
print("Einkuenfte Betriebskosten")
print(total_overhead)
print("Einkuenfte Total")
print(total_income)
print("Ausgaben")
print(expense)
print(total_expense)
print("davon betriebskosten-relevant")
print(total_overhead_relevant_expense)
profit <- total_income + total_expense
print("Überschuss")
print(profit)

View File

@ -2,6 +2,7 @@
## Tools and Templates ## Tools and Templates
* venv for Python tools is used, remember to activate, install dependencies via ``requirements.txt``, keep it up to date using ``pip freeze > requirements.txt``
* all tools provide should work on Linux and on Windows, shell scripts are avoided * all tools provide should work on Linux and on Windows, shell scripts are avoided
* templates files must be generated at development time, generated files must be put into the repository * templates files must be generated at development time, generated files must be put into the repository
* use ``generate.py`` in the umbrella project's root without any arguments to generate all template files in all subdirectories using the ``schema.json`` from the umbrella project's root * use ``generate.py`` in the umbrella project's root without any arguments to generate all template files in all subdirectories using the ``schema.json`` from the umbrella project's root
@ -14,3 +15,5 @@
* make sure these configuration files are not added to the repository, they contain secrets * make sure these configuration files are not added to the repository, they contain secrets
* in the development environment the API will answer at ``http://localhost:8080``, the Swagger-UI is available at ``http://localhost:8080/ui`` * in the development environment the API will answer at ``http://localhost:8080``, the Swagger-UI is available at ``http://localhost:8080/ui``

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
Cheetah3==3.2.6.post1

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,10 +153,23 @@
"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 }
] ]
},
{
"name": "contract",
"immutable": false,
"columns": [
{ "name": "supplier", "sqltype": "varchar(256)", "notnull": true, "selector": 0 },
{ "name": "content", "sqltype": "varchar(256)", "notnull": true, "selector": 1 },
{ "name": "identifier", "sqltype": "varchar(256)", "notnull": true },
{ "name": "notes", "sqltype": "varchar(4096)", "notnull": false }
],
"tableConstraints": [
"unique(supplier, content)"
]
} }
] ]
} }

View File

@ -0,0 +1,92 @@
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;
create or replace view income_v as
select f.description as flat,
p.description as premise,
ae.amount as amount,
ty.id as tenancy_id,
t.lastname as tenant
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
f.id = ty.flat and
t.id = ty.tenant and
ae.id = t.account and
aec.id = ae.account_entry_category and
aec.description = 'Mietzahlung' and
ae.fiscal_year = 2021
;
select f.description as flat,
p.description as premise,
--ty.id as tenancy_id,
--t.lastname || ', ' || t.firstname as tenant,
--a.description as account,
aec.description as category,
sum(ae.amount) as amount
from flat_t f,
premise_t p,
tenancy_t ty,
tenant_t t,
account_t a,
account_entry_t ae,
account_entry_category_t aec
where p.id = f.premise and
ty.flat = f.id and
ty.tenant = t.id and
a.id = t.account and
ae.account = a.id and
aec.id = ae.account_entry_category and
ae.fiscal_year = 2021 and
aec.description in ('Mietzahlung', 'Mietforderung', 'Betriebskostenforderung')
group by p.description,
f.description,
aec.description
;
select aec.overhead_relevant as overhead_relevant,
aec.description as category,
sum(ae.amount) as amount
from account_entry_category_t aec,
account_entry_t ae
where aec.id = ae.account_entry_category and
aec.id not in (2, 3, 4, 29) and
ae.fiscal_year = 2021
group by aec.overhead_relevant,
aec.description
order by aec.overhead_relevant;

View File

@ -0,0 +1,11 @@
CREATE TABLE contract_t (
id serial not null primary key
,supplier varchar(256) not null
,content varchar(256) not null
,identifier varchar(256) not null
,unique(supplier, content)
);
GRANT SELECT, INSERT, UPDATE ON contract_t TO hv2;
GRANT SELECT, UPDATE ON contract_t_id_seq 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)
); );
@ -166,6 +172,18 @@ CREATE TABLE note_t (
GRANT SELECT, INSERT ON note_t TO hv2; GRANT SELECT, INSERT ON note_t TO hv2;
GRANT SELECT, UPDATE ON note_t_id_seq TO hv2; GRANT SELECT, UPDATE ON note_t_id_seq TO hv2;
CREATE TABLE contract_t (
id serial not null primary key
,supplier varchar(256) not null
,content varchar(256) not null
,identifier varchar(256) not null
,notes varchar(4096)
,unique(supplier, content)
);
GRANT SELECT, INSERT, UPDATE ON contract_t TO hv2;
GRANT SELECT, UPDATE ON contract_t_id_seq TO hv2;

17420
ui/hv2-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,6 @@
"@types/jasmine": "~3.6.0", "@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.0", "karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3", "karma-coverage": "~2.0.3",

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,11 +19,15 @@ 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';
import { ContractComponent } from './contract/contract.component';
import { MyContractsComponent } from './my-contracts/my-contracts.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'tenants', component: MyTenantsComponent, canActivate: [ AuthGuardService ] }, { path: 'tenants', component: MyTenantsComponent, canActivate: [ AuthGuardService ] },
{ path: 'premises', component: MyPremisesComponent, canActivate: [ AuthGuardService ] }, { path: 'premises', component: MyPremisesComponent, canActivate: [ AuthGuardService ] },
{ path: 'contracts', component: MyContractsComponent, canActivate: [ AuthGuardService ] },
{ path: 'flats', component: MyFlatsComponent, canActivate: [ AuthGuardService ] }, { path: 'flats', component: MyFlatsComponent, canActivate: [ AuthGuardService ] },
{ path: 'parkings', component: MyParkingsComponent, canActivate: [ AuthGuardService ] }, { path: 'parkings', component: MyParkingsComponent, canActivate: [ AuthGuardService ] },
{ path: 'commercialunits', component: MyCommercialUnitsComponent, canActivate: [ AuthGuardService ] }, { path: 'commercialunits', component: MyCommercialUnitsComponent, canActivate: [ AuthGuardService ] },
@ -44,6 +48,9 @@ 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: 'contract', component: ContractComponent, canActivate: [ AuthGuardService ] },
{ path: 'contract/:id', component: ContractComponent, 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,12 @@ 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';
import { ContractComponent } from './contract/contract.component';
import { MyContractsComponent } from './my-contracts/my-contracts.component';
registerLocaleData(localeDe) registerLocaleData(localeDe)
@ -76,7 +81,11 @@ registerLocaleData(localeDe)
AccountComponent, AccountComponent,
NoteComponent, NoteComponent,
EnterPaymentComponent, EnterPaymentComponent,
HomeComponent HomeComponent,
LedgerComponent,
ErrorDialogComponent,
ContractComponent,
MyContractsComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -98,7 +107,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

@ -0,0 +1,4 @@
.notearea {
width: 75%;
}

View File

@ -0,0 +1,46 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
{{contract?.supplier}} {{contract?.content}}
</mat-card-title>
<mat-card-subtitle>
ID: {{contract?.id}}
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div>>
<form (ngSubmit)="saveContract()">
<div>
<mat-form-field appearance="outline">
<mat-label>Partner</mat-label>
<input matInput name="supplier" [(ngModel)]="contract.supplier"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>Gegenstand</mat-label>
<input matInput name="content" [(ngModel)]="contract.content"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline">
<mat-label>ID</mat-label>
<input matInput name="identifier" [(ngModel)]="contract.identifier"/>
</mat-form-field>
</div><div>
<mat-form-field appearance="outline" class="notearea">
<mat-label>Notizen</mat-label>
<textarea matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="5"
cdkAutosizeMaxRows="10"
name="notes" [(ngModel)]="contract.notes"></textarea>
</mat-form-field>
</div>
<button #submitButton type="submit" mat-raised-button color="primary">Speichern</button>
</form>
</div>
</mat-card-content>
</mat-card>
</section>

View File

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

View File

@ -0,0 +1,61 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { ContractService } from '../data-object-service';
import { Contract, NULL_CommercialPremise, NULL_Contract } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-contract',
templateUrl: './contract.component.html',
styleUrls: ['./contract.component.css']
})
export class ContractComponent implements OnInit {
@ViewChild('submitButton') submitButton: MatButton
contract: Contract = NULL_Contract
constructor(
private contractService: ContractService,
private messageService: MessageService,
private route: ActivatedRoute,
private router: Router
) { }
async getContract(): Promise<void> {
try {
const id = +this.route.snapshot.paramMap.get('id')
if (id != 0) {
this.contract = await this.contractService.getContract(id)
}
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async saveContract() {
try {
this.submitButton.disabled = true
this.messageService.add(`saveContract: ${ JSON.stringify(this.contract, undefined, 4) }`)
if (this.contract.id == 0) {
this.messageService.add("about to insert new contract")
this.contract = await this.contractService.postContract(this.contract)
this.messageService.add(`Successfully added contract with id ${this.contract.id}`)
} else {
this.messageService.add("about to update existing contract")
this.contract = await this.contractService.putContract(this.contract)
this.messageService.add(`Successfully changed contract with id ${this.contract.id}`)
}
this.router.navigate(['/contracts'])
} finally {
this.submitButton.disabled = false
}
}
ngOnInit(): void {
this.getContract()
}
}

View File

@ -27,6 +27,7 @@ import { TenancyFeeMapping } from './data-objects';
import { AccountEntryCategory } from './data-objects'; import { AccountEntryCategory } from './data-objects';
import { AccountEntry } from './data-objects'; import { AccountEntry } from './data-objects';
import { Note } from './data-objects'; import { Note } from './data-objects';
import { Contract } from './data-objects';
@ -130,6 +131,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()
}
} }
@ -514,4 +520,35 @@ export class NoteService {
} }
@Injectable({ providedIn: 'root' })
export class ContractService {
constructor(private messageService: MessageService, private http: HttpClient) { }
async getContracts(): Promise<Contract[]> {
this.messageService.add(`ContractService: get data`);
return this.http.get<Contract[]>(`${serviceBaseUrl}/v1/contracts`).toPromise()
}
async getContract(id: number): Promise<Contract> {
this.messageService.add(`ContractService: get data for ${id}`);
return this.http.get<Contract>(`${serviceBaseUrl}/v1/contracts/${id}`).toPromise()
}
async postContract(item: Contract): Promise<Contract> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`ContractService: post data for ${itemStr}`);
return this.http.post<Contract>(`${serviceBaseUrl}/v1/contracts`, item).toPromise()
}
async putContract(item: Contract): Promise<Contract> {
let itemStr: string = JSON.stringify(item, undefined, 4)
this.messageService.add(`ContractService: put data for ${itemStr}`)
let id: number = item["id"]
return this.http.put<Contract>(`${serviceBaseUrl}/v1/contracts/${id}`, item).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
} }
@ -214,4 +226,19 @@ export const NULL_Note: Note = {
,note: '' ,note: ''
} }
export interface Contract {
id: number
supplier: string
content: string
identifier: string
notes: string
}
export const NULL_Contract: Contract = {
id: 0
,supplier: ''
,content: ''
,identifier: ''
,notes: ''
}

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,20 @@
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Buchführung
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<span>Konto auswählen: </span>
<mat-form-field appearance="outline">
<mat-select #mapSelect [(ngModel)]="accountId" name="account">
<mat-label>Mieter</mat-label>
<mat-option *ngFor="let p of accounts" [value]="p.id">{{p.description}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<app-account [selectedAccountId]="accountId" [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 { AccountService } from '../data-object-service';
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 {
accounts: Account[]
accountId: number
@ViewChild('accountComponent') accountComponent: AccountComponent
constructor(
private accountService: AccountService,
private messageService: MessageService
) { }
async getAccount(): Promise<void> {
try {
this.messageService.add("Trying to load accounts")
this.accounts = await this.accountService.getAccounts()
this.messageService.add("Accounts loaded")
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
async ngOnInit(): Promise<void> {
await this.getAccount()
}
}

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

@ -0,0 +1,31 @@
<section class="mat-typography">
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
<span>Meine Verträge</span>
<span class="spacer"></span>
<a mat-button routerLink="/contract">Neu anlegen</a>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
<table mat-table [dataSource]="dataSource" #zftable>
<ng-container matColumnDef="supplier">
<th mat-header-cell *matHeaderCellDef>Partner</th>
<td mat-cell *matCellDef="let element">{{element.supplier}}</td>
</ng-container>
<ng-container matColumnDef="content">
<th mat-header-cell *matHeaderCellDef>Gegenstand</th>
<td mat-cell *matCellDef="let element">{{element.content}}</td>
</ng-container>
<ng-container matColumnDef="identifier">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let element">{{element.identifier}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/contract/', row.id]"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</section>

View File

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

View File

@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { ContractService } from '../data-object-service';
import { Contract } from '../data-objects';
import { MessageService } from '../message.service';
@Component({
selector: 'app-my-contracts',
templateUrl: './my-contracts.component.html',
styleUrls: ['./my-contracts.component.css']
})
export class MyContractsComponent implements OnInit {
contracts: Contract[]
dataSource: MatTableDataSource<Contract>
displayedColumns: string[] = [ "supplier", "content", "identifier" ]
constructor(private contractService: ContractService, private messageService: MessageService) { }
async getContracts(): Promise<void> {
try {
this.messageService.add("Trying to load contracts")
this.contracts = await this.contractService.getContracts();
this.messageService.add("Contracts loaded")
this.dataSource = new MatTableDataSource<Contract>(this.contracts)
} catch (err) {
this.messageService.add(JSON.stringify(err, undefined, 4))
}
}
ngOnInit(): void {
this.messageService.add("MyContractsComponent.ngOnInit")
this.getContracts()
}
}

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,10 @@
<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">
<a mat-list-item href="/contracts">Verträge</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))