Add DV zuschuss

This commit is contained in:
philipp lang 2022-08-23 23:49:19 +02:00
parent 9d53dfc772
commit 5eee14e0de
20 changed files with 442 additions and 91 deletions

View File

@ -2,6 +2,7 @@
namespace App\Contribution;
use App\Country;
use App\Http\Controllers\Controller;
use App\Member\Member;
use App\Member\MemberResource;
@ -19,6 +20,8 @@ class ContributionController extends Controller
return Inertia::render('contribution/VIndex', [
'allMembers' => MemberResource::collection(Member::slangOrdered()->get()),
'countries' => Country::pluck('name', 'id'),
'defaultCountry' => Country::firstWhere('name', 'Deutschland')->id,
]);
}

110
app/Contribution/DvData.php Normal file
View File

@ -0,0 +1,110 @@
<?php
namespace App\Contribution;
use App\Country;
use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Spatie\LaravelData\Data;
class DvData extends Data implements PdfRepository
{
public function __construct(
public string $dateFrom,
public string $dateUntil,
public string $zipLocation,
public ?Country $country,
public array $members,
public ?string $filename = '',
public $type = 'F',
) {
}
public static function fromRequest(Request $request): self
{
return new self(
dateFrom: $request->dateFrom,
dateUntil: $request->dateUntil,
zipLocation: $request->zipLocation,
country: Country::findOrFail($request->country),
members: $request->members,
);
}
public function members(): Collection
{
return Member::whereIn('id', $this->members)->orderByRaw('lastname, firstname')->get();
}
public function memberShort(Member $member): string
{
return $member->isLeader() ? 'L' : '';
}
public function memberName(Member $member): string
{
return $member->lastname.', '.$member->firstname;
}
public function memberAddress(Member $member): string
{
return $member->fullAddress;
}
public function memberGender(Member $member): string
{
if (!$member->gender) {
return '';
}
return strtolower(substr($member->gender->name, 0, 1));
}
public function memberAge(Member $member): string
{
return (string) $member->getAge();
}
public function countryName(): string
{
return $this->country->name;
}
public function dateRange(): string
{
return Carbon::parse($this->dateFrom)->format('d.m.Y')
.' - '
.Carbon::parse($this->dateUntil)->format('d.m.Y');
}
public function getFilename(): string
{
return 'zuschuesse-dv';
}
public function getView(): string
{
return 'tex.zuschuss-dv';
}
public function getTemplate(): ?string
{
return 'zuschussdv';
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::PDFLATEX;
}
}

View File

@ -73,9 +73,9 @@ class SolingenData extends Data implements PdfRepository
return 'tex.zuschuss-stadt';
}
public function getTemplate(): string
public function getTemplate(): ?string
{
return 'efz';
return null;
}
public function setFilename(string $filename): static

View File

@ -91,9 +91,14 @@ class Member extends Model
return $this->firstname.' '.$this->lastname;
}
public function getFullAddressAttribute(): string
{
return $this->address.', '.$this->zip.' '.$this->location;
}
public function getEfzLink(): ?string
{
return $this->memberships()->whereHas('activity', fn (Builder $query) => $query->where('has_efz', true))->exists()
return $this->isLeader()
? route('efz', ['member' => $this])
: null;
}
@ -120,6 +125,16 @@ class Member extends Model
return $this->subscription->fee->nami_id;
}
public function isLeader(): bool
{
return $this->memberships()->whereHas('activity', fn (Builder $query) => $query->where('has_efz', true))->exists();
}
public function getAge(): int
{
return $this->birthday->diffInYears(now());
}
// ---------------------------------- Relations ----------------------------------
public function country(): BelongsTo
{

View File

@ -71,7 +71,7 @@ class MemberResource extends JsonResource
'without_efz_at' => $this->without_efz_at,
'multiply_pv' => $this->multiply_pv,
'multiply_more_pv' => $this->multiply_more_pv,
'age' => $this->birthday->diffInYears(now()),
'age' => $this->getModel()->getAge(),
];
}
}

View File

@ -47,7 +47,7 @@ class BillType extends Repository implements LetterRepository
return 'tex.bill';
}
public function getTemplate(): string
public function getTemplate(): ?string
{
return 'default';
}

View File

@ -49,7 +49,7 @@ class MemberEfzData extends Data implements PdfRepository
return 'tex.efz';
}
public function getTemplate(): string
public function getTemplate(): ?string
{
return 'efz';
}

View File

@ -27,7 +27,9 @@ class PdfGenerator implements Responsable
Storage::disk('temp')->put($this->dir.'/'.$this->repo->getFilename().'.tex', $this->compileView());
Storage::disk('temp')->makeDirectory($this->dir);
$this->copyTemplateTo(Storage::disk('temp')->path($this->dir));
if ($this->repo->getTemplate()) {
$this->copyTemplateTo(Storage::disk('temp')->path($this->dir));
}
$command = 'cd '.Storage::disk('temp')->path($this->dir);
$command .= ' && '.env($this->repo->getScript()->value).' --halt-on-error '.$this->repo->getFilename().'.tex';

View File

@ -10,7 +10,7 @@ interface PdfRepository
public function getView(): string;
public function getTemplate(): string;
public function getTemplate(): ?string;
public function getScript(): EnvType;
}

View File

@ -47,7 +47,7 @@ class RememberType extends Repository implements LetterRepository
return 'tex.remember';
}
public function getTemplate(): string
public function getTemplate(): ?string
{
return 'default';
}

View File

@ -5,7 +5,7 @@
<span v-show="required" class="text-red-800">&nbsp;*</span>
</span>
<div class="real-field-wrap" :class="`size-${size}`">
<select :disabled="disabled" :value="value" @change="trigger">
<select :disabled="disabled" :name="name" :value="value" @change="trigger">
<option v-if="placeholder" v-html="placeholder" :value="null"></option>
<option
@ -74,6 +74,9 @@ export default {
type: Number,
default: -1,
},
name: {
required: true,
},
hint: {},
options: {
default: function () {

View File

@ -18,6 +18,16 @@
required
></f-text>
<f-text id="zipLocation" name="zipLocation" v-model="values.zipLocation" label="PLZ / Ort" required></f-text>
<f-select
id="country"
:options="countries"
name="country"
v-model="values.country"
label="Land"
required
></f-select>
<div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2">
<f-text
class="col-span-2"
@ -52,6 +62,15 @@
>
Für Stadt erstellen
</button>
<button
target="_BLANK"
type="submit"
name="type"
value="\App\Contribution\DvData"
class="btn btn-primary mt-3 inline-block"
>
Für DV erstellen
</button>
</form>
</template>
@ -65,10 +84,14 @@ export default {
event_name: '',
dateFrom: '',
dateUntil: '',
zipLocation: '',
country: null,
},
};
},
props: {
countries: {},
defaultCountry: {},
allMembers: {},
},
computed: {
@ -105,5 +128,9 @@ export default {
this.onSubmitMemberResult(this.memberResults[0]);
},
},
created() {
this.values.country = this.defaultCountry;
},
};
</script>

View File

