Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
3e256dc2f7 | |||
bf5aec351c | |||
0bbe22e9ae | |||
9734149848 | |||
28e05c70a9 | |||
1116b50773 | |||
8b59f3b7a8 | |||
2d8e8a2e42 | |||
eb8f3fef3e | |||
b5509ba162 | |||
4dd6fa6d8a | |||
a4b26013a7 |
15 changed files with 78 additions and 12 deletions
|
@ -6,7 +6,7 @@ Administration für Feuerwehren und Vereine (Backend).
|
||||||
|
|
||||||
Dieses Projekt, `ff-admin-server`, ist das Backend zur Verwaltung von Mitgliederdaten. Die zugehörige Webapp ist im Repository [ff-admin-ui](https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin) zu finden.
|
Dieses Projekt, `ff-admin-server`, ist das Backend zur Verwaltung von Mitgliederdaten. Die zugehörige Webapp ist im Repository [ff-admin-ui](https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin) zu finden.
|
||||||
|
|
||||||
Eine Demo zusammen mit der `ff-admin` finden Sie unter [ff-admin-demo.jk-effects.cloud](ff-admin-demo.jk-effects.cloud).
|
Eine Demo zusammen mit der `ff-admin` finden Sie unter [https://admin-demo.ff-admin.de](https://admin-demo.ff-admin.de).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "ff-admin-server",
|
"name": "ff-admin-server",
|
||||||
"version": "1.0.2",
|
"version": "1.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ff-admin-server",
|
"name": "ff-admin-server",
|
||||||
"version": "1.0.2",
|
"version": "1.1.1",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ff-admin-server",
|
"name": "ff-admin-server",
|
||||||
"version": "1.0.2",
|
"version": "1.1.1",
|
||||||
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
|
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -6,4 +6,5 @@ export interface SynchronizeProtocolPresenceCommand {
|
||||||
export interface ProtocolPresenceCommand {
|
export interface ProtocolPresenceCommand {
|
||||||
memberId: number;
|
memberId: number;
|
||||||
absent: boolean;
|
absent: boolean;
|
||||||
|
excused: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ export default abstract class ProtocolPresenceCommandHandler {
|
||||||
.update(protocolPresence)
|
.update(protocolPresence)
|
||||||
.set({
|
.set({
|
||||||
absent: member.absent,
|
absent: member.absent,
|
||||||
|
excused: member.excused,
|
||||||
})
|
})
|
||||||
.where("memberId = :memberId", { memberId: member.memberId })
|
.where("memberId = :memberId", { memberId: member.memberId })
|
||||||
.andWhere("protocolId = :protocolId", { protocolId })
|
.andWhere("protocolId = :protocolId", { protocolId })
|
||||||
|
|
|
@ -61,8 +61,13 @@ export async function getAllMembers(req: Request, res: Response): Promise<any> {
|
||||||
let offset = parseInt((req.query.offset as string) ?? "0");
|
let offset = parseInt((req.query.offset as string) ?? "0");
|
||||||
let count = parseInt((req.query.count as string) ?? "25");
|
let count = parseInt((req.query.count as string) ?? "25");
|
||||||
let search = (req.query.search as string) ?? "";
|
let search = (req.query.search as string) ?? "";
|
||||||
|
let noLimit = req.query.noLimit === "true";
|
||||||
|
let ids = ((req.query.ids ?? "") as string)
|
||||||
|
.split(",")
|
||||||
|
.filter((i) => i)
|
||||||
|
.map((i) => parseInt(i));
|
||||||
|
|
||||||
let [members, total] = await MemberService.getAll(offset, count, search);
|
let [members, total] = await MemberService.getAll({ offset, count, search, noLimit, ids });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
members: MemberFactory.mapToBase(members),
|
members: MemberFactory.mapToBase(members),
|
||||||
|
|
|
@ -260,7 +260,9 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
||||||
agenda,
|
agenda,
|
||||||
decisions,
|
decisions,
|
||||||
presence: presence.filter((p) => !p.absent).map((p) => p.member),
|
presence: presence.filter((p) => !p.absent).map((p) => p.member),
|
||||||
absent: presence.filter((p) => p.absent).map((p) => p.member),
|
absent: presence.filter((p) => p.absent).map((p) => ({ ...p.member, excused: p.excused })),
|
||||||
|
excused_absent: presence.filter((p) => p.absent && p.excused).map((p) => p.member),
|
||||||
|
unexcused_absent: presence.filter((p) => p.absent && !p.excused).map((p) => p.member),
|
||||||
votings,
|
votings,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -389,6 +391,7 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons
|
||||||
members: presence.map((p) => ({
|
members: presence.map((p) => ({
|
||||||
memberId: p.memberId,
|
memberId: p.memberId,
|
||||||
absent: p.absent,
|
absent: p.absent,
|
||||||
|
excused: p.excused,
|
||||||
})),
|
})),
|
||||||
protocolId,
|
protocolId,
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,6 +67,7 @@ import { ProtocolAbsent1736072179716 } from "./migrations/1736072179716-protocol
|
||||||
import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist";
|
import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist";
|
||||||
import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues";
|
import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues";
|
||||||
import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer";
|
import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer";
|
||||||
|
import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse";
|
||||||
|
|
||||||
const dataSource = new DataSource({
|
const dataSource = new DataSource({
|
||||||
type: DB_TYPE as any,
|
type: DB_TYPE as any,
|
||||||
|
@ -144,6 +145,7 @@ const dataSource = new DataSource({
|
||||||
Memberlist1736079005086,
|
Memberlist1736079005086,
|
||||||
ExtendViewValues1736084198860,
|
ExtendViewValues1736084198860,
|
||||||
FinishInternalIdTransfer1736505324488,
|
FinishInternalIdTransfer1736505324488,
|
||||||
|
ProtocolPresenceExcuse1737287798828,
|
||||||
],
|
],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
migrationsTransactionMode: "each",
|
migrationsTransactionMode: "each",
|
||||||
|
|
|
@ -13,6 +13,9 @@ export class protocolPresence {
|
||||||
@Column({ type: "boolean", default: false })
|
@Column({ type: "boolean", default: false })
|
||||||
absent: boolean;
|
absent: boolean;
|
||||||
|
|
||||||
|
@Column({ type: "boolean", default: true })
|
||||||
|
excused: boolean;
|
||||||
|
|
||||||
@ManyToOne(() => member, {
|
@ManyToOne(() => member, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
|
|
|
@ -12,6 +12,7 @@ export default abstract class ProtocolPresenceFactory {
|
||||||
return {
|
return {
|
||||||
memberId: record.member.id,
|
memberId: record.member.id,
|
||||||
absent: record.absent,
|
absent: record.absent,
|
||||||
|
excused: record.excused,
|
||||||
protocolId: record.protocolId,
|
protocolId: record.protocolId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ export abstract class NewsletterHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let members = await MemberService.getAll(0, 1000);
|
let members = await MemberService.getAll({ noLimit: true });
|
||||||
|
|
||||||
return members[0].filter((m) => queryMemberIds.includes(m.id));
|
return members[0].filter((m) => queryMemberIds.includes(m.id));
|
||||||
}
|
}
|
||||||
|
|
21
src/migrations/1737287798828-protocolPresenceExcuse.ts
Normal file
21
src/migrations/1737287798828-protocolPresenceExcuse.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||||
|
|
||||||
|
export class ProtocolPresenceExcuse1737287798828 implements MigrationInterface {
|
||||||
|
name = "ProtocolPresenceExcuse1737287798828";
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.addColumn(
|
||||||
|
"protocol_presence",
|
||||||
|
new TableColumn({
|
||||||
|
name: "excused",
|
||||||
|
type: "tinyint",
|
||||||
|
default: "1",
|
||||||
|
isNullable: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropColumn("protocol_presence", "excused");
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,19 @@ export default abstract class MemberService {
|
||||||
* @description get all members
|
* @description get all members
|
||||||
* @returns {Promise<[Array<member>, number]>}
|
* @returns {Promise<[Array<member>, number]>}
|
||||||
*/
|
*/
|
||||||
static async getAll(offset: number = 0, count: number = 25, search: string = ""): Promise<[Array<member>, number]> {
|
static async getAll({
|
||||||
|
offset = 0,
|
||||||
|
count = 25,
|
||||||
|
search = "",
|
||||||
|
noLimit = false,
|
||||||
|
ids = [],
|
||||||
|
}: {
|
||||||
|
offset?: number;
|
||||||
|
count?: number;
|
||||||
|
search?: string;
|
||||||
|
noLimit?: boolean;
|
||||||
|
ids?: Array<number>;
|
||||||
|
}): Promise<[Array<member>, number]> {
|
||||||
let query = dataSource
|
let query = dataSource
|
||||||
.getRepository(member)
|
.getRepository(member)
|
||||||
.createQueryBuilder("member")
|
.createQueryBuilder("member")
|
||||||
|
@ -54,9 +66,15 @@ export default abstract class MemberService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ids.length != 0) {
|
||||||
|
query = query.where({ id: ids });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noLimit) {
|
||||||
|
query = query.offset(offset).limit(count);
|
||||||
|
}
|
||||||
|
|
||||||
return await query
|
return await query
|
||||||
.offset(offset)
|
|
||||||
.limit(count)
|
|
||||||
.orderBy("member.lastname")
|
.orderBy("member.lastname")
|
||||||
.addOrderBy("member.firstname")
|
.addOrderBy("member.firstname")
|
||||||
.addOrderBy("member.nameaffix")
|
.addOrderBy("member.nameaffix")
|
||||||
|
|
|
@ -14,9 +14,19 @@
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<h2>Anwesenheit ({{presence.length}})</h2>
|
<h2>Anwesenheit ({{presence.length}})</h2>
|
||||||
<p>{{#each presence}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
|
<p>
|
||||||
|
{{#each presence}}{{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}}{{/each}}{{#unless
|
||||||
|
presence.length}}---{{/unless}}
|
||||||
|
</p>
|
||||||
<h2>Abwesenheit ({{absent.length}})</h2>
|
<h2>Abwesenheit ({{absent.length}})</h2>
|
||||||
<p>{{#each absent}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
|
<p>
|
||||||
|
entschuldigt: {{#each excused_absent}}{{this.firstname}} {{this.lastname}}{{#unless @last}},
|
||||||
|
{{/unless}}{{/each}}{{#unless excused_absent.length}}---{{/unless}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
unentschuldigt: {{#each unexcused_absent}}{{this.firstname}} {{this.lastname}}{{#unless @last}},
|
||||||
|
{{/unless}}{{/each}}{{#unless unexcused_absent.length}}---{{/unless}}
|
||||||
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<h2>Agenda</h2>
|
<h2>Agenda</h2>
|
||||||
{{#each agenda}}
|
{{#each agenda}}
|
||||||
|
|
|
@ -3,5 +3,6 @@ import { MemberViewModel } from "../member/member.models";
|
||||||
export interface ProtocolPresenceViewModel {
|
export interface ProtocolPresenceViewModel {
|
||||||
memberId: number;
|
memberId: number;
|
||||||
absent: boolean;
|
absent: boolean;
|
||||||
|
excused: boolean;
|
||||||
protocolId: number;
|
protocolId: number;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue