Compare commits

..

89 commits

Author SHA1 Message Date
c648fe8c52 Update changelog for 1.19. 2025-06-11 17:18:03 +02:00
42620b38ff Preliminary release of 1.19. 2025-04-16 22:09:28 +02:00
b0b558c8d4 Add councilml predicate to rada-ml-pred. 2025-04-16 22:08:52 +02:00
c06bc95b36 Fix handling missing MLs DB. 2025-04-16 21:41:07 +02:00
37b608ab67 Add dry run support. 2025-04-16 21:21:08 +02:00
5f4724874e Bump version to 1.19-dev. 2025-01-30 21:43:04 +01:00
9f5877d3f0 Remove mailman2 support. 2025-01-30 21:42:28 +01:00
ac83dd9c72 Remove nonexistent option from manpage. 2025-01-30 21:39:51 +01:00
b324516514 Fix amount formatting for QR code for integer amounts. 2025-01-07 11:42:21 +01:00
1d523a0495 Prepare release 1.18. 2025-01-04 17:45:16 +01:00
17ce5cc126 Finish QR code integration. 2025-01-03 17:08:16 +01:00
4d73afe3c5 Preliminary QR code embedding. 2025-01-03 16:56:59 +01:00
bbbc6527a0 Ensure proper amount format and prepare for generating QR code. 2025-01-03 11:34:18 +01:00
306b9cb20e Initial import of QR payment implementation. 2025-01-03 11:00:44 +01:00
826a5f1070 Update copyright years. 2025-01-02 20:50:50 +01:00
5052a8d46f Start work on changelog and banner for 1.18 version. 2025-01-02 20:45:33 +01:00
fa8466cfff Fix typo prolems. 2025-01-02 20:42:29 +01:00
cebe6a6cf7 Finish almost final version of attendance sheet. 2025-01-02 19:34:13 +01:00
df1a30eead Typography improvements. 2025-01-02 18:09:03 +01:00
c8c71f8465 Preliminary longtable version of attendance sheet. 2025-01-02 17:42:13 +01:00
6cfdf705c8 Finish new stats. 2025-01-02 16:58:41 +01:00
227787597d Finish stats for debts. 2025-01-02 16:22:37 +01:00
7dbdd3ea6e Balance summaries for all members over time. 2025-01-02 16:10:00 +01:00
0e9cfd546b Add data for graph of expected income. 2025-01-02 15:18:53 +01:00
b25fbd407d Split out mbase-stats into separate query module. 2025-01-02 15:06:32 +01:00
e02853edc7 Preliminary version of attendance sheet. 2024-12-26 22:20:17 +01:00
53be61d345 Generate date and GM number. 2024-12-26 21:21:31 +01:00
51a108ce64 Generate file based on command-line argument. 2024-12-26 21:08:28 +01:00
fe42315cd9 Number of active months. 2024-12-26 20:58:14 +01:00
9eb835fa72 Names cleanup, alignment and amount formatting. 2024-12-26 20:26:41 +01:00
eff186cb4c Start work on attendance sheet. 2024-12-26 20:11:01 +01:00
a86063e722 Release 1.17. 2024-10-01 20:36:28 +02:00
9562d0fa61 Fix handling members without any fees or payments. 2024-10-01 20:34:44 +02:00
09b971ad93 Fix soon expires list append. 2024-09-10 20:20:33 +02:00
f95f7a0543 Revert "When creating cal-period, ensure months for periods."
This reverts commit 488499cf23.
2024-09-10 18:54:55 +02:00
488499cf23 When creating cal-period, ensure months for periods. 2024-09-10 18:49:19 +02:00
065d406e9c Coerce suspend start to month when calculating suspended months. 2024-09-10 18:42:19 +02:00
dbc52833f0 Update relevant copyright years. 2024-07-02 21:40:38 +02:00
661d754083 Missing format import. 2024-07-02 20:58:11 +02:00
6a82821626 Start writing down 1.17 changes. 2024-07-02 20:52:27 +02:00
7d1101657f Add support for full email addresses. 2024-07-02 20:50:43 +02:00
8c436f6910 Update historical changelog. 2024-07-02 20:31:49 +02:00
1840f5675b Fix erroneous newline when sending notifications. 2024-07-02 20:21:33 +02:00
bc5db8db99 Current totals. 2024-06-11 21:33:12 +02:00
079551e41a Proper lookup. 2024-06-11 21:29:06 +02:00
90930391d0 Wrong list ref. 2024-06-11 21:25:25 +02:00
0906f9d27c List spec fee. 2024-06-11 21:23:15 +02:00
8a3c812797 One more column. 2024-06-11 20:21:05 +02:00
ce74a8962d Add current fee column. 2024-06-11 20:12:16 +02:00
85af3fcff3 Release 1.16.2 with -mlsync fix. 2024-05-07 20:27:07 +02:00
8b6e1955ef Export rada-ml-pred? for backwards compatibility. 2024-05-07 20:24:00 +02:00
2a7fb0d735 Release 1.16.1 2024-04-02 19:45:57 +02:00
e1bb1885b2 Add EUR account. 2024-04-02 19:43:27 +02:00
0a762ccb1d Improve changelog for 1.16. 2024-02-09 15:18:24 +01:00
6915cc0e21 Fix spacing and bump version. 2024-02-09 15:14:46 +01:00
6947dd37b3 Report ML check status in summary emails. 2024-02-09 15:13:09 +01:00
d24b7c4136 Move more to the new mailinglist module. 2024-02-09 15:05:49 +01:00
fabb387ba1 Split out mailinglist check base. 2024-02-09 15:01:21 +01:00
708268d91d Update the changelog. 2024-02-09 14:53:34 +01:00
a9f5fc74e4 Add members expiring soon to the summary email. 2024-02-09 14:51:26 +01:00
c458dc3900 Use the same algorithm for expected income in summary emails. 2024-02-09 14:44:24 +01:00
dcf6d8937f Update changelog. 2024-02-09 14:30:06 +01:00
15888b7e3e Fix computing expected income based on actual fees and discounts. 2024-02-09 14:28:17 +01:00
dc3044026c Fix utf-8 3-byte handling. 2024-02-08 21:05:08 +01:00
ed55660c80 Add finished issues to changelog for next version. 2024-01-16 22:26:18 +01:00
d0771e130a Remove debug output, bump version to -dev. 2024-01-16 22:16:51 +01:00
6282a934c6 Always handle lines starting with # as comment. 2024-01-16 22:14:41 +01:00
707bb1d61e More debugging. 2024-01-16 22:13:17 +01:00
ba2c753109 Allow parsing config lines without comments. 2024-01-16 22:10:56 +01:00
2674f08674 Print line-by-line for debugging. 2024-01-16 22:03:32 +01:00
3629844743 Convert condition to list. 2024-01-16 22:01:27 +01:00
65c7155ba3 Log exception details. 2024-01-16 21:58:30 +01:00
939af54e87 Sync mlcheck with mlsync. 2024-01-16 21:54:10 +01:00
a64ab232c6 Fix tests. 2024-01-16 21:50:13 +01:00
2baffe570b Update changelog for 1.15.1 2024-01-02 13:12:50 +01:00
9b165490e5 Bump version to 1.15.1 2024-01-02 13:02:15 +01:00
3b68a9f834 Fix info without loaded ML. 2024-01-02 13:01:57 +01:00
0e82221c16 Fix current month setting for members-fees calculation. 2024-01-02 12:59:33 +01:00
778f89717f Bump version to 1.15 2023-12-24 21:35:38 +01:00
5f03e0c251 Another SEPA parser. 2023-12-24 21:33:53 +01:00
4f59fbc6cf Document member file format changes. 2023-12-24 21:27:36 +01:00
0b70563b10 Support for multiple phones. 2023-12-24 21:25:22 +01:00
af5976ad43 Add changelog. 2023-12-23 20:52:01 +01:00
c987ac6c81 Use specific fee in all computations. 2023-12-23 19:57:40 +01:00
28dd25998b Match fee periods properly. 2023-12-23 19:48:43 +01:00
b9030db455 Formatting of new fee period amounts. 2023-12-18 23:02:35 +01:00
055f7ba030 Parsing of amount in fee period specification. 2023-12-18 22:56:23 +01:00
259a2664a0 Parse feestart/feestop as generic start/stop. 2023-12-18 22:39:52 +01:00
c00b0f8283 Increase membership fees starting 2024-01. 2023-12-18 10:42:29 +01:00
37 changed files with 1611 additions and 812 deletions

205
CHANGELOG.md Normal file
View file

@ -0,0 +1,205 @@
ChangeLog
=========
1.19 - released 2025-04-16
--------------------------
* manpage updated
* added -n option for dry-runs
* removed mailman 2.x support
* added "councilml" start/stop support for member files
1.18 - released 2025-01-06
--------------------------
* fix typo in members-print
* create LaTeX source of general meeting attendance sheet
* add expected income, cash flow and average age to stats
* add QR code payment in CZK on members' payments pages'
1.17 - released 2024-10-01
--------------------------
* add "Current Fee" column to -fees to see special discounts
* add EUR account to members page
* fix erroneous newlines in cronjobs
* add support for full RFC email addresses in *email-from* configuration
* handling of members without any fees or payments
1.16.2 - released 2024-05-07
----------------------------
* fix rada-ml-pred? in -mlsync
1.16.1 - released 2024-04-02
----------------------------
* add EUR account for paying membership fees to member's page
1.16 - released 2024-02-09
--------------------------
* handle # character at weird positions in DokuWiki users.auth.php
* unify -mlsync and -mlcheck handling of member predicates
* handle unicode characters with 3-byte UTF-8 representation correctly
* calculate expected income with respect to discounts granted
* report soon-expiring members in the summary emails
* report mailing lists check status in summary emails
1.15.1 - released 2024-01-02
----------------------------
* fix calculating historical membership fee (was erroneously based on
current date)
* fix showing basic information without MLs loaded
1.15 - released 2023-12-24
--------------------------
* increase membership fees starting 2024-01 (specification.rkt)
* add support for explicit fee amounts for specified period
1.14 - released 2023-12-06
--------------------------
* add support for dynamic terminal size
* use table cell formatting instead of paragraph formatting everywhere
* fix sqlite3 database locking issue
* allow limiting -fees output to -active only
1.13 - released 2023-12-05
--------------------------
* add dokuwiki problems to summary emails
* handle more SEPA payments
1.12 - released 2023-11-16
--------------------------
* switch to eggs: srfi-1, sqlite3
* semi-automatic export for brmdoor
* improvements of summary emails for council
* redirect dokuwiki plugin to login page if not logged in
* sync council and revision mailing lists
1.11 - released 2023-09-23
--------------------------
* add support for CC in emails
* update manual page
* setup new cron jobs
1.10 - released 2023-09-17
--------------------------
* direct access of mailman 3 database
1.9 - released 2023-09-16
-------------------------
* implement support for mailman 3
* add total debt to long-term debtors listings
1.8 - released 2023-07-29
------------------------
* remove old compatibility static web pages generator
* update documentation
* update Fio fetcher to handle new limits imposed by the bank
* output plain list of active members (used by BrmBar project)
1.7 - released 2023-07-04
-------------------------
* include current month in stats
* right-alignment in table cells
* functionality improvements of dokuwiki plugin
* checking council group between dokuwiki and members database
1.6.2 - released 2023-06-29
---------------------------
* fix passing members to remove_members mailman binary
1.6.1 - released 2023-06-27
---------------------------
* fix ML removal
* fix sync re-read
1.6 - released 2023-06-27
-------------------------
* dokuwiki plugin
* delete generated files for destroyed members
* verify dokuwiki users information
1.5 - released 2023-06-19
-------------------------
* improved table renderer
* show membership fees and payments balances history
* improved generator of static web pages
1.4 - released 2023-05-26
-------------------------
* vim and joe syntax highlighting support
* improved Fio bank statement fetcher and merger
1.3 - released 2023-05-22
-------------------------
* organizational bodies membership
1.2.1 - released 2023-05-19
---------------------------
* fix email string argument passing
* use bi-directional mailman communication
1.2 - released 2023-05-19
-------------------------
* split configuration and action command-line options
* support for git annotate
* show suspended members that are about to expire
* optimized utf-8 support
1.1 - released 2023-05-14
-------------------------
* support for suppressing output (used in cron jobs)
* sorted members in notifications
* report missing keys in member files
* internal ML membership synchronization
1.0 - released 2023-04-23
-------------------------
This was the first oficially released version which contains all the
functionality required to take over the original solution.
* parsing and interpreting member files with specified grammar
* basic support for start/stop periods
* command-line arguments support with integrated help display
* static builds
* cards export for BrmDoor project
* data validation and error reporting
* improved manual credit handling
* member id validation and generation
* export of gnuplot-compatible statistics
* static web data generation
* table formatting with color support
* member fees and payments accounting
* support for multiple join/destroy periods
* period-based exchange rates lookups
* unpaired transactions handling
* internal ML membership check
* notifications for both council and members with debts
* universal Fio bank account statement fetcher
* preliminary SEPA payment parsing
* programming modules documentation
* git status support
* sample configuration
* manual page

View file

@ -9,7 +9,7 @@ License
ISC License ISC License
Copyright 2023 Brmlab, z.s. Copyright 2023-2024 Brmlab, z.s.
Dominik Pantůček <dominik.pantucek@trustica.cz> Dominik Pantůček <dominik.pantucek@trustica.cz>
Permission to use, copy, modify, and/or distribute this software Permission to use, copy, modify, and/or distribute this software

View file

@ -56,7 +56,6 @@ Processed source is scanned for known keys. Known keys are:
* nick * nick
* name * name
* mail * mail
* phone
* born * born
Multiple instances of single key are considered an error. Multiple instances of single key are considered an error.
@ -83,6 +82,7 @@ line numbers as the value for such key. Multikeys are:
* revisionstop * revisionstop
* grantstart * grantstart
* grantstop * grantstop
* phone
The result is a valid dictionary of keys and multikeys. The result is a valid dictionary of keys and multikeys.
@ -104,7 +104,8 @@ periods.
The joined key is converted into a month value. The joined key is converted into a month value.
Card and desfire lists are parsed to get lists of card id and optional Card and desfire lists are parsed to get lists of card id and optional
comment. comment. The same processing is used for phone to support multiple
phone numbers.
Credit list is parsed to get a list of amounts and optional comments. Credit list is parsed to get a list of amounts and optional comments.

View file

@ -273,10 +273,6 @@ Specify member by nickname.
.B \-destroyed .B \-destroyed
Show destroyed members in \fB-fees\fR action as well. Show destroyed members in \fB-fees\fR action as well.
.TP
.B \-ml-all
Load all mailman list memberships to show them in members info.
.SH "FILES" .SH "FILES"
All the information about members is stored in in members file in the All the information about members is stored in in members file in the

View file

@ -1,22 +0,0 @@
set terminal pngcairo size 1000,600
set title "Members stats"
set output 'members-base-stats-2023-11.png'
src='members-base-stats-2023-11.data'
set timefmt "%Y-%m"
set xdata time
set format x "%Y-%m"
set xlabel "Month"
set ylabel "Members"
set grid
set key out right
plot[1420066800:][0:] \
src u 1:3 w l lw 2 t 'active', \
src u 1:4 w l t 'suspended', \
src u 1:5 w l t 'students', \
src u 1:6 w l t 'destroyed'

View file

@ -42,7 +42,9 @@ HACKERBASE-DEPS=hackerbase.scm cal-month.import.scm \
tests.import.scm notifications.import.scm logging.import.scm \ tests.import.scm notifications.import.scm logging.import.scm \
progress.import.scm cal-period.import.scm \ progress.import.scm cal-period.import.scm \
util-stdout.import.scm export-web-static.import.scm \ util-stdout.import.scm export-web-static.import.scm \
dokuwiki.import.scm dokuwiki.import.scm mailinglist.import.scm \
export-sheet.import.scm mbase-query.import.scm \
qr-payment.import.scm
HACKERBASE-OBJS=hackerbase.o testing.o listing.o cal-month.o \ HACKERBASE-OBJS=hackerbase.o testing.o listing.o cal-month.o \
cal-period.o ansi.o util-bst-ldict.o command-line.o mbase.o \ cal-period.o ansi.o util-bst-ldict.o command-line.o mbase.o \
@ -58,8 +60,9 @@ HACKERBASE-OBJS=hackerbase.o testing.o listing.o cal-month.o \
table-style.o sgr-state.o util-utf8.o sgr-cell.o \ table-style.o sgr-state.o util-utf8.o sgr-cell.o \
template-list-expander.o box-drawing.o export-web-static.o \ template-list-expander.o box-drawing.o export-web-static.o \
util-dir.o dokuwiki.o racket-kwargs.o duck.o util-bst.o \ util-dir.o dokuwiki.o racket-kwargs.o duck.o util-bst.o \
util-bst-bdict.o util-bst-ldict.o util-bst-lset.o mailman2.o \ util-bst-bdict.o util-bst-ldict.o util-bst-lset.o \
mailman-common.o mailman3.o mailman3-sql.o tiocgwinsz.o mailman-common.o mailman3.o mailman3-sql.o tiocgwinsz.o \
mailinglist.o export-sheet.o mbase-query.o qr-payment.o
GENDOC-SOURCES=gendoc.scm duck-extract.import.scm \ GENDOC-SOURCES=gendoc.scm duck-extract.import.scm \
util-time.import.scm util-csv.import.scm util-git.import.scm \ util-time.import.scm util-csv.import.scm util-git.import.scm \
@ -185,7 +188,7 @@ progress.o: progress.import.scm
progress.import.scm: $(PROGRESS-SOURCES) progress.import.scm: $(PROGRESS-SOURCES)
EXPORT-CARDS-SOURCES=export-cards.scm util-bst-ldict.import.scm \ EXPORT-CARDS-SOURCES=export-cards.scm util-bst-ldict.import.scm \
mbase.import.scm brmember.import.scm mbase.import.scm brmember.import.scm configuration.import.scm
export-cards.o: export-cards.import.scm export-cards.o: export-cards.import.scm
export-cards.import.scm: $(EXPORT-CARDS-SOURCES) export-cards.import.scm: $(EXPORT-CARDS-SOURCES)
@ -257,13 +260,6 @@ ENVIRONMENT-SOURCES=environment.scm ansi.import.scm
environment.o: environment.import.scm environment.o: environment.import.scm
environment.import.scm: $(ENVIRONMENT-SOURCES) environment.import.scm: $(ENVIRONMENT-SOURCES)
MAILMAN2-SOURCES=mailman2.scm util-bst-lset.import.scm \
util-io.import.scm mailman-common.import.scm \
configuration.import.scm
mailman2.o: mailman2.import.scm
mailman2.import.scm: $(MAILMAN2-SOURCES)
UTIL-TIME-SOURCES=util-time.scm duck.import.scm UTIL-TIME-SOURCES=util-time.scm duck.import.scm
util-time.o: util-time.import.scm util-time.o: util-time.import.scm
@ -291,7 +287,7 @@ util-io.o: util-io.import.scm
util-io.import.scm: $(UTIL-IO-SOURCES) util-io.import.scm: $(UTIL-IO-SOURCES)
UTIL-PARSER-SOURCES=util-parser.scm testing.import.scm \ UTIL-PARSER-SOURCES=util-parser.scm testing.import.scm \
duck.import.scm duck.import.scm racket-kwargs.import.scm
util-parser.o: util-parser.import.scm util-parser.o: util-parser.import.scm
util-parser.import.scm: $(UTIL-PARSER-SOURCES) util-parser.import.scm: $(UTIL-PARSER-SOURCES)
@ -332,7 +328,8 @@ NOTIFICATIONS-SOURCES=notifications.scm brmember.import.scm \
brmember-format.import.scm configuration.import.scm \ brmember-format.import.scm configuration.import.scm \
util-time.import.scm members-fees.import.scm mbase.import.scm \ util-time.import.scm members-fees.import.scm mbase.import.scm \
members-print.import.scm table.import.scm \ members-print.import.scm table.import.scm \
bank-account.import.scm logging.import.scm bank-account.import.scm logging.import.scm \
mailinglist.import.scm
notifications.o: notifications.import.scm notifications.o: notifications.import.scm
notifications.import.scm: $(NOTIFICATIONS-SOURCES) notifications.import.scm: $(NOTIFICATIONS-SOURCES)
@ -471,7 +468,8 @@ box-drawing.import.scm: $(BOX-DRAWING-SOURCES)
EXPORT-WEB-STATIC-SOURCES=export-web-static.scm brmember.import.scm \ EXPORT-WEB-STATIC-SOURCES=export-web-static.scm brmember.import.scm \
util-dir.import.scm mbase.import.scm \ util-dir.import.scm mbase.import.scm \
members-payments.import.scm cal-day.import.scm \ members-payments.import.scm cal-day.import.scm \
util-git.import.scm configuration.import.scm texts.import.scm util-git.import.scm configuration.import.scm texts.import.scm \
members-fees.import.scm qr-payment.import.scm
export-web-static.o: export-web-static.import.scm export-web-static.o: export-web-static.import.scm
export-web-static.import.scm: $(EXPORT-WEB-STATIC-SOURCES) export-web-static.import.scm: $(EXPORT-WEB-STATIC-SOURCES)
@ -522,10 +520,9 @@ UTIL-BST-LSET-SOURCES=util-bst-lset.scm util-bst.import.scm \
util-bst-lset.o: util-bst-lset.import.scm util-bst-lset.o: util-bst-lset.import.scm
util-bst-lset.import.scm: $(UTIL-BST-LSET-SOURCES) util-bst-lset.import.scm: $(UTIL-BST-LSET-SOURCES)
MAILMAN-SOURCES=mailman.scm mailman2.import.scm \ MAILMAN-SOURCES=mailman.scm mailman-common.import.scm \
mailman-common.import.scm util-bst-lset.import.scm \ util-bst-lset.import.scm configuration.import.scm \
configuration.import.scm mailman3.import.scm \ mailman3.import.scm progress.import.scm
progress.import.scm
mailman.o: mailman.import.scm mailman.o: mailman.import.scm
mailman.import.scm: $(MAILMAN-SOURCES) mailman.import.scm: $(MAILMAN-SOURCES)
@ -550,3 +547,32 @@ TIOCGWINSZ-SOURCES=tiocgwinsz.scm duck.import.scm
tiocgwinsz.o: tiocgwinsz.import.scm tiocgwinsz.o: tiocgwinsz.import.scm
tiocgwinsz.import.scm: $(TIOCGWINSZ-SOURCES) tiocgwinsz.import.scm: $(TIOCGWINSZ-SOURCES)
MAILINGLIST-SOURCES=mailinglist.scm racket-kwargs.import.scm \
mailman.import.scm mbase.import.scm util-string.import.scm \
brmember.import.scm
mailinglist.o: mailinglist.import.scm
mailinglist.import.scm: $(MAILINGLIST-SOURCES)
EXPORT-SHEET-SOURCES=export-sheet.scm mbase.import.scm \
brmember.import.scm brmember-format.import.scm \
util-bst-ldict.import.scm members-payments.import.scm \
util-format.import.scm members-fees.import.scm \
cal-period.import.scm
export-sheet.o: export-sheet.import.scm
export-sheet.import.scm: $(EXPORT-SHEET-SOURCES)
MBASE-QUERY-SOURCES=mbase-query.scm mbase.import.scm \
brmember.import.scm util-bst-ldict.scm primes.import.scm \
cal-period.import.scm cal-month.import.scm \
members-fees.import.scm members-payments.import.scm
mbase-query.o: mbase-query.import.scm
mbase-query.import.scm: $(MBASE-QUERY-SOURCES)
QR-PAYMENT-SOURCES=qr-payment.scm util-io.import.scm
qr-payment.o: qr-payment.import.scm
qr-payment.import.scm: $(QR-PAYMENT-SOURCES)