@ -1,10 +1,25 @@
<template>
<div class="sidebar flex flex-col">
<sidebar-header :links="indexLinks" @close="$emit('close')" @create="mode = 'create'; single = {}" title="Ausbildungen"></sidebar-header>
<sidebar-header
:links="indexLinks"
@close="$emit('close')"
@create="
mode = 'create';
single = {};
"
title="Ausbildungen"
></sidebar-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="completed_at" type="date" v-model="single.completed_at" label="Datum" required></f-text>
<f-select id="course_id" :options="courses" v-model="single.course_id" label="Baustein" required></f-select>
<f-select
id="course_id"
name="course_id"
:options="courses"
v-model="single.course_id"
label="Baustein"
required
></f-select>
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text>
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text>
<button type="submit" class="btn btn-primary">Absenden</button>
@ -20,14 +35,24 @@
<th></th>
</thead>
<tr v-for="course, index in value.courses" :key="index">
<tr v-for="(course, index) in value.courses" :key="index">
<td v-text="course.course_name"></td>
<td v-text="course.event_name"></td>
<td v-text="course.organizer"></td>
<td v-text="course.completed_at_human"></td>
<td class="flex">
<a href="#" @click.prevent="single = course; mode = 'edit'" class="inline-flex btn btn-warning btn-sm"><svg-sprite src="pencil"></svg-sprite></a>
<i-link href="#" @click.prevent="remove(course)" class="inline-flex btn btn-danger btn-sm"><svg-sprite src="trash"></svg-sprite></i-link>
<a
href="#"
@click.prevent="
single = course;
mode = 'edit';
"
class="inline-flex btn btn-warning btn-sm"
><svg-sprite src="pencil"></svg-sprite
></a>
<i-link href="#" @click.prevent="remove(course)" class="inline-flex btn btn-danger btn-sm"
><svg-sprite src="trash"></svg-sprite
></i-link>
</td>
</tr>
</table>
@ -39,22 +64,20 @@
import SidebarHeader from '../../components/SidebarHeader.vue';
export default {
data: function() {
data: function () {
return {
mode: null,
single: null,
indexLinks: [
{event: 'create', label: 'Neuer Kurs'}
]
indexLinks: [{event: 'create', label: 'Neuer Kurs'}],
};
},
props: {
courses: {},
value: {}
value: {},
},
components: { SidebarHeader },
components: {SidebarHeader},
methods: {
remove(payment) {
@ -72,18 +95,18 @@ export default {
submit() {
var _self = this;
this.mode === 'create'
this.mode === 'create'
? this.$inertia.post(`/member/${this.value.id}/course`, this.single, {
onFinish() {
_self.single = null;
}
})
onFinish() {
_self.single = null;
},
})
: this.$inertia.patch(`/member/${this.value.id}/course/${this.single.id}`, this.single, {
onFinish() {
_self.single = null;
}
});
}
}
onFinish() {
_self.single = null;
},
});
},
},
};
</script>

View File

@ -1,9 +1,41 @@
<template>
<div class="px-6 py-2 flex border-b border-gray-600 space-x-3">
<f-switch v-show="hasModule('bill')" id="ausstand" @input="reload" v-model="inner.ausstand" label="Nur Ausstände" size="sm"></f-switch>
<f-select v-show="hasModule('bill')" id="billKinds" @input="reload" :options="billKinds" v-model="inner.bill_kind" label="Rechnung" size="sm"></f-select>
<f-select id="activity_id" @input="reload" :options="activities" v-model="inner.activity_id" label="Tätigkeit" size="sm"></f-select>
<f-select id="subactivity_id" @input="reload" :options="subactivities" v-model="inner.subactivity_id" label="Untertätigkeit" size="sm"></f-select>
<f-switch
v-show="hasModule('bill')"
id="ausstand"
@input="reload"
v-model="inner.ausstand"
label="Nur Ausstände"
size="sm"
></f-switch>
<f-select
v-show="hasModule('bill')"
name="billKinds"
id="billKinds"
@input="reload"
:options="billKinds"
v-model="inner.bill_kind"
label="Rechnung"
size="sm"
></f-select>
<f-select
id="activity_id"
@input="reload"
:options="activities"
v-model="inner.activity_id"
label="Tätigkeit"
size="sm"
name="activity_id"
></f-select>
<f-select
id="subactivity_id"
@input="reload"
:options="subactivities"
v-model="inner.subactivity_id"
label="Untertätigkeit"
size="sm"
name="subactivity_id"
></f-select>
</div>
</template>
@ -11,10 +43,9 @@
import mergesQueryString from '../../mixins/mergesQueryString.js';
export default {
data: function() {
data: function () {
return {
inner: {}
inner: {},
};
},
@ -30,14 +61,13 @@ export default {
methods: {
reload() {
this.$inertia.visit(this.qs({filter: JSON.stringify(this.inner)}), {
preserveState: true
preserveState: true,
});
}
},
},
created() {
this.inner = this.value;
}
},
};
</script>

View File

