Add a dues-invoicing feature: send, track and remind on membership fees
New DuesInvoice model (one per membership, resendable) plus club.services.invoicing: resolves the best email to invoice (the member's own, else a parent/guardian's), snapshots the outstanding balance and a due date on send, and mails a branded HTML invoice (same club-colour email shell as the parent-claim email) with a WeasyPrint PDF attached when the native libs are available. Dues & billing gains a bulk "Send invoice" action (checkbox selection + a shared due-in-days prompt), a per-row invoice status column, a staff-facing invoice detail/PDF page, and a push-button "Send reminders" action for every sent, unpaid invoice past its own due date. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
@@ -785,6 +785,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse-plus {
|
||||
@layer daisyui.l1.l2 {
|
||||
> .collapse-title:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition-property: all;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
top: 0.9rem;
|
||||
inset-inline-end: 1.4rem;
|
||||
--tw-content: "+";
|
||||
content: var(--tw-content);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
@@ -1330,6 +1350,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.validator-hint {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
visibility: hidden;
|
||||
margin-top: calc(0.25rem * 2);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
.validator {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
&:user-valid, &:has(:user-valid) {
|
||||
@@ -1353,6 +1380,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse-open {
|
||||
@layer daisyui.l1.l2 {
|
||||
grid-template-rows: max-content 1fr;
|
||||
> .collapse-content {
|
||||
--overflow-delay: 0.2s;
|
||||
overflow: revert-layer;
|
||||
content-visibility: visible;
|
||||
min-height: fit-content;
|
||||
padding-bottom: 1rem;
|
||||
@supports not (content-visibility: visible) {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapse {
|
||||
visibility: collapse;
|
||||
}
|
||||
@@ -1421,6 +1463,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.toast {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: fixed;
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: calc(0.25rem * 4);
|
||||
top: auto;
|
||||
bottom: calc(0.25rem * 4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem * 2);
|
||||
background-color: transparent;
|
||||
translate: var(--toast-x, 0) var(--toast-y, 0);
|
||||
width: max-content;
|
||||
max-width: calc(100vw - 2rem);
|
||||
& > * {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: toast 0.25s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggle {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
border: var(--border) solid currentColor;
|
||||
@@ -1873,6 +1936,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.aura {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
--aura-padding: 0.125rem;
|
||||
padding: var(--aura-padding);
|
||||
border-radius: calc(var(--aura-padding) + var(--aura-radius, var(--radius-box)));
|
||||
animation: aura var(--tw-duration, 6s) linear infinite;
|
||||
background-image: conic-gradient(from var(--aura-angle), transparent 225deg, currentColor);
|
||||
&:has( > .card, > .alert) {
|
||||
--aura-radius: var(--radius-box);
|
||||
}
|
||||
&:has( > .btn, > .input, > .select) {
|
||||
--aura-radius: var(--radius-field);
|
||||
}
|
||||
&:has( > .checkbox, > .toggle, > .badge) {
|
||||
--aura-radius: var(--radius-selector);
|
||||
}
|
||||
&:before, &:after {
|
||||
animation: inherit;
|
||||
background-color: inherit;
|
||||
background-image: inherit;
|
||||
border-radius: inherit;
|
||||
position: absolute;
|
||||
top: calc(1 / 2 * 100%);
|
||||
left: calc(1 / 2 * 100%);
|
||||
z-index: 0;
|
||||
display: block;
|
||||
opacity: 70%;
|
||||
filter: blur(0.25rem);
|
||||
translate: -50% -50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
}
|
||||
&:after {
|
||||
opacity: 30%;
|
||||
filter: blur(1rem);
|
||||
}
|
||||
& > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.steps {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: inline-grid;
|
||||
@@ -2395,6 +2503,48 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.rating {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
--size: var(--size-selector, 0.25rem) * 6;
|
||||
input {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
}
|
||||
* {
|
||||
border-radius: 0;
|
||||
background-color: var(--color-base-content);
|
||||
opacity: 20%;
|
||||
width: calc(var(--size) * 1);
|
||||
height: calc(var(--size));
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: rating 0.25s ease-out;
|
||||
}
|
||||
}
|
||||
.rating-hidden {
|
||||
width: calc(0.25rem * 2);
|
||||
background-color: transparent;
|
||||
}
|
||||
:checked, [aria-checked="true"], [aria-current="true"], :has( ~ :checked, ~ [aria-checked="true"], ~ [aria-current="true"]) {
|
||||
opacity: 100%;
|
||||
}
|
||||
:focus-visible {
|
||||
scale: 1.1;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: scale 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
:active:focus {
|
||||
animation: none;
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
@layer daisyui.l1.l2 {
|
||||
--size: var(--size-selector, 0.25rem) * 6;
|
||||
}
|
||||
}
|
||||
.navbar {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
@@ -2545,6 +2695,30 @@
|
||||
.inset-0 {
|
||||
inset: 0;
|
||||
}
|
||||
.dropdown-right {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: right;
|
||||
--anchor-v: span-bottom;
|
||||
.dropdown-content {
|
||||
inset-inline-start: 100%;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
transform-origin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-left {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: left;
|
||||
--anchor-v: span-bottom;
|
||||
.dropdown-content {
|
||||
inset-inline-end: 100%;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
transform-origin: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dropdown-end {
|
||||
@layer daisyui.l1.l2 {
|
||||
--anchor-h: span-left;
|
||||
@@ -3041,6 +3215,26 @@
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
.tab-content {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
order: var(--tabcontent-order);
|
||||
display: none;
|
||||
border-color: transparent;
|
||||
--tabcontent-radius-ss: var(--radius-box);
|
||||
--tabcontent-radius-se: var(--radius-box);
|
||||
--tabcontent-radius-es: var(--radius-box);
|
||||
--tabcontent-radius-ee: var(--radius-box);
|
||||
--tabcontent-order: 1;
|
||||
width: 100%;
|
||||
height: calc(100% - var(--tab-height) + var(--border));
|
||||
margin: var(--tabcontent-margin);
|
||||
border-width: var(--border);
|
||||
border-start-start-radius: var(--tabcontent-radius-ss);
|
||||
border-start-end-radius: var(--tabcontent-radius-se);
|
||||
border-end-start-radius: var(--tabcontent-radius-es);
|
||||
border-end-end-radius: var(--tabcontent-radius-ee);
|
||||
}
|
||||
}
|
||||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
@@ -3443,6 +3637,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.fieldset-label {
|
||||
@layer daisyui.l1.l2.l3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(0.25rem * 1.5);
|
||||
color: var(--color-base-content);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
|
||||
}
|
||||
&:has(input) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
border-width: var(--border);
|
||||
border-color: var(--alert-border-color, var(--color-base-200));
|
||||
@@ -3590,6 +3798,9 @@
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
.inline-grid {
|
||||
display: inline-grid;
|
||||
}
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
@@ -3867,6 +4078,9 @@
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -3900,6 +4114,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.aura-glow {
|
||||
@layer daisyui.l1.l2 {
|
||||
animation: none;
|
||||
background-image: radial-gradient(closest-corner at center, currentColor 0%, transparent 90%);
|
||||
&:before {
|
||||
animation: aura-glow var(--tw-duration, 6s) ease-out infinite;
|
||||
}
|
||||
&:after {
|
||||
animation: aura-glow-after var(--tw-duration, 6s) ease-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: var(--animate-pulse);
|
||||
}
|
||||
@@ -4288,6 +4514,9 @@
|
||||
--btn-shadow: 0 0 0 0 oklch(0% 0 0/0);
|
||||
}
|
||||
}
|
||||
.mask-repeat {
|
||||
mask-repeat: repeat;
|
||||
}
|
||||
.stroke-2 {
|
||||
stroke-width: 2;
|
||||
}
|
||||
@@ -4788,6 +5017,9 @@
|
||||
text-decoration-line: none;
|
||||
}
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.antialiased {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@@ -4886,6 +5118,14 @@
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.ease-in-out {
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
.ease-out {
|
||||
--tw-ease: var(--ease-out);
|
||||
transition-timing-function: var(--ease-out);
|
||||
}
|
||||
.input-lg {
|
||||
@layer daisyui.l1.l2 {
|
||||
--in-size-mul: 12;
|
||||
@@ -5938,6 +6178,10 @@
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-ease {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
@@ -6002,6 +6246,7 @@
|
||||
--tw-backdrop-opacity: initial;
|
||||
--tw-backdrop-saturate: initial;
|
||||
--tw-backdrop-sepia: initial;
|
||||
--tw-ease: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user