View file

@ -48,7 +48,7 @@
cal-day) cal-day)
;; Pass 2: known keys ;; Pass 2: known keys
(define mandatory-keys '(nick name mail phone)) (define mandatory-keys '(nick name mail))
(define optional-keys '(born)) (define optional-keys '(born))
(define known-multikeys (define known-multikeys
'(card desfire '(card desfire
@ -59,7 +59,10 @@
councilstart councilstop councilstart councilstop
revisionstart revisionstop revisionstart revisionstop
grantstart grantstop grantstart grantstop
joined destroyed)) joined destroyed
feestart feestop
phone
councilmlstart councilmlstop))
(define ignored-keys '(mail2)) (define ignored-keys '(mail2))
(define known-keys (append mandatory-keys optional-keys)) (define known-keys (append mandatory-keys optional-keys))
@ -83,6 +86,12 @@
(joined member start) (joined member start)
(destroyed member stop) (destroyed member stop)
(feestart fee start)
(feestop fee stop)
(councilmlstart councilml start)
(councilmlstop councilml stop)
)) ))
(define start-stop-markers (map car start-stop-markers-lookup)) (define start-stop-markers (map car start-stop-markers-lookup))
@ -109,17 +118,33 @@
(info (info
,(lambda (mr output key value) ,(lambda (mr output key value)
(case key (case key
((student suspend member revision chair council grant) ((student suspend member revision chair council grant fee councilml)
(let* ((res (period-markers->cal-periods value)) (let* ((res (period-markers->cal-periods value))
(ok? (car res)) (ok? (car res))
(periods (cadr res)) (periods0 (cadr res))
(periods
(if (eq? key 'fee)
(let ((ps
(map
(lambda (p)
(let* ((sc (cal-period-scomment p))
(scp (string-first+rest sc))
(amts (car scp))
(amt (string->number amts))
(rc (cdr scp)))
(set-cal-period-scomment
p
(list amt rc))))
periods0)))
ps)
periods0))
(msg (caddr res)) (msg (caddr res))
(line-number (cadddr res)) (line-number (cadddr res))
(mr1 (brmember-sub-set mr output key periods))) (mr1 (brmember-sub-set mr output key periods)))
(if ok? (if ok?
mr1 mr1
(brmember-add-highlight mr1 line-number msg 3 'error)))) (brmember-add-highlight mr1 line-number msg 3 'error))))
((card desfire) ((card desfire phone)
(brmember-sub-set mr output key (brmember-sub-set mr output key
(map (map
(lambda (rec) (lambda (rec)
@ -253,7 +278,7 @@
(caar passes) (caar passes)
(interpreter-pass mr pass-name (ldict-ref mr prev-name) pass-proc))))))) (interpreter-pass mr pass-name (ldict-ref mr prev-name) pass-proc)))))))
;; Loads member file source. Performs passes 0, 1 and 2. ;; Loads member file source. Performs passes 0-4
(define (load-brmember-file mr) (define (load-brmember-file mr)
(let* ((mrif (brmember-input-file mr)) (let* ((mrif (brmember-input-file mr))
(source (read-lines mrif)) (source (read-lines mrif))

View file

@ -65,6 +65,7 @@
brmember-chair? brmember-chair?
brmember-council? brmember-council?
brmember-councilml?
brmember-revision? brmember-revision?
brmember-grant? brmember-grant?
@ -85,6 +86,10 @@
brmember-mailman brmember-mailman
brmember-add-mailman brmember-add-mailman
brmember-spec-fee
brmember-age
brmember-tests! brmember-tests!
) )
@ -390,6 +395,7 @@
;; Predicates for all organizational bodies recognized ;; Predicates for all organizational bodies recognized
(define brmember-chair? (brmember-body? 'chair)) (define brmember-chair? (brmember-body? 'chair))
(define brmember-council? (brmember-body? 'council)) (define brmember-council? (brmember-body? 'council))
(define brmember-councilml? (brmember-body? 'councilml))
(define brmember-revision? (brmember-body? 'revision)) (define brmember-revision? (brmember-body? 'revision))
(define brmember-grant? (brmember-body? 'grant)) (define brmember-grant? (brmember-body? 'grant))
@ -421,7 +427,8 @@
(if (brmember-suspended? mr) (if (brmember-suspended? mr)
(let ((period (cal-periods-match (brmember-info mr 'suspend)))) (let ((period (cal-periods-match (brmember-info mr 'suspend))))
(if period (if period
(cal-month-diff (cal-period-since period) (*current-month*)) (cal-month-diff (cal-ensure-month (cal-period-since period))
(*current-month*))
0)) 0))
0)) 0))
@ -478,6 +485,29 @@
(cons ml (cons ml
(brmember-mailman mr)))) (brmember-mailman mr))))
;; Returns special fee for current month or #f if it should be default
(define (brmember-spec-fee mr)
(let ((fee-periods (brmember-info mr 'fee #f)))
(if fee-periods
(let ((fee-period (cal-month-find-period fee-periods)))
(if fee-period
(let ()
(car (cal-period-scomment fee-period)))
#f))
#f)))
(define (brmember-age mr)
(let ((born (brmember-info mr 'born #f)))
(if born
(let ((lst (string-split born "-")))
(if (null? lst)
#f
(let ((y (string->number (car lst))))
(if y
(- (current-year) y)
#f))))
#f)))
;; Self-tests ;; Self-tests
(define (brmember-tests!) (define (brmember-tests!)
(run-tests (run-tests
@ -486,8 +516,8 @@
(ldict-equal? (ldict-equal?
(make-brmember '|1234| "members/1234" '(|member|)) (make-brmember '|1234| "members/1234" '(|member|))
(make-ldict (make-ldict
`((file-name . |1234|) `((TAG . ,TAG-BRMEMBER)
(TAG . ,TAG-BRMEMBER) (file-name . |1234|)
(file-path . "members/1234") (file-path . "members/1234")
(symlinks |member|) (symlinks |member|)
(id . 1234))))) (id . 1234)))))

View file

@ -28,6 +28,7 @@
(module (module
cal-period cal-period
( (
current-year
*current-month* *current-month*
*current-day* *current-day*
@ -44,6 +45,8 @@
cal-period-scomment cal-period-scomment
cal-period-bcomment cal-period-bcomment
set-cal-period-scomment
period-markers->cal-periods period-markers->cal-periods
cal-periods-duration cal-periods-duration
@ -51,6 +54,8 @@
cal-month-in-period? cal-month-in-period?
cal-month-in-periods? cal-month-in-periods?
cal-month-find-period
cal-day-in-period? cal-day-in-period?
cal-day-in-periods? cal-day-in-periods?
@ -81,6 +86,9 @@
;; Type tag ;; Type tag
(define TAG-CAL-PERIOD (make-tag CAL-PERIOD)) (define TAG-CAL-PERIOD (make-tag CAL-PERIOD))
(define (current-year)
(cal-month-year (*current-month*)))
;; Current month - if changed, we get the actual state for given month. ;; Current month - if changed, we get the actual state for given month.
(define *current-month* (define *current-month*
(make-parameter (make-parameter
@ -142,6 +150,14 @@
(define cal-period-scomment cadddr) (define cal-period-scomment cadddr)
(define cal-period-bcomment (compose cadddr cdr)) (define cal-period-bcomment (compose cadddr cdr))
;; Direct updater
(define (set-cal-period-scomment p c)
(list TAG-CAL-PERIOD
(cal-period-since p)
(cal-period-before p)
c
(cal-period-bcomment p)))
;; Type predicate ;; Type predicate
(define (cal-period? p) (define (cal-period? p)
(and (pair? p) (and (pair? p)
@ -255,6 +271,19 @@
#t #t
(loop (cdr ps))))))) (loop (cdr ps)))))))
;; Returns true if given month is in at least one of the periods
;; given. Defaults to current month.
(define (cal-month-find-period ps . ml)
(let ((m (if (null? ml)
(*current-month*)
(car ml))))
(let loop ((ps ps))
(if (null? ps)
#f
(if (cal-month-in-period? (car ps) m)
(car ps)
(loop (cdr ps)))))))
;; Checks whether given day belongs to day or month period ;; Checks whether given day belongs to day or month period
(define (cal-day-in-period? p . dl) (define (cal-day-in-period? p . dl)
(let ((d (if (null? dl) (let ((d (if (null? dl)

View file

@ -43,6 +43,7 @@
*mailman3-sql* *mailman3-sql*
*mailman3-sql-path* *mailman3-sql-path*
*notifications-cc* *notifications-cc*
*dummy-run*
load-configuration! load-configuration!
) )
@ -100,7 +101,7 @@
;; Which version of mailman to use ;; Which version of mailman to use
(define *mailman-version* (make-parameter #f)) (define *mailman-version* (make-parameter #f))
(define =mailman-version= 2) (define =mailman-version= 3)
;; What is the mailman 3 command ;; What is the mailman 3 command
(define *mailman3-bin* (make-parameter #f)) (define *mailman3-bin* (make-parameter #f))
@ -111,7 +112,7 @@
;; A string is the default, gets converted to boolean at the end of ;; A string is the default, gets converted to boolean at the end of
;; loading configuration ;; loading configuration
(define *mailman3-sql* (make-parameter #f)) (define *mailman3-sql* (make-parameter #f))
(define =mailman3-sql= "0") (define =mailman3-sql= "1")
;; The path to SQLite3 DB file ;; The path to SQLite3 DB file
(define *mailman3-sql-path* (make-parameter #f)) (define *mailman3-sql-path* (make-parameter #f))
@ -121,6 +122,9 @@
(define *notifications-cc* (make-parameter #f)) (define *notifications-cc* (make-parameter #f))
(define =notifications-cc= "rada@brmlab.cz") (define =notifications-cc= "rada@brmlab.cz")
;; If #t, do not do anything
(define *dummy-run* (make-parameter #f))
(define (load-single-configuration! fname) (define (load-single-configuration! fname)
(when (file-exists? fname) (when (file-exists? fname)
(let loop ((lines (read-lines (open-input-file fname)))) (let loop ((lines (read-lines (open-input-file fname))))

View file

@ -54,7 +54,7 @@
(users '())) (users '()))
(if (null? lines) (if (null? lines)
users users
(let ((line (parser-preprocess-line (car lines)))) (let ((line (parser-preprocess-line (car lines) #:strip-comments? #f)))
(if (equal? line "") (if (equal? line "")
(loop (cdr lines) (loop (cdr lines)
users) users)
@ -74,8 +74,8 @@
(handle-exceptions (handle-exceptions
exn exn
(let () (let ()
(log-warning "DokuWiki: cannot open ~A" fname) (log-warning "DokuWiki (~A) exception ~A" fname (condition->list exn))
(stdout-printf "DokuWiki: cannot open ~A" fname) (stdout-printf "DokuWiki (~A) exception ~A" fname (condition->list exn))
'()) '())
(with-input-from-file fname (with-input-from-file fname
parse-dokuwiki-users-auth))) parse-dokuwiki-users-auth)))

View file

@ -135,18 +135,12 @@
res)))))) res))))))
(define (print-duck-signature sig) (define (print-duck-signature sig)
;;(print sig)
(let* ((curry-depth (get-curry-depth sig)) (let* ((curry-depth (get-curry-depth sig))
(name (get-signature-name sig)) (name (get-signature-name sig))
(nameline (format " ~A~A" (make-string curry-depth #\() name)) (nameline (format " ~A~A" (make-string curry-depth #\() name))
(spaceline (make-string (add1 (string-length nameline)) #\space)) (spaceline (make-string (add1 (string-length nameline)) #\space))
(args (gather-signature-arguments sig)) (args (gather-signature-arguments sig))
(eargs (expand-signature-arguments args))) (eargs (expand-signature-arguments args)))
;;(print " curry depth = " curry-depth)
;;(print " name = " name)
;;(print " args = " args)
;;(printf " eargs = ~S" eargs)
;;(newline)
(if (null? eargs) (if (null? eargs)
(print nameline ")") (print nameline ")")
(let loop ((args eargs) (let loop ((args eargs)

View file

@ -38,7 +38,8 @@
(chicken irregex) (chicken irregex)
util-bst-ldict util-bst-ldict
mbase mbase
brmember) brmember
configuration)
;; Prints single card type records. ;; Prints single card type records.
(define (cards-print/type mb type) (define (cards-print/type mb type)
@ -84,7 +85,8 @@
;; Exports cards and desfires to the files specified. ;; Exports cards and desfires to the files specified.
(define (cards-export mb cardsfn desfirefn) (define (cards-export mb cardsfn desfirefn)
(when (not (*dummy-run*))
(cards-export/type mb 'card cardsfn) (cards-export/type mb 'card cardsfn)
(cards-export/type mb 'desfire desfirefn)) (cards-export/type mb 'desfire desfirefn)))
) )

226
src/export-sheet.scm Normal file
View file

@ -0,0 +1,226 @@
;;
;; export-sheet.scm
;;
;; Export attendance sheet as MarkDown document.
;;
;; ISC License
;;
;; Copyright 2024 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit export-sheet))
(module
export-sheet
(
print-attendance-sheet
)
(import scheme
(chicken base)
(chicken string)
(chicken format)
(chicken sort)
srfi-1
mbase
brmember
brmember-format
util-bst-ldict
members-payments
util-format
members-fees
cal-period
cal-day)
(define (print-attendance-sheet MB number)
(print "\\documentclass{article}")
(print "\\usepackage{fancyhdr}")
(print "\\usepackage{longtable}")
(print "\\usepackage{lastpage}")
(print "\\usepackage[top=3cm,left=1cm,right=2cm,bottom=3cm]{geometry}")
(print "\\lhead{}")
(print
(format
"\\chead{Prezenční listina ~A. Valné Hromady brmlab z.s. konané ~A. ~A. ~A v sídle spolku}"
number
(cal-day-day (*current-day*))
(cal-day-month (*current-day*))
(cal-day-year (*current-day*))
))
(print "\\rhead{}")
(print "\\renewcommand{\\headrulewidth}{0pt}")
(print "\\lfoot{}")
(print "\\cfoot{Strana \\thepage{} ze \\pageref*{LastPage}}")
(print "\\rfoot{}")
(print "\\pagestyle{fancy}")
(print "\\begin{document}")
(print "\\begin{center}")
(newline)
(print "\\vskip1em")
(newline)
(define colnames
'((id) Nick "Jméno" "Příjmení" (Fee) (Bilance)
("\\raisebox{2pt}{\\begin{minipage}{15mm}\\begin{center}Aktivní\\\\Měsíce\\end{center}\\end{minipage}}")
((Hlas?)) Podpis))
(print "\\renewcommand\\arraystretch{2.1}")
(print
(format
"\\begin{longtable}{|~A|}"
(string-intersperse
(map
(lambda (x)
(if (list? x)
(if (list? (car x))
"c"
"r")
"l"))
colnames)
"|")))
(print "\\hline")
(print
(string-intersperse
(map
(lambda (x)
(format
"\\textbf{~A}"
(if (symbol? x)
(symbol->string x)
(if (string? x)
x
(if (string? (car x))
(car x)
(if (list? (car x))
(symbol->string (caar x))
(symbol->string (car x))))))))
colnames)
"&")
"\\\\")
(print "\\hline")
(print "\\endhead")
(define valid-voters 0)
(define ok-balances 0)
(define ok-actives 0)
(let loop ((mrs (sort
(find-members-by-predicate
MB (lambda (mr)
(brmember-active? mr)))
(lambda (a b)
(string<? (brmember-nick a)
(brmember-nick b))))))
(when (not (null? mrs))
(let* ((mr (car mrs))
(info (ldict-ref mr 'info))
(name (ldict-ref info 'name "ERROR"))
(name* (string-translate*
name
'(("_" . " "))))
(namel (string-split name*))
(sname (car (reverse namel)))
(fname
(string-intersperse
(reverse
(cdr
(reverse namel)))
" "))
(cal (member-calendar mr))
(rcal (reverse cal))
(rcal12
(if (> (length rcal) 12)
(take rcal 12)
rcal))
(acal12 (map cadr rcal12))
(acal12* (map (lambda (f) (if (memq 'active f) 1 0)) acal12))
(numactive (foldl + 0 acal12*))
(spec-fee (brmember-spec-fee mr))
(current-fee (if spec-fee
spec-fee
(member-calendar-entry->fee
(list (*current-month*)
(brmember-flags mr)
spec-fee))))
(balance-ok? (>= (member-total-balance mr)
(- current-fee)))
(active-ok? (>= numactive 9))
(vote-ok? (and balance-ok? active-ok?))
)
(when balance-ok?
(set! ok-balances (+ ok-balances 1)))
(when active-ok?
(set! ok-actives (+ ok-actives 1)))
(when vote-ok?
(set! valid-voters (+ valid-voters 1)))
(print
(brmember-id mr)
" & "
(string-translate*
(brmember-nick mr)
'(("_" . "\\_")))
" & \\small "
fname
" & \\small "
sname
" & "
current-fee
" & "
"\\raisebox{2pt}{\\begin{minipage}{15mm}\\begin{flushright}"
(format-amount-tex
(member-total-balance mr))
"\\\\"
(if balance-ok?
"Bez~dluhu"
"---~~~~~~")
"\\end{flushright}\\end{minipage}}"
" & "
;(if balance-ok?
; "Y"
; "--")
;" & "
"\\raisebox{2pt}{\\begin{minipage}{12mm}\\begin{center}"
numactive "/" 12
"\\\\"
(if active-ok?
"Splněno"
"\\phantom{Sp}---\\phantom{Sp}")
"\\end{center}\\end{minipage}}"
" & "
;(if active-ok?
; "Y"
; "--")
;" & "
(if vote-ok?
"Ano"
"--")
" & "
"~\\hskip28mm~"
" \\\\")
(print "\\hline")
(loop (cdr mrs)))))
(print "\\end{longtable}")
(print "\\end{center}")
(print "\\end{document}")
(print "% valid-voters = " valid-voters)
(print "% valid-balances = " ok-balances)
(print "% valid-actives = " ok-actives)
)
(define (format-amount-tex amt)
(string-translate*
(format-amount amt)
'(("--" . "--{}--"))))
)

View file

@ -44,7 +44,10 @@
cal-day cal-day
util-git util-git
configuration configuration
texts) texts
logging
qr-payment
members-fees)
;; HTML entities ;; HTML entities
(define (sanitize-html str) (define (sanitize-html str)
@ -87,6 +90,8 @@
(print "dd+dt,dd+dt+dd{border-top:1px solid #8cacbb}") (print "dd+dt,dd+dt+dd{border-top:1px solid #8cacbb}")
(print "dd{grid-column:2/3;font-weight:bold;margin:0px;padding-left:16px}") (print "dd{grid-column:2/3;font-weight:bold;margin:0px;padding-left:16px}")
(print "footer{background:#dee7ec;border-top:1px solid #8cacbb;padding:16px}") (print "footer{background:#dee7ec;border-top:1px solid #8cacbb;padding:16px}")
(print ".qr svg{width:100%;height:auto;max-width:10cm}")
(print ".qr{text-align: center}")
(print "</style>") (print "</style>")
(print "</head>") (print "</head>")
(print "<body>") (print "<body>")
@ -100,10 +105,21 @@
(brmember-nick mr) "</dd>") (brmember-nick mr) "</dd>")
(print "<dt>Member ID, Variable Symbol for Payments<br>(Členské číslo, variabilní symbol plateb)</dt><dd>" (print "<dt>Member ID, Variable Symbol for Payments<br>(Členské číslo, variabilní symbol plateb)</dt><dd>"
(brmember-id mr) "</dd>") (brmember-id mr) "</dd>")
(print "<dt>Balance in CZK<br>(Zůstatek v Kč)</dt><dd>" (caar (reverse bhs)) "</dd>") (print "<dt>Balance in CZK<br>(Zůstatek v Kč)</dt><dd>"
(print "<dt>Account for Payments<br>(Účet pro platbu příspěvků)</dt><dd>2500079551/2010</dd>") (if (null? bhs)
"0"
(caar (reverse bhs)))
"</dd>")
(print "<dt>Account for Payments<br>(Účet pro platbu příspěvků)</dt><dd>CZK: 2500079551/2010<br>EUR: CZ93 2010 0000 0021 0007 9552</dd>")
(print "</dl>") (print "</dl>")
(print "</div>") (print "</div>")
(print "<div class=\"bi qr\">")
(let ((fee (member-calendar-entry->fee
(make-member-calendar-entry mr))))
(print "<h2>Payment of membership fee " fee " CZK<br/>(Platba členského příspěvku)</h2>")
(print (make-brmlab-qrp-svg-string
fee "CZK" (brmember-id mr))))
(print "</div>")
(print "<div class=\"bi\">") (print "<div class=\"bi\">")
(print "<h2>Payments History</h2>") (print "<h2>Payments History</h2>")
(print "<table>") (print "<table>")
@ -175,10 +191,11 @@
;; Generates all members in given directory ;; Generates all members in given directory
(define (gen-html-members mb dir) (define (gen-html-members mb dir)
(when (not (*dummy-run*))
(ensure-directory dir) (ensure-directory dir)
(with-mbase-progress% (with-mbase-progress%
mb dir mr mb dir mr
(gen-html-member mr dir)) (gen-html-member mr dir))
(clean-members-files mb dir)) (clean-members-files mb dir)))
) )

View file

@ -50,7 +50,10 @@
export-web-static export-web-static
dokuwiki dokuwiki
racket-kwargs racket-kwargs
util-string) util-string
mailinglist
export-sheet
mbase-query)
;; Command-line options and configurable parameters ;; Command-line options and configurable parameters
(define -needs-bank- (make-parameter #f)) (define -needs-bank- (make-parameter #f))
@ -65,6 +68,7 @@
(define -show-only-active- (make-parameter #f)) (define -show-only-active- (make-parameter #f))
(define -notify-months- (make-parameter 1)) (define -notify-months- (make-parameter 1))
(define -send-emails- (make-parameter #f)) (define -send-emails- (make-parameter #f))
(define -number- (make-parameter #f))
;; Arguments parsing ;; Arguments parsing
(command-line (command-line
@ -112,6 +116,8 @@
(-mailman3-sql-path (path) "Set mailman3 direct SQL access path" (-mailman3-sql-path (path) "Set mailman3 direct SQL access path"
(*mailman3-sql* "1") (*mailman3-sql* "1")
(*mailman3-sql-path* path)) (*mailman3-sql-path* path))
(-n () "Do not do anything"
(*dummy-run* #t))
"" ""
"Email options:" "Email options:"
(-from (email) "Sender email address" (-from (email) "Sender email address"
@ -180,7 +186,14 @@
(-action- 'genweb)) (-action- 'genweb))
(-stats (file:gnuplot-data) "Get stats for all months" (-stats (file:gnuplot-data) "Get stats for all months"
(-action- 'print-stats) (-action- 'print-stats)
(-needs-bank- #t)
(-fname- file:gnuplot-data)) (-fname- file:gnuplot-data))
(-sheet (filename gmnum) "Generate attendance sheet for given GM number"
(-needs-bank- #t)
(-fname- filename)
(-number- gmnum)
(-action- 'gen-sheet))
"" ""
"Mailman Actions:" "Mailman Actions:"
(-mlsync () "Synchronize internal ML" (-mlsync () "Synchronize internal ML"
@ -286,31 +299,6 @@
(print " " (car keys) ": " (length (ldict-ref status (car keys))))) (print " " (car keys) ": " (length (ldict-ref status (car keys)))))
(loop (cdr keys))))))) (loop (cdr keys)))))))
(define* (check-mailing-list mls name #:pred? (pred? #f))
(define ml (find-mailman-list mls name))
(let-values (((missing surplus)
(mailman-compare-members ml
(mbase-active-emails MB
#:suspended #t
#:pred? pred?
))))
(if (null? (cdr ml))
(print "Skipping ML check - not loaded")
(if (and (null? missing)
(null? surplus))
(print (format "~a mailing list membership in sync." (string-capitalize name)))
(let ()
(print (format "~a mailing list:" (string-capitalize name)))
(when (not (null? missing))
(print " Missing: " missing))
(when (not (null? surplus))
(print " Outsiders: " surplus)))))))
(define (rada-ml-pred? mr)
(or (brmember-council? mr)
(brmember-chair? mr)
(brmember-revision? mr)))
;; Perform requested action ;; Perform requested action
(case (-action-) (case (-action-)
((print-info) ((print-info)
@ -322,10 +310,7 @@
(let () (let ()
(print-members-base-table MB) (print-members-base-table MB)
(newline) (newline)
(check-mailing-list MLS "internal") (print-mailing-list-checks MB MLS)
(check-mailing-list MLS "rada"
#:pred? rada-ml-pred?)
(check-mailing-list MLS "rk" #:pred? brmember-revision?)
(print-git-status))) (print-git-status)))
(newline)) (newline))
((print-stats) ((print-stats)
@ -381,6 +366,10 @@
((genweb) ((genweb)
(log-info "Generating static web files") (log-info "Generating static web files")
(gen-html-members MB (-web-dir-))) (gen-html-members MB (-web-dir-)))
((gen-sheet)
(log-info "Generating attendance sheet")
(parameterize ((current-output-port (open-output-file (-fname-))))
(print-attendance-sheet MB (-number-))))
((edit) ((edit)
(if mr (if mr
(let () (let ()
@ -405,7 +394,7 @@
(print "Mailman synchronization disabled with manually specified current month.")))) (print "Mailman synchronization disabled with manually specified current month."))))
((notify) ((notify)
(let ((nmembers (members-to-notify MB (-notify-months-)))) (let ((nmembers (members-to-notify MB (-notify-months-))))
(newline) (stdout-newline)
(if (null? nmembers) (if (null? nmembers)
(print "Everyone paid on time.") (print "Everyone paid on time.")
(let () (let ()
@ -423,8 +412,8 @@
(print-git-status)) (print-git-status))
((summary) ((summary)
(if (-send-emails-) (if (-send-emails-)
(make+send-summary-email MB) (make+send-summary-email MB MLS)
(make+print-summary-email MB))) (make+print-summary-email MB MLS)))
((list) ((list)
(for-each (lambda (mr) (for-each (lambda (mr)
(print (brmember-nick mr))) (print (brmember-nick mr)))

79
src/mailinglist.scm Normal file
View file

@ -0,0 +1,79 @@
;;
;; mailinglist.scm
;;
;; Common high-level mailinglist management procedures.
;;
;; ISC License
;;
;; Copyright 2023 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit mailinglist))
(module
mailinglist
(
check-mailing-list
print-mailing-list-checks
rada-ml-pred?
)
(import scheme
(chicken base)
(chicken format)
racket-kwargs
mailman
mbase
util-string
brmember)
(define* (check-mailing-list MB mls name #:pred? (pred? #f) #:suspended (suspended #f))
(define ml (find-mailman-list mls name))
(when ml
(let-values (((missing surplus)
(mailman-compare-members ml
(mbase-active-emails MB
#:suspended suspended
#:pred? pred?
))))
(if (null? (cdr ml))
(print "Skipping ML check - not loaded")
(if (and (null? missing)
(null? surplus))
(print (format "~a mailing list membership in sync." (string-capitalize name)))
(let ()
(print (format "~a mailing list:" (string-capitalize name)))
(when (not (null? missing))
(print " Missing: " missing))
(when (not (null? surplus))
(print " Outsiders: " surplus))))))))
(define (print-mailing-list-checks MB MLS)
(check-mailing-list MB MLS "internal" #:suspended #t)
(check-mailing-list MB MLS "rada"
#:pred? rada-ml-pred?)
(check-mailing-list MB MLS "rk" #:pred? brmember-revision?))
(define (rada-ml-pred? mr)
(or (brmember-council? mr)
(brmember-chair? mr)
(brmember-revision? mr)
(brmember-councilml? mr)))
)

View file

@ -49,7 +49,6 @@
(import scheme (import scheme
(chicken base) (chicken base)
(chicken module) (chicken module)
mailman2
mailman-common mailman-common
util-bst-lset util-bst-lset
configuration configuration
@ -59,24 +58,17 @@
;; Syntax for simplifying export of case-version procedures ;; Syntax for simplifying export of case-version procedures
(define-syntax define-mailman-proc (define-syntax define-mailman-proc
(syntax-rules () (syntax-rules ()
((_ name proc2) ((_ name proc3)
(begin (begin
(export name) (export name)
(define (name . args) (define (name . args)
(case (*mailman-version*) (case (*mailman-version*)
((2) (apply proc2 args))))))
((_ name proc2 proc3)
(begin
(export name)
(define (name . args)
(case (*mailman-version*)
((2) (apply proc2 args))
((3) (apply proc3 args)))))))) ((3) (apply proc3 args))))))))
(define-mailman-proc list-mailman-lists (define-mailman-proc list-mailman-lists
list-mailman2-lists list-mailman3-lists) list-mailman3-lists)
(define-mailman-proc list-mailman-list-members (define-mailman-proc list-mailman-list-members
list-mailman2-list-members list-mailman3-list-members) list-mailman3-list-members)
;; Loads a single mailman list as mailman structure, if ;; Loads a single mailman list as mailman structure, if
;; unsuccessfull, returns only a list with ML name and no member ;; unsuccessfull, returns only a list with ML name and no member
@ -112,9 +104,9 @@
(assoc name lsts)) (assoc name lsts))
(define-mailman-proc add-email-to-mailman-list (define-mailman-proc add-email-to-mailman-list
add-email-to-mailman2-list add-email-to-mailman3-list) add-email-to-mailman3-list)
(define-mailman-proc remove-email-from-mailman-list (define-mailman-proc remove-email-from-mailman-list
remove-email-from-mailman2-list remove-email-from-mailman3-list) remove-email-from-mailman3-list)
;; Ensures given email is in given ML ;; Ensures given email is in given ML
(define (mailman-ensure-member ml email) (define (mailman-ensure-member ml email)

View file

@ -1,104 +0,0 @@
;;
;; mailman2.scm
;;
;; Mailman management interface - Mailman version 2.x support
;;
;; ISC License
;;
;; Copyright 2023 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit mailman2))
(module
mailman2
(
list-mailman2-lists
list-mailman2-list-members
add-email-to-mailman2-list
remove-email-from-mailman2-list
)
(import scheme
(chicken base)
(chicken pathname)
(chicken string)
(chicken sort)
(chicken format)
srfi-1
util-bst-lset
util-io
mailman-common
configuration)
;; Returns full path to given mailman binary
(define (mailman-bin bin)
(make-pathname (*mailman2-bin*) bin))
;; Mailman-specific process output lines capture
(define (get-mailman-output-lines bin . args)
(apply
get-process-output-lines
(mailman-bin bin)
args))
;; Sends all lines to the process
(define (mailman-send/recv bin args . lines)
(apply
process-send/recv
(mailman-bin bin)
args
lines))
;; Returns the list of available lists
(define (list-mailman2-lists)
(get-mailman-output-lines "list_lists" "-b"))
;; Returns the list of members of given list
(define (list-mailman2-list-members lst)
(sort
(get-mailman-output-lines "list_members" lst)
string-ci<?))
;; Adds given email to given listname
(define (add-email-to-mailman2-list listname email)
(print "Add " email " to " listname ".")
(let ((result
(mailman-send/recv
"add_members"
(list "-r" "-" listname)
email)))
(let loop ((lines result))
(when (not (null? lines))
(print " | " (car lines))
(loop (cdr lines))))))
;; Removes given email from given listname
(define (remove-email-from-mailman2-list listname email)
(print "Remove " email " from " listname ".")
(let ((result
(get-mailman-output-lines
"remove_members" listname
(sprintf "~A" email))))
(let loop ((lines result))
(when (not (null? lines))
(print " | " (car lines))
(loop (cdr lines))))))
)

View file

@ -37,7 +37,8 @@
(chicken base) (chicken base)
(chicken format) (chicken format)
sqlite3 sqlite3
configuration) configuration
(chicken condition))
;; Thread-local parameter to re-use SQLite3 DB handle for subsequent queries ;; Thread-local parameter to re-use SQLite3 DB handle for subsequent queries
(define *cached-mailman3-db* (make-parameter #f)) (define *cached-mailman3-db* (make-parameter #f))
@ -55,10 +56,15 @@
;; Returns the list of mailman3 mailinglists by querying te ;; Returns the list of mailman3 mailinglists by querying te
;; underlying SQLite3 DB directly ;; underlying SQLite3 DB directly
(define (list-mailman3-sql-lists) (define (list-mailman3-sql-lists)
(handle-exceptions
ex
'()
(let ((result
(let-values (((stmt _) (let-values (((stmt _)
(prepare (mailman3-db) (prepare (mailman3-db)
"SELECT list_name FROM mailinglist"))) "SELECT list_name FROM mailinglist")))
(map-row identity stmt))) (map-row identity stmt))))
result)))
;; Returns a list of email addresses subscribed to given mailinglist ;; Returns a list of email addresses subscribed to given mailinglist
(define (list-mailman3-sql-list-members lst) (define (list-mailman3-sql-list-members lst)

View file

@ -94,7 +94,8 @@
;; Adds given email ;; Adds given email
(define (add-email-to-mailman3-list lst email) (define (add-email-to-mailman3-list lst email)
(print "Add " email " to " lst ".") (print "Add " email " to " lst "." (if (*dummy-run*) " [no-op]" ""))
(when (not (*dummy-run*))
(let ((result (let ((result
(mailman3-send/recv (mailman3-send/recv
(list "addmembers" "-" (format "~A@brmlab.cz" lst)) (list "addmembers" "-" (format "~A@brmlab.cz" lst))
@ -102,11 +103,12 @@
(let loop ((lines result)) (let loop ((lines result))
(when (not (null? lines)) (when (not (null? lines))
(print " | " (car lines)) (print " | " (car lines))
(loop (cdr lines)))))) (loop (cdr lines)))))))
;; Removes given email from given listname ;; Removes given email from given listname
(define (remove-email-from-mailman3-list lst email) (define (remove-email-from-mailman3-list lst email)
(print "Remove " email " from " lst ".") (print "Remove " email " from " lst "." (if (*dummy-run*) " [no-op]" ""))
(when (not (*dummy-run*))
(let ((result (let ((result
(get-mailman3-output-lines (get-mailman3-output-lines
"delmembers" "delmembers"
@ -115,6 +117,6 @@
(let loop ((lines result)) (let loop ((lines result))
(when (not (null? lines)) (when (not (null? lines))
(print " | " (car lines)) (print " | " (car lines))
(loop (cdr lines)))))) (loop (cdr lines)))))))
) )

123
src/mbase-query.scm Normal file
View file

@ -0,0 +1,123 @@
;;
;; mbase-query.scm
;;
;; Queries of various mbase derived attributes.
;;
;; ISC License
;;
;; Copyright 2023-2025 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit mbase-query))
(module
mbase-query
(
mbase-info
mbase-stats
)
(import scheme
(chicken base)
srfi-1
mbase
brmember
util-bst-ldict
primes
cal-period
cal-month
members-fees
members-payments)
(define (members-base-oldest-month mb)
(make-cal-month 2015 1))
(define (members-average-age mrs)
(let* ((ages (map brmember-age mrs))
(valid (filter (lambda (x) x) ages))
(num (length valid))
(sum (foldl + 0 valid)))
(exact->inexact (/ sum num))))
;; Returns dictionary with statistics about the members base.
(define (mbase-info mb-arg)
(let* ((members (find-members-by-predicate mb-arg brmember-usable?))
(di0 (make-ldict))
(di1 (ldict-set di0 'invalid
(filter (compose not is-4digit-prime? brmember-id) members)))
(active-members (filter brmember-active? members))
(di2 (ldict-set di1 'active
active-members))
(di3 (ldict-set di2 'suspended
(filter brmember-suspended? members)))
(di4 (ldict-set di3 'students
(filter brmember-student? members)))
(di5 (ldict-set di4 'destroyed
(filter brmember-destroyed? members)))
(di6 (ldict-set di5 'month (*current-month*)))
(di7 (ldict-set di6 'total members))
(di8 (ldict-set di7 'problems
(find-members-by-predicate mb-arg brmember-has-problems?)))
(di9 (ldict-set di8 'expected
(get-expected-income mb-arg)))
(mbals (map member-total-balance active-members))
(di10 (ldict-set di9 'balance
(foldl + 0 mbals)))
(di11 (ldict-set di10 'advance
(foldl + 0
(map (lambda (v)
(max 0 v))
mbals))))
(di12 (ldict-set di11 'debt
(foldl + 0
(map (lambda (v)
(min 0 v))
mbals))))
(di13 (ldict-set di12 'age
(members-average-age active-members)))
)
di13))
;; Returns a list two lists: keys, data.
;; Each data record contains values for all keys.
(define (mbase-stats mb)
(let ((keys
'(month
total active suspended students destroyed invalid
expected balance advance debt
age
)))
(let mloop ((data '())
(month (members-base-oldest-month mb)))
(if (cal-month<=? month (*current-month*))
(let ((bi (with-current-month month
(mbase-info mb))))
(let kloop ((row (list (ldict-ref bi 'month)))
(keys (cdr keys)))
(if (null? keys)
(mloop (cons (reverse row) data)
(cal-month-add month 1))
(kloop (cons (let ((val (ldict-ref bi (car keys))))
(if (list? val)
(length val)
val))
row)
(cdr keys)))))
(list keys (reverse data))))))
)

View file

@ -50,8 +50,6 @@
mbase-update-by-id mbase-update-by-id
mbase-update mbase-update
mbase-stats
mbase-add-unpaired mbase-add-unpaired
mbase-unpaired mbase-unpaired
@ -207,47 +205,6 @@
(proc mr) (proc mr)
mr))))) mr)))))
;; Returns dictionary with statistics about the members base.
(define (mbase-info mb-arg)
(let* ((members (find-members-by-predicate mb-arg brmember-usable?))
(di0 (make-ldict))
(di1 (ldict-set di0 'invalid
(filter (compose not is-4digit-prime? brmember-id) members)))
(di2 (ldict-set di1 'active
(filter brmember-active? members)))
(di3 (ldict-set di2 'suspended
(filter brmember-suspended? members)))
(di4 (ldict-set di3 'students
(filter brmember-student? members)))
(di5 (ldict-set di4 'destroyed
(filter brmember-destroyed? members)))
(di6 (ldict-set di5 'month (*current-month*)))
(di7 (ldict-set di6 'total members))
(di8 (ldict-set di7 'problems
(find-members-by-predicate mb-arg brmember-has-problems?))))
di8))
(define (members-base-oldest-month mb)
(make-cal-month 2015 1))
;; Returns a list two lists: keys, data.
;; Each data record contains values for all keys.
(define (mbase-stats mb)
(let ((keys '(month total active suspended students destroyed invalid)))
(let mloop ((data '())
(month (members-base-oldest-month mb)))
(if (cal-month<=? month (*current-month*))
(let ((bi (with-current-month month
(mbase-info mb))))
(let kloop ((row (list (ldict-ref bi 'month)))
(keys (cdr keys)))
(if (null? keys)
(mloop (cons (reverse row) data)
(cal-month-add month 1))
(kloop (cons (length (ldict-ref bi (car keys))) row)
(cdr keys)))))
(list keys (reverse data))))))
;; Adds unpaired transaction to given members-base ;; Adds unpaired transaction to given members-base
(define (mbase-add-unpaired mb tr) (define (mbase-add-unpaired mb tr)
(ldict-set mb 'unpaired (ldict-set mb 'unpaired

View file

@ -30,6 +30,7 @@
( (
lookup-member-fee lookup-member-fee
member-calendar member-calendar
make-member-calendar-entry
member-calendar-first-month member-calendar-first-month
member-calendar-last-month member-calendar-last-month
member-calendar-query member-calendar-query
@ -40,12 +41,15 @@
member-calendar->table member-calendar->table
members-summary members-summary
member-calendar-entry->fee member-calendar-entry->fee
get-expected-income
get-expected-income-string
) )
(import scheme (import scheme
(chicken base) (chicken base)
(chicken format) (chicken format)
(chicken sort) (chicken sort)
(chicken string)
srfi-1 srfi-1
configuration configuration
brmember brmember
@ -82,12 +86,17 @@
(if (cal-month>? cm last-month) (if (cal-month>? cm last-month)
(reverse cal) (reverse cal)
(loop (cal-month-add cm) (loop (cal-month-add cm)
(cons (list cm (cons (with-current-month
(with-current-month
cm cm
(brmember-flags mr))) (make-member-calendar-entry mr))
cal)))))) cal))))))
;; Assumes current-month is specified correctly
(define (make-member-calendar-entry mr)
(list (*current-month*)
(brmember-flags mr)
(brmember-spec-fee mr)))
;; Returns the first month of the calendar ;; Returns the first month of the calendar
(define (member-calendar-first-month mc) (define (member-calendar-first-month mc)
(caar mc)) (caar mc))
@ -111,12 +120,16 @@
(ansi-string #:bgblack "\xc2\xa0\xc2\xa0") ; Destroyed (ansi-string #:bgblack "\xc2\xa0\xc2\xa0") ; Destroyed
(if (member 'student (cadr e)) (if (member 'student (cadr e))
(ansi-string #:bgyellow "\xc2\xa0\xc2\xa0") ; Student (ansi-string #:bgyellow "\xc2\xa0\xc2\xa0") ; Student
(ansi-string #:bggreen "\xc2\xa0\xc2\xa0")))) ; Normal (if (caddr e)
(ansi-string #:bgblue (format "~a" (caddr e)))
(ansi-string #:bggreen "\xc2\xa0\xc2\xa0"))))) ; Normal
"\xc2\xa0\xc2\xa0") ; Nonexistent - should not happen "\xc2\xa0\xc2\xa0") ; Nonexistent - should not happen
"\xc2\xa0\xc2\xa0")) ; Nonexistent "\xc2\xa0\xc2\xa0")) ; Nonexistent
;; Converts the entry into the fee ;; Converts the entry into the fee
(define (member-calendar-entry->fee e) (define (member-calendar-entry->fee e)
(with-current-month
(car e)
(if e (if e
(if (member 'existing (cadr e)) (if (member 'existing (cadr e))
(if (member 'suspended (cadr e)) (if (member 'suspended (cadr e))
@ -125,9 +138,11 @@
0 ; Destroyed 0 ; Destroyed
(if (member 'student (cadr e)) (if (member 'student (cadr e))
(lookup-member-fee 'student) ; Student (lookup-member-fee 'student) ; Student
(lookup-member-fee 'regular)))) ; Normal (if (caddr e)
(caddr e)
(lookup-member-fee 'regular))))) ; Normal
0) ; Nonexistent - should not happen 0) ; Nonexistent - should not happen
0)) ; Nonexistent 0))) ; Nonexistent
;; Converts the calendar into a table where rows represent years and ;; Converts the calendar into a table where rows represent years and
;; contain the year in the first cell and 12 cells for months after ;; contain the year in the first cell and 12 cells for months after
@ -194,4 +209,38 @@
(cons 0 0) (cons 0 0)
members))) members)))
(define (get-expected-income mb)
(let* ((flst
(map (compose member-calendar-entry->fee make-member-calendar-entry)
(find-members-by-predicate mb brmember-active?)))
(amts (sort (delete-duplicates flst) <))
(sums
(map
(lambda (amt)
(cons amt
(length (filter (lambda (v) (= v amt)) flst))))
amts)))
(foldl + 0 (map (lambda (p) (* (car p) (cdr p))) sums))))
(define (get-expected-income-string mb)
(let* ((flst
(map (compose member-calendar-entry->fee make-member-calendar-entry)
(find-members-by-predicate mb brmember-active?)))
(amts (sort (delete-duplicates flst) <))
(sums
(map
(lambda (amt)
(cons amt
(length (filter (lambda (v) (= v amt)) flst))))
amts)))
(string-append
"Expected income: "
(string-intersperse (map
(lambda (p)
(format "~A*~A" (cdr p) (car p)))
sums)
" + ")
" = "
(number->string (foldl + 0 (map (lambda (p) (* (car p) (cdr p))) sums))))))
) )

View file

@ -90,12 +90,21 @@
(substring msg 0 ci2) (substring msg 0 ci2)
msg)) msg))
(ci3 (substring-index "NULL" msg)) (ci3 (substring-index "NULL" msg))
(vs3 (if ci3 (vs3 (if (and ci3
(>= (string-length msg) (+ ci3 8)))
(substring msg (+ ci3 4) (+ ci3 4 4)) (substring msg (+ ci3 4) (+ ci3 4 4))
msg))) msg))
(ci4 (substring-index "VS" msg))
(vs4 (if (and ci4
(>= (string-length msg) (+ ci4 6)))
(substring msg (+ ci4 2) (+ ci4 6))
msg))
)
(or (string->number vs1) (or (string->number vs1)
(string->number vs2) (string->number vs2)
(string->number vs3)))))) (string->number vs3)
(string->number vs4)
)))))
varsym-id))) varsym-id)))
;; Special comparator (originally with JendaSAP hack) ;; Special comparator (originally with JendaSAP hack)

View file

@ -97,7 +97,7 @@
(body (map (lambda (k) (body (map (lambda (k)
(let ((v (ldict-ref info k))) (let ((v (ldict-ref info k)))
(case k (case k
((card desfire) ((card desfire phone)
(list k (list k
(table->string (table->string
(map (map
@ -114,7 +114,7 @@
(caddr c))) (caddr c)))
(brmember-credit mr)) (brmember-credit mr))
#:border '(((#:right light) ... none) ...)))) #:border '(((#:right light) ... none) ...))))
((suspend student member council chair revision grant) ((suspend student member council chair revision grant councilml)
(let* ((pdata (cons (list "Since" "Until") (let* ((pdata (cons (list "Since" "Until")
(map (map
(lambda (p) (lambda (p)
@ -129,8 +129,25 @@
(ptbl (table->string (ptbl (table->string
pdata pdata
#:border '(((#:right light) ... none) ...)))) #:border '(((#:right light) ... none) ...))))
;;(print pdata) (list k ptbl)))
;;(write ptbl)(newline) ((fee)
(let* ((pdata
(cons
(list "Amount" "Since" "Until")
(map
(lambda (p)
(list
(format "\t~A" (car (cal-period-scomment p)))
(string-append (cal-day/month->string
(cal-period-since p)) " "
(or (cadr (cal-period-scomment p)) ""))
(string-append (cal-day/month->string
(cal-period-before p)) " "
(or (cal-period-bcomment p) ""))))
v)))
(ptbl (table->string
pdata
#:border '(((#:right light) ... none) ...))))
(list k ptbl))) (list k ptbl)))
(else (else
(if v (if v
@ -161,7 +178,6 @@
(list (list (ansi-string #:red "DokuWiki") (list (list (ansi-string #:red "DokuWiki")
(ansi-string #:red "---"))))) (ansi-string #:red "---")))))
(result (filter identity (append head body mailman dokuwiki)))) (result (filter identity (append head body mailman dokuwiki))))
;;(write result)(newline)
(table->string result (table->string result
#:border '(((#:bottom #:right light) ... (#:bottom light)) #:border '(((#:bottom #:right light) ... (#:bottom light))
... ...
@ -364,7 +380,7 @@
(members-table-row (ansi #:magenta #:bold) "Expire Soon:" (members-table-row (ansi #:magenta #:bold) "Expire Soon:"
soon-expire-mrs "~N (~S)")) soon-expire-mrs "~N (~S)"))
(members-pred-table-row mb (members-pred-table-row mb
(ansi-string #:red #:bold "Prolems:") (ansi-string #:red #:bold "Problems:")
brmember-has-problems? brmember-has-problems?
"~N~E ~A") "~N~E ~A")
(if (null? debtor-mrs) (if (null? debtor-mrs)
@ -479,7 +495,17 @@
(null? (cdr dsa))) (null? (cdr dsa)))
#f #f
(cadr dsa)))) (cadr dsa))))
(let* ((members ;; Pass 1 (let* ((raw-members
(sort
(if destroyed?
(find-members-by-predicate MB (lambda x #t))
(if only-active?
(find-members-by-predicate MB (lambda (mr)
(brmember-active? mr)))
(find-members-by-predicate MB (lambda (mr)
(not (brmember-destroyed? mr))))))
brmember<?))
(members ;; Pass 1
(map (map
(lambda (mr) (lambda (mr)
(let* ((balance (member-balance mr)) (let* ((balance (member-balance mr))
@ -500,16 +526,15 @@
payment payment
total total
balance balance
(let ((spec-fee (brmember-spec-fee mr)))
(if spec-fee
spec-fee
(member-calendar-entry->fee
(list (*current-month*)
(brmember-flags mr)
spec-fee))))
))) )))
(sort raw-members))
(if destroyed?
(find-members-by-predicate MB (lambda x #t))
(if only-active?
(find-members-by-predicate MB (lambda (mr)
(brmember-active? mr)))
(find-members-by-predicate MB (lambda (mr)
(not (brmember-destroyed? mr))))))
brmember<?)))
(balances (map (lambda (m) (balances (map (lambda (m)
(list-ref m 6)) (list-ref m 6))
members))) members)))
@ -518,6 +543,7 @@
(cons (cons
(list (ansi-string #:bgblue #:brightyellow #:bold "Member") (list (ansi-string #:bgblue #:brightyellow #:bold "Member")
(ansi-string #:bgblue #:brightyellow #:bold "Status") (ansi-string #:bgblue #:brightyellow #:bold "Status")
(ansi-string #:bgblue #:brightyellow #:bold "Current")
(ansi-string #:bgblue #:brightyellow #:bold "Fees") (ansi-string #:bgblue #:brightyellow #:bold "Fees")
(ansi-string #:bgblue #:brightyellow #:bold "Credit") (ansi-string #:bgblue #:brightyellow #:bold "Credit")
(ansi-string #:bgblue #:brightyellow #:bold "Payments") (ansi-string #:bgblue #:brightyellow #:bold "Payments")
@ -528,6 +554,7 @@
(let ((total (list-ref member 5))) (let ((total (list-ref member 5)))
(list (list-ref member 0) (list (list-ref member 0)
(list-ref member 1) (list-ref member 1)
(sprintf "\t~A" (list-ref member 7))
(sprintf "\t~A" (list-ref member 2)) (sprintf "\t~A" (list-ref member 2))
(sprintf "\t~A" (list-ref member 3)) (sprintf "\t~A" (list-ref member 3))
(sprintf "\t~A" (list-ref member 4)) (sprintf "\t~A" (list-ref member 4))
@ -544,9 +571,11 @@
(let* ((fees (foldl + 0 (map (lambda (b) (ldict-ref b 'fees)) balances))) (let* ((fees (foldl + 0 (map (lambda (b) (ldict-ref b 'fees)) balances)))
(credit (foldl + 0 (map (lambda (b) (ldict-ref b 'credit)) balances))) (credit (foldl + 0 (map (lambda (b) (ldict-ref b 'credit)) balances)))
(payment (foldl + 0 (map (lambda (b) (ldict-ref b 'payment)) balances))) (payment (foldl + 0 (map (lambda (b) (ldict-ref b 'payment)) balances)))
(total (- (+ credit payment) fees))) (total (- (+ credit payment) fees))
(current-total (foldl + 0 (map (lambda (m) (list-ref m 7)) members))))
(list (list (ansi-string #:bold "Total") (list (list (ansi-string #:bold "Total")
"" ""
(ansi-string "\t" #:bold (sprintf "~A" current-total))
(ansi-string "\t" #:bold (sprintf "~A" fees)) (ansi-string "\t" #:bold (sprintf "~A" fees))
(ansi-string "\t" #:bold (sprintf "~A" credit)) (ansi-string "\t" #:bold (sprintf "~A" credit))
(ansi-string "\t" #:bold (sprintf "~A" payment)) (ansi-string "\t" #:bold (sprintf "~A" payment))
@ -577,19 +606,7 @@
(map (lambda (member) (map (lambda (member)
(min 0 (list-ref member 5))) (min 0 (list-ref member 5)))
members))) members)))
(let* ((ns (foldl (lambda (acc member) (print (get-expected-income-string MB)))))
(cons (+ (car acc) (if (eq? 'student (cadr member)) 1 0))
(+ (cdr acc) (if (eq? 'active (cadr member)) 1 0))))
(cons 0 0)
members))
(students (car ns))
(full (cdr ns)))
(print "Expected income: "
(+ (* (lookup-member-fee 'normal) full)
(* (lookup-member-fee 'student) students))
" (" full " full members + " students " students)"))
)
))
(define (unpaired-table mb . args) (define (unpaired-table mb . args)
(apply (apply

View file

@ -40,6 +40,7 @@
(chicken format) (chicken format)
(chicken string) (chicken string)
(chicken sort) (chicken sort)
(chicken port)
brmember brmember
util-mail util-mail
util-bst-ldict util-bst-ldict
@ -54,7 +55,8 @@
table table
bank-account bank-account
logging logging
srfi-1) srfi-1
mailinglist)
;; Prints email to the console ;; Prints email to the console
(define (print-notification-email em) (define (print-notification-email em)
@ -142,16 +144,14 @@
(send-notification-email em))) (send-notification-email em)))
;; Summary email of membership fees payments ;; Summary email of membership fees payments
(define (summary-email-body mb) (define (summary-email-body mb mls)
(let* ((mbs (members-summary mb)) (let* ((mbs (members-summary mb))
(students (car mbs)) (students (car mbs))
(full (cdr mbs)) (full (cdr mbs))
(income (+ (* (lookup-member-fee 'normal) full) (income (+ (* (lookup-member-fee 'normal) full)
(* (lookup-member-fee 'student) students))) (* (lookup-member-fee 'student) students)))
(income-lst (income-lst
(list (format "Expected income: ~A CZK" income) (list (get-expected-income-string mb)))
(format " ~A full members" full)
(format " ~A students" students)))
(unpaired (mbase-unpaired mb)) (unpaired (mbase-unpaired mb))
(unpaired-lst (unpaired-lst
(if (null? unpaired) (if (null? unpaired)
@ -160,6 +160,30 @@
(list "" (list ""
"Unpaired transactions:") "Unpaired transactions:")
(unpaired-table mb #:border-style 'ascii)))) (unpaired-table mb #:border-style 'ascii))))
(soonexps (sort
(find-members-by-predicate
mb
(brmember-suspended-for 21 24))
brmember<?))
(soonexps-lst
(if (null? soonexps)
'()
(list ""
(format "Expiring members (~A): ~A"
(length soonexps)
(string-intersperse
(map
(lambda (mr)
(brmember-format "~N (~S)" mr))
soonexps)
",")))))
(mlcheck-lst
(cons ""
(string-split
(with-output-to-string
(lambda ()
(print-mailing-list-checks mb mls)))
"\n")))
(debtors (sort (debtors (sort
(members-to-notify mb 1) (members-to-notify mb 1)
brmember<?)) brmember<?))
@ -256,6 +280,8 @@
", ")))))) ", "))))))
(append income-lst (append income-lst
unpaired-lst unpaired-lst
soonexps-lst
mlcheck-lst
debtors-lst debtors-lst
boring-lst boring-lst
dw-lst dw-lst
@ -266,21 +292,21 @@
)))) ))))
;; Creates the summary email structure ;; Creates the summary email structure
(define (make-summary-email mb) (define (make-summary-email mb mls)
(make-ldict (make-ldict
`((to . ,(*summary-mailto*)) `((to . ,(*summary-mailto*))
(subject . ,(format "Členské příspěvky ~A" (subject . ,(format "Členské příspěvky ~A"
(today/iso))) (today/iso)))
(body . ,(summary-email-body mb))))) (body . ,(summary-email-body mb mls)))))
;; Just print to standard output ;; Just print to standard output
(define (make+print-summary-email mb) (define (make+print-summary-email mb mls)
(let ((em (make-summary-email mb))) (let ((em (make-summary-email mb mls)))
(print-notification-email em))) (print-notification-email em)))
;; Actually send emails ;; Actually send emails
(define (make+send-summary-email mr) (define (make+send-summary-email mr mls)
(let ((em (make-summary-email mr))) (let ((em (make-summary-email mr mls)))
(send-notification-email em))) (send-notification-email em)))
) )

104
src/qr-payment.scm Normal file
View file

@ -0,0 +1,104 @@
;;
;; qr-payment.scm
;;
;; QR payment generator.
;;
;; ISC License
;;
;; Copyright 2023-2025 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;;
;; Permission to use, copy, modify, and/or distribute this software
;; for any purpose with or without fee is hereby granted, provided
;; that the above copyright notice and this permission notice appear
;; in all copies.
;;
;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
;;
(declare (unit qr-payment))
(module
qr-payment
(
make-qrp
make-brmlab-qrp
make-brmlab-qrp-svg-string
)
(import scheme
(chicken format)
(chicken string)
(chicken base)
util-io)
(define (make-empty-qrp . vs)
(let ((v (if (null? vs) "1.0" (car vs))))
(list v "SPD")))
(define (add-field-to-qrp qrp key value)
(cons (format "~A:~A" key value)
qrp))
(define (serialize-qrp qrp)
(string-intersperse (reverse qrp) "*"))
(define (ensure-amount-format amt)
(let* ((n (if (string? amt)
(string->number amt)
amt))
(s (number->string n))
(f (string-split s "."))
(i? (null? (cdr f))))
(format "~A.~A"
(car f)
(if i?
"00"
(substring
(string-append (cadr f) "0")
0 2)))))
(define (make-qrp iban amt cc vs msg)
(let loop ((keys '(ACC AM CC MSG X-VS))
(vals (list iban (ensure-amount-format amt) cc msg vs))
(qrp (make-empty-qrp)))
(if (null? keys)
(serialize-qrp qrp)
(loop (cdr keys)
(cdr vals)
(add-field-to-qrp qrp (car keys) (car vals))))))
(define (make-brmlab-qrp amt cc vs)
(let ((iban (if (equal? cc "CZK")
"CZ0520100000002500079551"
(if (equal? cc "EUR")
"CZ9320100000002100079552"
(error "Invalid currency!")))))
(make-qrp iban amt cc vs "Brmlab")))
(define (qrp-create-svg-string qrps)
(let-values
(((ec ol)
(get-process-exit+output-lines
"qrencode"
"-t" "svg"
"--inline"
"-o" "-"
"-l" "M"
qrps)))
(if (eq? ec 0)
(string-intersperse ol "\n")
#f)))
(define (make-brmlab-qrp-svg-string amt cc vs)
(qrp-create-svg-string
(make-brmlab-qrp amt cc vs)))
)

View file

@ -366,7 +366,6 @@
slw)) slw))
state))) state)))
(let ((sln (sgr-list-neutralize sl))) (let ((sln (sgr-list-neutralize sl)))
;;(write sln)(newline)
(values (list sln) initial-state)))) (values (list sln) initial-state))))
;; Renders all the lines and appends the resulting blocks ;; Renders all the lines and appends the resulting blocks

View file

@ -39,7 +39,8 @@
;; Convert into lookups - a list of (list period regular student) ;; Convert into lookups - a list of (list period regular student)
(define member-fees-lookup-table (define member-fees-lookup-table
(make-cal-period-lookup-table (make-cal-period-lookup-table
'(((2010 1) 500 250)))) '(((2010 1) 500 250)
((2024 1) 1000 250))))
;; Exchange rates ;; Exchange rates
(define exchange-rates-lookup-table (define exchange-rates-lookup-table

View file

@ -204,8 +204,6 @@
(tbl1 (render-cells-widths ptbl col-widths)) (tbl1 (render-cells-widths ptbl col-widths))
;;(_ (print tbl1)) ;;(_ (print tbl1))
(tbl2 (map expand-row-height tbl1))) (tbl2 (map expand-row-height tbl1)))
;;(write tbl1)(newline)
;;(write tbl2)(newline)
;; Just return the result - both the table and cached column widths ;; Just return the result - both the table and cached column widths
(values tbl2 (values tbl2
col-widths)))) col-widths))))

View file

@ -88,7 +88,6 @@
(borders (expand-table-style border-spec num-columns num-rows)) (borders (expand-table-style border-spec num-columns num-rows))
(col-separators (table-col-separators? borders)) (col-separators (table-col-separators? borders))
(rows (merge-rows ptbl borders col-separators unicode?))) (rows (merge-rows ptbl borders col-separators unicode?)))
;;(write rows)(newline)
(let loop ((rows rows) (let loop ((rows rows)
(borders borders) (borders borders)
(res '()) (res '())

View file

@ -5,7 +5,7 @@
;; ;;
;; ISC License ;; ISC License
;; ;;
;; Copyright 2023 Brmlab, z.s. ;; Copyright 2023-2025 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz> ;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;; ;;
;; Permission to use, copy, modify, and/or distribute this software ;; Permission to use, copy, modify, and/or distribute this software
@ -39,7 +39,7 @@
(chicken format)) (chicken format))
;; Short banner ;; Short banner
(define banner-line "HackerBase 1.14 (c) 2023 Brmlab, z.s.") (define banner-line "HackerBase 1.19 (c) 2023-2025 Brmlab, z.s.")
;; Banner source with numbers for ANSI CSI SGR ;; Banner source with numbers for ANSI CSI SGR
(define banner-source " (define banner-source "

View file

@ -286,10 +286,12 @@
(call/cc (call/cc
(lambda (cc) (lambda (cc)
(set! break cc) (set! break cc)
(if resume (cond (resume
(resume '()) (resume '())
(bst-iter-kv bst yield)) (break #f))
#f))))) (else
(bst-iter-kv bst yield)
(break #f))))))))
(define/doc (bst-keys bst) (define/doc (bst-keys bst)
("Returns all the keys contained in given dictionary.") ("Returns all the keys contained in given dictionary.")

View file

@ -5,7 +5,7 @@
;; ;;
;; ISC License ;; ISC License
;; ;;
;; Copyright 2023 Brmlab, z.s. ;; Copyright 2023-2024 Brmlab, z.s.
;; Dominik Pantůček <dominik.pantucek@trustica.cz> ;; Dominik Pantůček <dominik.pantucek@trustica.cz>
;; ;;
;; Permission to use, copy, modify, and/or distribute this software ;; Permission to use, copy, modify, and/or distribute this software
@ -39,6 +39,8 @@
(chicken base) (chicken base)
(chicken keyword) (chicken keyword)
(chicken string) (chicken string)
(chicken irregex)
(chicken format)
util-io util-io
util-utf8 util-utf8
util-string util-string
@ -61,6 +63,14 @@ sent to the address stored within.")
"?=") "?=")
subj)) subj))
;; Extracts only usernam@domain from given full RFC email address
(define (extract-email-email str)
(let* ((irr (irregex "(?:\"?([^\"]*)\"?\\s)?(?:<?(.+@[^>]+)>?)"))
(em (irregex-match irr str))
(name (irregex-match-substring em 1))
(email (irregex-match-substring em 2)))
email))
;; Sends an email using the UNIX mail(1) utility. ;; Sends an email using the UNIX mail(1) utility.
(define*/doc (send-mail body-lines (define*/doc (send-mail body-lines
#:from (from #f) #:from (from #f)
@ -83,17 +93,22 @@ Sends email using mail(1) command. The arguments ```#:to``` and
tos)) tos))
(header-args (header-args
(flatten (flatten
(append
(if from (list (sprintf "From: ~A" from)) '())
(map (map
(lambda (h) (list "-a" h)) (lambda (h) (list "-a" h))
headers)))) headers)))))
(let ((from-email (if from
(extract-email-email from)
#f)))
(apply process-send/recv (apply process-send/recv
"mail" "mail"
(append (if from (append (if from
(list "-r" from) (list "-r" from-email)
'()) '())
(list "-s" (encode-subject subject)) (list "-s" (encode-subject subject))
real-tos real-tos
header-args) header-args)
body-lines))) body-lines))))
) )

View file

@ -39,11 +39,12 @@ member file parsers. All functions are UTF-8 aware.")
(import scheme (import scheme
(chicken base) (chicken base)
racket-kwargs
testing) testing)
;; Pass 0: Removes any comments and removes any leading and trailing ;; Pass 0: Removes any comments and removes any leading and trailing
;; whitespace. ;; whitespace.
(define/doc (parser-preprocess-line line) (define*/doc (parser-preprocess-line line #:strip-comments? (strip-comments? #t))
("* ```line``` - a string with contents of one source line ("* ```line``` - a string with contents of one source line
If the input ```line``` contains the ```#``` character, the rest of If the input ```line``` contains the ```#``` character, the rest of
@ -62,7 +63,9 @@ Returns a string representing the preprocessed line.")
(ploop (add1 pidx))))) (ploop (add1 pidx)))))
(hpos (let hloop ((hidx ppos)) (hpos (let hloop ((hidx ppos))
(if (or (= hidx llen) (if (or (= hidx llen)
(eq? (string-ref line hidx) #\#)) (and (or strip-comments?
(= hidx 0))
(eq? (string-ref line hidx) #\#)))
hidx hidx
(hloop (add1 hidx))))) (hloop (add1 hidx)))))
(spos (let sloop ((sidx (sub1 hpos))) (spos (let sloop ((sidx (sub1 hpos)))

View file

@ -120,13 +120,13 @@ of the string and a list of remaining bytes (as integers).")
(define/doc (utf8-bytes->lists chars) (define/doc (utf8-bytes->lists chars)
("The same as above but accepts a list of bytes (as integers).") ("The same as above but accepts a list of bytes (as integers).")
(let loop ((bytes chars) (let loop ((bytes chars)
(rpending '()) (rpending chars)
(pending 0) (pending 0)
(expected #f) (expected #f)
(res '())) (res '()))
(if (null? bytes) (if (null? bytes)
(values (reverse res) (values (reverse res)
(reverse rpending)) rpending)
(let ((byte (car bytes))) (let ((byte (car bytes)))
(cond (expected (cond (expected
;; Decode UTF-8 sequence ;; Decode UTF-8 sequence
@ -135,14 +135,14 @@ of the string and a list of remaining bytes (as integers).")
(let ((char (integer->char (bitwise-ior pending (let ((char (integer->char (bitwise-ior pending
(bitwise-and byte #b111111))))) (bitwise-and byte #b111111)))))
(loop (cdr bytes) (loop (cdr bytes)
'() (cdr bytes)
0 0
#f #f
(cons char res)))) (cons char res))))
(else (else
;; Intermediate bytes ;; Intermediate bytes
(loop (cdr bytes) (loop (cdr bytes)
(cons byte rpending) rpending
(arithmetic-shift (bitwise-ior pending (arithmetic-shift (bitwise-ior pending
(bitwise-and byte #b111111)) 6) (bitwise-and byte #b111111)) 6)
(sub1 expected) (sub1 expected)
@ -152,7 +152,7 @@ of the string and a list of remaining bytes (as integers).")
(cond ((= (bitwise-and byte #b10000000) 0) (cond ((= (bitwise-and byte #b10000000) 0)
;; ASCII ;; ASCII
(loop (cdr bytes) (loop (cdr bytes)
'() (cdr bytes)
0 0
#f #f
(cons (integer->char byte) res))) (cons (integer->char byte) res)))
@ -160,20 +160,20 @@ of the string and a list of remaining bytes (as integers).")
;; First byte of UTF-8 sequence ;; First byte of UTF-8 sequence
(let-values (let-values
(((first-byte char-bytes) (((first-byte char-bytes)
(cond ((= (bitwise-and byte #b11000000) #b11000000) (cond ((= (bitwise-and byte #b11100000) #b11000000)
(values (bitwise-and byte #b11111) (values (bitwise-and byte #b11111)
2)) 2))
((= (bitwise-and byte #b11100000) #b11100000) ((= (bitwise-and byte #b11110000) #b11100000)
(values (bitwise-and byte #b1111) (values (bitwise-and byte #b1111)
3)) 3))
((= (bitwise-and byte #b11110000) #b11110000) ((= (bitwise-and byte #b11111000) #b11110000)
(values (bitwise-and byte #b111) (values (bitwise-and byte #b111)
4)) 4))
(else (else
;; Should not happen ;; Should not happen
(values 0 0))))) (values 0 0)))))
(loop (cdr bytes) (loop (cdr bytes)
(list byte) bytes
(arithmetic-shift first-byte 6) (arithmetic-shift first-byte 6)
(sub1 char-bytes) (sub1 char-bytes)
res)))))))))) res))))))))))