@ -1,10 +1,32 @@
<template>
<div class="sidebar flex flex-col">
<sidebar-header :links="links" @create="mode = 'create'; single = {}" @close="$emit('close')" title="Mitgliedschaften"></sidebar-header>
<sidebar-header
:links="links"
@create="
mode = 'create';
single = {};
"
@close="$emit('close')"
title="Mitgliedschaften"
></sidebar-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-select id="activity_id" :options="activities" v-model="single.activity_id" label="Tätigkeit" required></f-select>
<f-select v-if="single.activity_id" :options="subactivities[single.activity_id]" id="subactivity_id" v-model="single.subactivity_id" label="Untertätigkeit"></f-select>
<f-select
id="activity_id"
name="activity_id"
:options="activities"
v-model="single.activity_id"
label="Tätigkeit"
required
></f-select>
<f-select
v-if="single.activity_id"
name="subactivity_id"
:options="subactivities[single.activity_id]"
id="subactivity_id"
v-model="single.subactivity_id"
label="Untertätigkeit"
></f-select>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
@ -17,13 +39,23 @@
<th></th>
</thead>
<tr v-for="membership, index in value.memberships" :key="index">
<tr v-for="(membership, index) in value.memberships" :key="index">
<td v-text="membership.activity_name"></td>
<td v-text="membership.subactivity_name"></td>
<td v-text="membership.human_date"></td>
<td class="flex">
<a href="#" @click.prevent="single = membership; mode = 'edit'" class="inline-flex btn btn-warning btn-sm"><svg-sprite src="pencil"></svg-sprite></a>
<i-link href="#" @click.prevent="remove(membership)" class="inline-flex btn btn-danger btn-sm"><svg-sprite src="trash"></svg-sprite></i-link>
<a
href="#"
@click.prevent="
single = membership;
mode = 'edit';
"
class="inline-flex btn btn-warning btn-sm"
><svg-sprite src="pencil"></svg-sprite
></a>
<i-link href="#" @click.prevent="remove(membership)" class="inline-flex btn btn-danger btn-sm"
><svg-sprite src="trash"></svg-sprite
></i-link>
</td>
</tr>
</table>
@ -35,17 +67,15 @@
import SidebarHeader from '../../components/SidebarHeader.vue';
export default {
data: function() {
data: function () {
return {
mode: null,
single: null,
links: [
{ event: 'create', label: 'Neu' }
]
links: [{event: 'create', label: 'Neu'}],
};
},
components: { SidebarHeader },
components: {SidebarHeader},
methods: {
remove(membership) {
@ -53,7 +83,7 @@ export default {
},
accept(payment) {
this.$inertia.patch(`/member/${this.value.id}/payment/${payment.id}`, { ...payment, status_id: 3 });
this.$inertia.patch(`/member/${this.value.id}/payment/${payment.id}`, {...payment, status_id: 3});
},
openLink(link) {
@ -71,19 +101,19 @@ export default {
onFinish() {
_self.single = null;
_self.mode = null;
}
},
};
this.mode === 'create'
this.mode === 'create'
? this.$inertia.post(`/member/${this.value.id}/membership`, this.single, options)
: this.$inertia.patch(`/member/${this.value.id}/membership/${this.single.id}`, this.single, options);
}
},
},
props: {
value: {},
activities: {},
subactivities: {},
}
},
};
</script>

View File

