7 Commits

16 changed files with 194 additions and 10 deletions

View File

@ -88,4 +88,26 @@
$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']

View File

@ -37,10 +37,20 @@ def get_tenant_with_saldo(user, token_info):
return dbGetMany(user, token_info, { return dbGetMany(user, token_info, {
"statement": """ "statement": """
SELECT t.id, 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.id, 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, )
}
)

View File

@ -1509,6 +1509,28 @@ 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']
components: components:

View File

@ -1,10 +1,13 @@
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
def perform(dbh, params): def perform(dbh, params):
createdAt = params['created_at'] try:
createdAt = params['created_at']
except KeyError:
createdAt = datetime.datetime.today().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:

View File

@ -38,14 +38,21 @@ parser.add_argument('--verbosity', '-v',
help='Minimal log level for output: DEBUG, INFO, WARNING, ..., default: DEBUG', help='Minimal log level for output: DEBUG, INFO, WARNING, ..., default: DEBUG',
required=False, required=False,
default="DEBUG") default="DEBUG")
parser.add_argument('--nocolorize', '-n',
help='disable colored output (for cron)',
required=False,
action='store_true',
default=False)
args = parser.parse_args() args = parser.parse_args()
operation = args.operation operation = args.operation
params = json.loads(args.params) params = json.loads(args.params)
logLevel = args.verbosity logLevel = args.verbosity
noColorize = args.nocolorize
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

@ -145,7 +145,7 @@ 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()
,amount numeric(10,2) not null ,amount numeric(10,2) not null

View File

@ -8,7 +8,7 @@
</mat-form-field> </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)]="newAccountEntry.account_entry_category" name="category" [disabled]="shallBeRentPayment">
<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>

View File

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

View File

@ -47,7 +47,8 @@ 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'
registerLocaleData(localeDe) registerLocaleData(localeDe)
@ -76,7 +77,8 @@ registerLocaleData(localeDe)
AccountComponent, AccountComponent,
NoteComponent, NoteComponent,
EnterPaymentComponent, EnterPaymentComponent,
HomeComponent HomeComponent,
LedgerComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -6,7 +6,7 @@ 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 } from './ext-data-objects';
@ -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()

View File

@ -0,0 +1,33 @@
<mat-card class="defaultCard">
<mat-card-header>
<mat-card-title>
Buchführung
</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-accordion>
<mat-expansion-panel (opened)="collapseIncomeDetails = true"
(closed)="collapseIncomeDetails = false">
<mat-expansion-panel-header>
<mat-panel-title>
Einnahmen
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<app-account [selectedAccountId]="incomeAccountId" [shallBeRentPayment]="false"></app-account>
</mat-expansion-panel>
<mat-expansion-panel (opened)="collapseExpenseDetails = true"
(closed)="collapseExpenseDetails = false">
<mat-expansion-panel-header>
<mat-panel-title>
Ausgaben
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<app-account [selectedAccountId]="expenseAccountId" [shallBeRentPayment]="false"></app-account>
</mat-expansion-panel>
</mat-accordion>
</mat-card-content>
</mat-card>

View File

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

View File

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

View File

@ -9,6 +9,8 @@
</mat-nav-list> </mat-nav-list>
<mat-nav-list *ngIf="authenticated"> <mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/enterPayment">Mietzahlung eintragen</a> <a mat-list-item href="/enterPayment">Mietzahlung eintragen</a>
</mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/ledger">Buchführung</a>
</mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated"> </mat-nav-list><mat-divider *ngIf="authenticated"></mat-divider><mat-nav-list *ngIf="authenticated">
<a mat-list-item href="/tenants">Meine Mieter/innen</a> <a mat-list-item href="/tenants">Meine Mieter/innen</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">

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