@ -1,11 +1,33 @@
<template>
<div class="sidebar flex flex-col">
<sidebar-header :links="indexLinks" @close="$emit('close')" @create="mode = 'create'; single = {}" title="Zahlungen"></sidebar-header>
<sidebar-header
:links="indexLinks"
@close="$emit('close')"
@create="
mode = 'create';
single = {};
"
title="Zahlungen"
></sidebar-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="nr" v-model="single.nr" label="Jahr" required></f-text>
<f-select id="subscription_id" :options="subscriptions" v-model="single.subscription_id" label="Beitrag" required></f-select>
<f-select id="status_id" :options="statuses" v-model="single.status_id" label="Status" required></f-select>
<f-select
id="subscription_id"
name="subscription_id"
:options="subscriptions"
v-model="single.subscription_id"
label="Beitrag"
required
></f-select>
<f-select
id="status_id"
name="status_id"
:options="statuses"
v-model="single.status_id"
label="Status"
required
></f-select>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
@ -18,20 +40,45 @@
<th></th>
</thead>
<tr v-for="payment, index in value.payments" :key="index">
<tr v-for="(payment, index) in value.payments" :key="index">
<td v-text="payment.nr"></td>
<td v-text="payment.status_name"></td>
<td v-text="payment.subscription_name"></td>
<td class="flex">
<a href="#" @click.prevent="single = payment; mode = 'edit'" class="inline-flex btn btn-warning btn-sm"><svg-sprite src="pencil"></svg-sprite></a>
<i-link v-show="!payment.is_accepted" href="#" @click.prevent="accept(payment)" class="inline-flex btn btn-success btn-sm"><svg-sprite src="check"></svg-sprite></i-link>
<i-link href="#" @click.prevent="remove(payment)" class="inline-flex btn btn-danger btn-sm"><svg-sprite src="trash"></svg-sprite></i-link>
<a
href="#"
@click.prevent="
single = payment;
mode = 'edit';
"
class="inline-flex btn btn-warning btn-sm"
><svg-sprite src="pencil"></svg-sprite
></a>
<i-link
v-show="!payment.is_accepted"
href="#"
@click.prevent="accept(payment)"
class="inline-flex btn btn-success btn-sm"
><svg-sprite src="check"></svg-sprite
></i-link>
<i-link href="#" @click.prevent="remove(payment)" class="inline-flex btn btn-danger btn-sm"
><svg-sprite src="trash"></svg-sprite
></i-link>
</td>
</tr>
</table>
</div>
<div class="flex flex-col pb-6 px-6">
<a href="#" @click.prevent="openLink(link)" :class="{'disabled': link.disabled}" target="_BLANK" v-for="link, index in value.payment_links" :key="index" class="mt-1 text-center btn btn-primary" v-text="link.label"></a>
<a
href="#"
@click.prevent="openLink(link)"
:class="{disabled: link.disabled}"
target="_BLANK"
v-for="(link, index) in value.payment_links"
:key="index"
class="mt-1 text-center btn btn-primary"
v-text="link.label"
></a>
</div>
</div>
</template>
@ -40,17 +87,15 @@
import SidebarHeader from '../../components/SidebarHeader.vue';
export default {
data: function() {
data: function () {
return {
mode: null,
single: null,
indexLinks: [
{event: 'create', label: 'Neue Zahlung'}
]
indexLinks: [{event: 'create', label: 'Neue Zahlung'}],
};
},
components: { SidebarHeader },
components: {SidebarHeader},
methods: {
remove(payment) {
@ -58,7 +103,7 @@ export default {
},
accept(payment) {
this.$inertia.patch(`/member/${this.value.id}/payment/${payment.id}`, { ...payment, status_id: 3 });
this.$inertia.patch(`/member/${this.value.id}/payment/${payment.id}`, {...payment, status_id: 3});
},
openLink(link) {
@ -72,24 +117,24 @@ export default {
submit() {
var _self = this;
this.mode === 'create'
this.mode === 'create'
? this.$inertia.post(`/member/${this.value.id}/payment`, this.single, {
onFinish() {
_self.single = null;
}
})
onFinish() {
_self.single = null;
},
})
: this.$inertia.patch(`/member/${this.value.id}/payment/${this.single.id}`, this.single, {
onFinish() {
_self.single = null;
}
});
}
onFinish() {
_self.single = null;
},
});
},
},
props: {
value: {},
subscriptions: {},
statuses: {},
}
},
};
</script>

View File

@ -46,7 +46,13 @@
<!-- ***************************** Hauptbereich ****************************** -->
<div class="grow">
<div class="grid grid-cols-2 gap-3 p-4" v-show="menuTitle == 'Stammdaten'">
<f-select id="gender_id" :options="genders" v-model="inner.gender_id" label="Geschlecht"></f-select>
<f-select
id="gender_id"
name="gender_id"
:options="genders"
v-model="inner.gender_id"
label="Geschlecht"
></f-select>
<f-text id="firstname" v-model="inner.firstname" label="Vorname" required></f-text>
<f-text id="lastname" v-model="inner.lastname" label="Nachname" required></f-text>
<f-text id="address" v-model="inner.address" label="Adresse" required></f-text>
@ -54,12 +60,19 @@
<f-text id="zip" v-model="inner.zip" label="PLZ" required></f-text>
<f-text id="location" v-model="inner.location" label="Ort" required></f-text>
<f-text type="date" id="birthday" v-model="inner.birthday" label="Geburtsdatum" required></f-text>
<f-select :options="regions" id="region_id" v-model="inner.region_id" label="Bundesland"></f-select>
<f-select
:options="regions"
name="region_id"
id="region_id"
v-model="inner.region_id"
label="Bundesland"
></f-select>
<f-select
:options="countries"
id="country_id"
v-model="inner.country_id"
label="Land"
name="country_id"
required
></f-select>
<f-select
@ -67,6 +80,7 @@
id="nationality_id"
v-model="inner.nationality_id"
label="Staatsangehörigkeit"
name="nationality_id"
required
></f-select>
<f-text
@ -83,6 +97,7 @@
id="first_activity_id"
v-model="inner.first_activity_id"
label="Erste Tätigkeit"
name="first_activity_id"
required
></f-select>
<f-select
@ -91,6 +106,7 @@
id="first_subactivity_id"
v-model="inner.first_subactivity_id"
label="Erste Untertätigkeit"
name="first_subactivity_id"
required
></f-select>
</div>
@ -110,6 +126,7 @@
id="bill_kind_id"
v-model="inner.bill_kind_id"
label="Rechnung versenden über"
name="bill_kind_id"
></f-select>
</div>
<div class="grid grid-cols-2 gap-3 p-4" v-show="menuTitle == 'Prävention'">
@ -200,6 +217,7 @@
id="subscription_id"
v-model="inner.subscription_id"
label="Beitrag"
name="subscription_id"
></f-select>
<f-textarea
class="col-span-2"

View File

@ -1,8 +1,14 @@
<template>
<form class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="name" v-model="inner.name" label="Name" required></f-text>
<f-select id="fee_id" :options="fees" v-model="inner.fee_id" label="Nami-Beitrag" required></f-select>
<f-select
id="fee_id"
name="fee_id"
:options="fees"
v-model="inner.fee_id"
label="Nami-Beitrag"
required
></f-select>
<f-text id="amount" v-model="inner.amount" label="Interner Beitrag" required></f-text>
<button type="submit" class="btn btn-primary">Absenden</button>
@ -11,7 +17,7 @@
<script>
export default {
data: function() {
data: function () {
return {
inner: {},
};
@ -28,11 +34,11 @@ export default {
this.mode === 'create'
? this.$inertia.post(`/subscription`, this.inner)
: this.$inertia.patch(`/subscription/${this.inner.id}`, this.inner);
}
},
},
created() {
this.inner = this.data;
}
},
};
</script>

View File

@ -0,0 +1,39 @@
\documentclass[a4paper,landscape]{article}
\usepackage[landscape,top=0cm,left=0cm,bottom=0cm,right=0cm]{geometry}
\usepackage{tikz}
\usepackage{background}
\usepackage{blindtext}
\usetikzlibrary{matrix, shapes.misc, calc}
\pagestyle{empty}
\setlength{\parindent}{0cm}
\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {teilnahmeliste.pdf}}}
\begin{document}
\noindent \sffamily
@foreach($data->members()->chunk(17) as $chunk)
\begin{tikzpicture}[remember picture,overlay,yscale=-1]
\node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<<!!$data->dateRange()!!>>>}}};
\node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<<!!$data->zipLocation!!>>>}}};
\node[anchor=base west] at (242.7mm,41.62mm) {\bfseries{\large{<<<!!$data->countryName()!!>>>}}};
\node[thick, cross out,draw=black,text width=2.4mm, text height=2.4mm, inner sep=0mm] at (17.76mm,47.10mm) {};
@foreach($chunk as $i => $member)
\node[anchor=base, text width=7.75mm, align=center] at ($(16.35mm, 76.6mm + 7mm * <<<$i % 17>>>)$) {<<<$i+1>>>};
\node[anchor=base, text width=18mm, align=center] at ($(32.55mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberShort($member)>>>};
\node[anchor=base, text width=70mm, align=center] at ($(80.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberName($member)>>>};
\node[anchor=base, text width=118mm, align=center] at ($(178.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberAddress($member)>>>};
\node[anchor=base, text width=16mm, align=center] at ($(249.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberGender($member)>>>};
\node[anchor=base, text width=16mm, align=center] at ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$data->memberAge($member)>>>};
@endforeach
\end{tikzpicture}
\pagebreak
@endforeach
\end{document}