Compare commits
	
		
			26 commits
		
	
	
		
			a86063e722
			...
			5f4724874e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5f4724874e | |||
| 9f5877d3f0 | |||
| ac83dd9c72 | |||
| b324516514 | |||
| 1d523a0495 | |||
| 17ce5cc126 | |||
| 4d73afe3c5 | |||
| bbbc6527a0 | |||
| 306b9cb20e | |||
| 826a5f1070 | |||
| 5052a8d46f | |||
| fa8466cfff | |||
| cebe6a6cf7 | |||
| df1a30eead | |||
| c8c71f8465 | |||
| 6cfdf705c8 | |||
| 227787597d | |||
| 7dbdd3ea6e | |||
| 0e9cfd546b | |||
| b25fbd407d | |||
| e02853edc7 | |||
| 53be61d345 | |||
| 51a108ce64 | |||
| fe42315cd9 | |||
| 9eb835fa72 | |||
| eff186cb4c | 
					 16 changed files with 1002 additions and 626 deletions
				
			
		|  | @ -1,6 +1,14 @@ | ||||||
| ChangeLog | ChangeLog | ||||||
| ========= | ========= | ||||||
| 
 | 
 | ||||||
|  | 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 | 1.17 - released 2024-10-01 | ||||||
| -------------------------- | -------------------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
							
								
								
									
										47
									
								
								src/Makefile
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								src/Makefile
									
										
									
									
									
								
							|  | @ -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 mailinglist.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,9 +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 | 	 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	\
 | ||||||
|  | @ -258,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 | ||||||
|  | @ -473,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) | ||||||
|  | @ -524,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) | ||||||
|  | @ -559,3 +554,25 @@ MAILINGLIST-SOURCES=mailinglist.scm racket-kwargs.import.scm		\ | ||||||
| 
 | 
 | ||||||
| mailinglist.o: mailinglist.import.scm | mailinglist.o: mailinglist.import.scm | ||||||
| mailinglist.import.scm: $(MAILINGLIST-SOURCES) | 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) | ||||||
|  |  | ||||||
|  | @ -87,6 +87,8 @@ | ||||||
| 
 | 
 | ||||||
|   brmember-spec-fee |   brmember-spec-fee | ||||||
| 
 | 
 | ||||||
|  |   brmember-age | ||||||
|  | 
 | ||||||
|   brmember-tests! |   brmember-tests! | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
|  | @ -492,6 +494,18 @@ | ||||||
| 	       #f)) | 	       #f)) | ||||||
| 	 #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 | ||||||
|  |  | ||||||
|  | @ -26,460 +26,464 @@ | ||||||
| (declare (unit cal-period)) | (declare (unit cal-period)) | ||||||
| 
 | 
 | ||||||
| (module | (module | ||||||
|  cal-period |  | ||||||
|  ( |  | ||||||
|   *current-month* |  | ||||||
|   *current-day* |  | ||||||
| 
 |  | ||||||
|   set-current-month! |  | ||||||
|   set-current-day! |  | ||||||
| 
 |  | ||||||
|   with-current-month |  | ||||||
|   with-current-day |  | ||||||
|    |  | ||||||
|   make-cal-period |  | ||||||
| 
 |  | ||||||
|   cal-period-since |  | ||||||
|   cal-period-before |  | ||||||
|   cal-period-scomment |  | ||||||
|   cal-period-bcomment |  | ||||||
| 
 |  | ||||||
|   set-cal-period-scomment |  | ||||||
|    |  | ||||||
|   period-markers->cal-periods |  | ||||||
|    |  | ||||||
|   cal-periods-duration |  | ||||||
|    |  | ||||||
|   cal-month-in-period? |  | ||||||
|   cal-month-in-periods? |  | ||||||
| 
 |  | ||||||
|   cal-month-find-period |  | ||||||
| 
 |  | ||||||
|   cal-day-in-period? |  | ||||||
|   cal-day-in-periods? |  | ||||||
|    |  | ||||||
|   cal-periods->string |  | ||||||
|   cal-periods-match |  | ||||||
|    |  | ||||||
|   make-cal-period-lookup-table |  | ||||||
|   lookup-by-cal-period |  | ||||||
| 
 |  | ||||||
|   cal-ensure-month |  | ||||||
|   cal-ensure-day |  | ||||||
|    |  | ||||||
|   cal-period-tests! |  | ||||||
|   ) |  | ||||||
| 
 |  | ||||||
|  (import scheme |  | ||||||
| 	 (chicken base) |  | ||||||
| 	 (chicken sort) |  | ||||||
| 	 (chicken time) |  | ||||||
| 	 (chicken time posix) |  | ||||||
| 	 (chicken format) |  | ||||||
| 	 (chicken string) |  | ||||||
| 	 cal-month |  | ||||||
| 	 testing |  | ||||||
| 	 util-tag |  | ||||||
| 	 cal-day) |  | ||||||
| 
 |  | ||||||
|  ;; Type tag |  | ||||||
|  (define TAG-CAL-PERIOD (make-tag CAL-PERIOD)) |  | ||||||
| 
 |  | ||||||
|  ;; Current month - if changed, we get the actual state for given month. |  | ||||||
|  (define *current-month* |  | ||||||
|    (make-parameter |  | ||||||
|     (let ((d (seconds->local-time (current-seconds)))) |  | ||||||
|       (make-cal-month (+ 1900 (vector-ref d 5)) |  | ||||||
| 		      (+ (vector-ref d 4) 1))))) |  | ||||||
| 
 |  | ||||||
|  ;; Current month - if changed, we get the actual state for given month. |  | ||||||
|  (define *current-day* |  | ||||||
|    (make-parameter |  | ||||||
|     (let ((d (seconds->local-time (current-seconds)))) |  | ||||||
|       (make-cal-day (+ 1900 (vector-ref d 5)) |  | ||||||
| 		    (+ (vector-ref d 4) 1) |  | ||||||
| 		    (vector-ref d 3))))) |  | ||||||
| 
 |  | ||||||
|  ;; Changes both current-month and current-day based on given month |  | ||||||
|  (define (set-current-month! m) |  | ||||||
|    (*current-month* m) |  | ||||||
|    (*current-day* (cal-ensure-day m))) |  | ||||||
| 
 |  | ||||||
|  ;; Changes both current-day and current-month based on given day |  | ||||||
|  (define (set-current-day! d) |  | ||||||
|    (*current-day* d) |  | ||||||
|    (*current-month* (cal-ensure-month d))) |  | ||||||
| 
 |  | ||||||
|  ;; Parameterizes both current-month and current-day based on given |  | ||||||
|  ;; month |  | ||||||
|  (define-syntax with-current-month |  | ||||||
|    (syntax-rules () |  | ||||||
|      ((_ ms body ...) |  | ||||||
|       (let ((m ms)) |  | ||||||
| 	(parameterize ((*current-month* m) |  | ||||||
| 		       (*current-day* (cal-ensure-day m))) |  | ||||||
| 	  body ...))))) |  | ||||||
| 
 |  | ||||||
|  ;; Parameterizes both current-day and current-month based on given |  | ||||||
|  ;; day |  | ||||||
|  (define-syntax with-current-day |  | ||||||
|    (syntax-rules () |  | ||||||
|      ((_ ds body ...) |  | ||||||
|       (let ((d ds)) |  | ||||||
| 	(parameterize ((*current-day* d) |  | ||||||
| 		       (*current-month* (cal-ensure-month d))) |  | ||||||
| 	  body ...))))) |  | ||||||
| 
 |  | ||||||
|  ;; Creates a new period value with optional since and before |  | ||||||
|  ;; comments. |  | ||||||
|  (define (make-cal-period since before . args) |  | ||||||
|    (let ((scomment (if (not (null? args)) (car args) #f)) |  | ||||||
| 	 (bcomment (if (and (not (null? args)) |  | ||||||
| 			    (not (null? (cdr args)))) |  | ||||||
| 		       (cadr args) |  | ||||||
| 		       #f))) |  | ||||||
|      (list TAG-CAL-PERIOD since before scomment bcomment))) |  | ||||||
| 
 |  | ||||||
|  ;; Simple accessors |  | ||||||
|  (define cal-period-since cadr) |  | ||||||
|  (define cal-period-before caddr) |  | ||||||
|  (define cal-period-scomment cadddr) |  | ||||||
|  (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 |  | ||||||
|  (define (cal-period? p) |  | ||||||
|    (and (pair? p) |  | ||||||
| 	(eq? (car p) |  | ||||||
| 	     TAG-CAL-PERIOD))) |  | ||||||
| 
 |  | ||||||
|  ;; Month subtype predicate |  | ||||||
|  (define (cal-period-month? p) |  | ||||||
|    (and (cal-period? p) |  | ||||||
| 	(cal-month? (cal-period-since p)) |  | ||||||
| 	(cal-month? (cal-period-before p)))) |  | ||||||
| 
 |  | ||||||
|  ;; Day subtype predicate |  | ||||||
|  (define (cal-period-day? p) |  | ||||||
|    (and (cal-period? p) |  | ||||||
| 	(cal-day? (cal-period-since p)) |  | ||||||
| 	(cal-day? (cal-period-before p)))) |  | ||||||
| 
 |  | ||||||
|  ;; Validation |  | ||||||
|  (define (cal-period-valid? p) |  | ||||||
|    (and (pair? p) |  | ||||||
| 	(eq? (car p) |  | ||||||
| 	     TAG-CAL-PERIOD) |  | ||||||
| 	(let ((since (cal-period-since p)) |  | ||||||
| 	      (before (cal-period-before p))) |  | ||||||
| 	  (or (and (cal-month? since) |  | ||||||
| 		   (cal-month? before) |  | ||||||
| 		   (cal-month<=? since before)) |  | ||||||
| 	      (and (cal-day? since) |  | ||||||
| 		   (cal-day? before) |  | ||||||
| 		   (cal-day<=? since before)))))) |  | ||||||
| 
 |  | ||||||
|  ;; Sorts period markers (be it start or end) chronologically and |  | ||||||
|  ;; returns the sorted list. |  | ||||||
|  (define (sort-period-markers l) |  | ||||||
|    (sort l |  | ||||||
| 	 (lambda (a b) |  | ||||||
| 	   (cal-day/month<? (cadr a) (cadr b))))) |  | ||||||
| 
 |  | ||||||
|  ;; Converts list of start/stop markers to list of pairs of months - |  | ||||||
|  ;; periods. The markers are lists in the form (start/stop cal-month). |  | ||||||
|  (define (period-markers->cal-periods l) |  | ||||||
|    (let loop ((l (sort-period-markers l)) |  | ||||||
| 	      (ps '()) |  | ||||||
| 	      (cb #f)) |  | ||||||
|      (if (null? l) |  | ||||||
| 	 (list #t |  | ||||||
| 	       (if cb |  | ||||||
| 		   (reverse (cons (make-cal-period (car cb) #f (cadr cb)) ps)) |  | ||||||
| 		   (reverse ps)) |  | ||||||
| 	       "" |  | ||||||
| 	       -1) |  | ||||||
| 	 (let* ((marker (car l)) |  | ||||||
| 		(rmt (if cb 'stop 'start)) |  | ||||||
| 		(mtype (car marker)) |  | ||||||
| 		(month (cadr marker)) |  | ||||||
| 		(line-number (if (null? (cddr marker)) |  | ||||||
| 				 #f |  | ||||||
| 				 (caddr marker))) |  | ||||||
| 		(comment (if (and line-number |  | ||||||
| 				  (not (null? (cdddr marker)))) |  | ||||||
| 			     (cadddr marker) |  | ||||||
| 			     #f))) |  | ||||||
| 	   (if (eq? mtype rmt) |  | ||||||
| 	       (if cb |  | ||||||
| 		   (loop (cdr l) |  | ||||||
| 			 (cons (make-cal-period (car cb) month (cadr cb) comment) ps) |  | ||||||
| 			 #f) |  | ||||||
| 		   (loop (cdr l) |  | ||||||
| 			 ps |  | ||||||
| 			 (list month comment))) |  | ||||||
| 	       (list #f |  | ||||||
| 		     (reverse ps) |  | ||||||
| 		     (sprintf "Invalid start/stop sequence marker ~A" marker) |  | ||||||
| 		     line-number)))))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns duration of period in months. Start is included, end is |  | ||||||
|  ;; not. The period contains the month just before the specified end. |  | ||||||
|  (define (cal-period->duration p) |  | ||||||
|    (let* ((b (cal-period-since p)) |  | ||||||
| 	  (e (cal-period-before p)) |  | ||||||
| 	  (e- (if e e (*current-month*)))) |  | ||||||
|      (cal-month-diff b e-))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns sum of periods lengths. |  | ||||||
|  (define (cal-periods-duration l) |  | ||||||
|    (apply + (map cal-period->duration l))) |  | ||||||
| 
 |  | ||||||
|  ;; True if month belongs to given month period - start inclusive, end |  | ||||||
|  ;; exclusive. |  | ||||||
|  (define (cal-month-in-period? p . ml) |  | ||||||
|    (let ((m (if (null? ml) |  | ||||||
| 		(*current-month*) |  | ||||||
| 		(cal-ensure-month (car ml)))) |  | ||||||
| 	 (before (cal-ensure-month (cal-period-before p) #t)) |  | ||||||
| 	 (since (cal-ensure-month (cal-period-since p)))) |  | ||||||
|      (and (or (not before) |  | ||||||
| 	      (cal-month<? m before)) |  | ||||||
| 	  (not (cal-month<? m since))))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns true if given month is in at least one of the periods |  | ||||||
|  ;; given. Defaults to current month. |  | ||||||
|  (define (cal-month-in-periods? 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) |  | ||||||
| 	       #t |  | ||||||
| 	       (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 |  | ||||||
|  (define (cal-day-in-period? p . dl) |  | ||||||
|    (let ((d (if (null? dl) |  | ||||||
| 		(*current-day*) |  | ||||||
| 		(cal-ensure-day (car dl)))) |  | ||||||
| 	 (before (cal-ensure-day (cal-period-before p))) |  | ||||||
| 	 (since (cal-ensure-day (cal-period-since p)))) |  | ||||||
|      (and (or (not before) |  | ||||||
| 	      (cal-day<? d before)) |  | ||||||
| 	  (not (cal-day<? d since))))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns true if the day belongs to at least one period |  | ||||||
|  (define (cal-day-in-periods? ps . dl) |  | ||||||
|    (let ((d (if (null? dl) |  | ||||||
| 		(*current-day*) |  | ||||||
| 		(cal-ensure-day (car dl))))) |  | ||||||
|      (let loop ((ps ps)) |  | ||||||
|        (if (null? ps) |  | ||||||
| 	   #f |  | ||||||
| 	   (if (cal-day-in-period? (car ps) d) |  | ||||||
| 	       #t |  | ||||||
| 	       (loop (cdr ps))))))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns string representing a month period with possibly open end. |  | ||||||
|  (define (cal-period->string p) |  | ||||||
|    (sprintf "~A..~A" |  | ||||||
| 	    (cal-day/month->string (cal-period-since p)) |  | ||||||
| 	    (cal-day/month->string (cal-period-before p)))) |  | ||||||
| 
 |  | ||||||
|  ;; Returns a string representing a list of periods. |  | ||||||
|  (define (cal-periods->string ps) |  | ||||||
|    (string-intersperse |  | ||||||
|     (map cal-period->string ps) |  | ||||||
|     ", ")) |  | ||||||
| 
 |  | ||||||
|  ;; Finds a period the month matches and returns it. If no period |  | ||||||
|  ;; matches, it returns #f. |  | ||||||
|  (define (cal-periods-match 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))))))) |  | ||||||
| 
 |  | ||||||
|  ;; Creates lookup table from definition source |  | ||||||
|  (define (make-cal-period-lookup-table source) |  | ||||||
|    (let loop ((lst source) |  | ||||||
| 	      (res '()) |  | ||||||
| 	      (prev #f)) |  | ||||||
|      (if (null? lst) |  | ||||||
| 	 (reverse |  | ||||||
| 	  (cons (cons (make-cal-period (apply make-cal-month (car prev)) #f) |  | ||||||
| 		      (cdr prev)) |  | ||||||
| 		res)) |  | ||||||
| 	 (loop (cdr lst) |  | ||||||
| 	       (if prev |  | ||||||
| 		   (cons (cons (make-cal-period (apply make-cal-month (car prev)) |  | ||||||
| 						(apply make-cal-month (caar lst))) |  | ||||||
| 			       (cdr prev)) |  | ||||||
| 			 res) |  | ||||||
| 		   res) |  | ||||||
| 	       (car lst))))) |  | ||||||
| 
 |  | ||||||
|  ;; Looks up current month and returns associated definitions |  | ||||||
|  (define (lookup-by-cal-period table) |  | ||||||
|    (let loop ((lst table)) |  | ||||||
|      (if (null? lst) |  | ||||||
| 	 #f |  | ||||||
| 	 (if (cal-month-in-period? (caar lst)) |  | ||||||
| 	     (cdar lst) |  | ||||||
| 	     (loop (cdr lst)))))) |  | ||||||
| 
 |  | ||||||
|  ;; Wrapper that accepts either day or month and returns testable month |  | ||||||
|  (define (cal-ensure-month v . stop?s) |  | ||||||
|    (if v |  | ||||||
|        (if (cal-month? v) |  | ||||||
| 	   v |  | ||||||
| 	   (if (cal-day? v) |  | ||||||
| 	       (apply cal-day->month v stop?s) |  | ||||||
| 	       #f)) |  | ||||||
|        #f)) |  | ||||||
| 
 |  | ||||||
|  ;; Ensures day for checking the periods |  | ||||||
|  (define (cal-ensure-day v) |  | ||||||
|    (if v |  | ||||||
|        (if (cal-day? v) |  | ||||||
| 	   v |  | ||||||
| 	   (if (cal-month? v) |  | ||||||
| 	       (make-cal-day (cal-month-year v) |  | ||||||
| 			     (cal-month-month v) |  | ||||||
| 			     1) |  | ||||||
| 	       #f)) |  | ||||||
|        #f)) |  | ||||||
| 
 |  | ||||||
|  ;; Performs self-tests of the period module. |  | ||||||
|  (define (cal-period-tests!) |  | ||||||
|    (run-tests |  | ||||||
|     cal-period |     cal-period | ||||||
|     (test-equal? sort-period-markers |     ( | ||||||
| 		 (sort-period-markers |      current-year | ||||||
| 		  `((start ,(make-cal-month 2023 1)) |      *current-month* | ||||||
| 		    (stop ,(make-cal-month 2022 10)) |      *current-day* | ||||||
| 		    (start ,(make-cal-month 2022 3)))) | 
 | ||||||
| 		 `((start ,(make-cal-month 2022 3)) |      set-current-month! | ||||||
| 		   (stop ,(make-cal-month 2022 10)) |      set-current-day! | ||||||
| 		   (start ,(make-cal-month 2023 1)))) | 
 | ||||||
|     (test-equal? period-markers->cal-periods |      with-current-month | ||||||
| 	         (period-markers->cal-periods |      with-current-day | ||||||
|  |    | ||||||
|  |      make-cal-period | ||||||
|  | 
 | ||||||
|  |      cal-period-since | ||||||
|  |      cal-period-before | ||||||
|  |      cal-period-scomment | ||||||
|  |      cal-period-bcomment | ||||||
|  | 
 | ||||||
|  |      set-cal-period-scomment | ||||||
|  |    | ||||||
|  |      period-markers->cal-periods | ||||||
|  |    | ||||||
|  |      cal-periods-duration | ||||||
|  |    | ||||||
|  |      cal-month-in-period? | ||||||
|  |      cal-month-in-periods? | ||||||
|  | 
 | ||||||
|  |      cal-month-find-period | ||||||
|  | 
 | ||||||
|  |      cal-day-in-period? | ||||||
|  |      cal-day-in-periods? | ||||||
|  |    | ||||||
|  |      cal-periods->string | ||||||
|  |      cal-periods-match | ||||||
|  |    | ||||||
|  |      make-cal-period-lookup-table | ||||||
|  |      lookup-by-cal-period | ||||||
|  | 
 | ||||||
|  |      cal-ensure-month | ||||||
|  |      cal-ensure-day | ||||||
|  |    | ||||||
|  |      cal-period-tests! | ||||||
|  |      ) | ||||||
|  | 
 | ||||||
|  |   (import scheme | ||||||
|  | 	  (chicken base) | ||||||
|  | 	  (chicken sort) | ||||||
|  | 	  (chicken time) | ||||||
|  | 	  (chicken time posix) | ||||||
|  | 	  (chicken format) | ||||||
|  | 	  (chicken string) | ||||||
|  | 	  cal-month | ||||||
|  | 	  testing | ||||||
|  | 	  util-tag | ||||||
|  | 	  cal-day) | ||||||
|  | 
 | ||||||
|  |   ;; Type tag | ||||||
|  |   (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. | ||||||
|  |   (define *current-month* | ||||||
|  |     (make-parameter | ||||||
|  |      (let ((d (seconds->local-time (current-seconds)))) | ||||||
|  |        (make-cal-month (+ 1900 (vector-ref d 5)) | ||||||
|  | 		       (+ (vector-ref d 4) 1))))) | ||||||
|  | 
 | ||||||
|  |   ;; Current month - if changed, we get the actual state for given month. | ||||||
|  |   (define *current-day* | ||||||
|  |     (make-parameter | ||||||
|  |      (let ((d (seconds->local-time (current-seconds)))) | ||||||
|  |        (make-cal-day (+ 1900 (vector-ref d 5)) | ||||||
|  | 		     (+ (vector-ref d 4) 1) | ||||||
|  | 		     (vector-ref d 3))))) | ||||||
|  | 
 | ||||||
|  |   ;; Changes both current-month and current-day based on given month | ||||||
|  |   (define (set-current-month! m) | ||||||
|  |     (*current-month* m) | ||||||
|  |     (*current-day* (cal-ensure-day m))) | ||||||
|  | 
 | ||||||
|  |   ;; Changes both current-day and current-month based on given day | ||||||
|  |   (define (set-current-day! d) | ||||||
|  |     (*current-day* d) | ||||||
|  |     (*current-month* (cal-ensure-month d))) | ||||||
|  | 
 | ||||||
|  |   ;; Parameterizes both current-month and current-day based on given | ||||||
|  |   ;; month | ||||||
|  |   (define-syntax with-current-month | ||||||
|  |     (syntax-rules () | ||||||
|  |       ((_ ms body ...) | ||||||
|  |        (let ((m ms)) | ||||||
|  | 	 (parameterize ((*current-month* m) | ||||||
|  | 			(*current-day* (cal-ensure-day m))) | ||||||
|  | 	   body ...))))) | ||||||
|  | 
 | ||||||
|  |   ;; Parameterizes both current-day and current-month based on given | ||||||
|  |   ;; day | ||||||
|  |   (define-syntax with-current-day | ||||||
|  |     (syntax-rules () | ||||||
|  |       ((_ ds body ...) | ||||||
|  |        (let ((d ds)) | ||||||
|  | 	 (parameterize ((*current-day* d) | ||||||
|  | 			(*current-month* (cal-ensure-month d))) | ||||||
|  | 	   body ...))))) | ||||||
|  | 
 | ||||||
|  |   ;; Creates a new period value with optional since and before | ||||||
|  |   ;; comments. | ||||||
|  |   (define (make-cal-period since before . args) | ||||||
|  |     (let ((scomment (if (not (null? args)) (car args) #f)) | ||||||
|  | 	  (bcomment (if (and (not (null? args)) | ||||||
|  | 			     (not (null? (cdr args)))) | ||||||
|  | 			(cadr args) | ||||||
|  | 			#f))) | ||||||
|  |       (list TAG-CAL-PERIOD since before scomment bcomment))) | ||||||
|  | 
 | ||||||
|  |   ;; Simple accessors | ||||||
|  |   (define cal-period-since cadr) | ||||||
|  |   (define cal-period-before caddr) | ||||||
|  |   (define cal-period-scomment cadddr) | ||||||
|  |   (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 | ||||||
|  |   (define (cal-period? p) | ||||||
|  |     (and (pair? p) | ||||||
|  | 	 (eq? (car p) | ||||||
|  | 	      TAG-CAL-PERIOD))) | ||||||
|  | 
 | ||||||
|  |   ;; Month subtype predicate | ||||||
|  |   (define (cal-period-month? p) | ||||||
|  |     (and (cal-period? p) | ||||||
|  | 	 (cal-month? (cal-period-since p)) | ||||||
|  | 	 (cal-month? (cal-period-before p)))) | ||||||
|  | 
 | ||||||
|  |   ;; Day subtype predicate | ||||||
|  |   (define (cal-period-day? p) | ||||||
|  |     (and (cal-period? p) | ||||||
|  | 	 (cal-day? (cal-period-since p)) | ||||||
|  | 	 (cal-day? (cal-period-before p)))) | ||||||
|  | 
 | ||||||
|  |   ;; Validation | ||||||
|  |   (define (cal-period-valid? p) | ||||||
|  |     (and (pair? p) | ||||||
|  | 	 (eq? (car p) | ||||||
|  | 	      TAG-CAL-PERIOD) | ||||||
|  | 	 (let ((since (cal-period-since p)) | ||||||
|  | 	       (before (cal-period-before p))) | ||||||
|  | 	   (or (and (cal-month? since) | ||||||
|  | 		    (cal-month? before) | ||||||
|  | 		    (cal-month<=? since before)) | ||||||
|  | 	       (and (cal-day? since) | ||||||
|  | 		    (cal-day? before) | ||||||
|  | 		    (cal-day<=? since before)))))) | ||||||
|  | 
 | ||||||
|  |   ;; Sorts period markers (be it start or end) chronologically and | ||||||
|  |   ;; returns the sorted list. | ||||||
|  |   (define (sort-period-markers l) | ||||||
|  |     (sort l | ||||||
|  | 	  (lambda (a b) | ||||||
|  | 	    (cal-day/month<? (cadr a) (cadr b))))) | ||||||
|  | 
 | ||||||
|  |   ;; Converts list of start/stop markers to list of pairs of months - | ||||||
|  |   ;; periods. The markers are lists in the form (start/stop cal-month). | ||||||
|  |   (define (period-markers->cal-periods l) | ||||||
|  |     (let loop ((l (sort-period-markers l)) | ||||||
|  | 	       (ps '()) | ||||||
|  | 	       (cb #f)) | ||||||
|  |       (if (null? l) | ||||||
|  | 	  (list #t | ||||||
|  | 		(if cb | ||||||
|  | 		    (reverse (cons (make-cal-period (car cb) #f (cadr cb)) ps)) | ||||||
|  | 		    (reverse ps)) | ||||||
|  | 		"" | ||||||
|  | 		-1) | ||||||
|  | 	  (let* ((marker (car l)) | ||||||
|  | 		 (rmt (if cb 'stop 'start)) | ||||||
|  | 		 (mtype (car marker)) | ||||||
|  | 		 (month (cadr marker)) | ||||||
|  | 		 (line-number (if (null? (cddr marker)) | ||||||
|  | 				  #f | ||||||
|  | 				  (caddr marker))) | ||||||
|  | 		 (comment (if (and line-number | ||||||
|  | 				   (not (null? (cdddr marker)))) | ||||||
|  | 			      (cadddr marker) | ||||||
|  | 			      #f))) | ||||||
|  | 	    (if (eq? mtype rmt) | ||||||
|  | 		(if cb | ||||||
|  | 		    (loop (cdr l) | ||||||
|  | 			  (cons (make-cal-period (car cb) month (cadr cb) comment) ps) | ||||||
|  | 			  #f) | ||||||
|  | 		    (loop (cdr l) | ||||||
|  | 			  ps | ||||||
|  | 			  (list month comment))) | ||||||
|  | 		(list #f | ||||||
|  | 		      (reverse ps) | ||||||
|  | 		      (sprintf "Invalid start/stop sequence marker ~A" marker) | ||||||
|  | 		      line-number)))))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns duration of period in months. Start is included, end is | ||||||
|  |   ;; not. The period contains the month just before the specified end. | ||||||
|  |   (define (cal-period->duration p) | ||||||
|  |     (let* ((b (cal-period-since p)) | ||||||
|  | 	   (e (cal-period-before p)) | ||||||
|  | 	   (e- (if e e (*current-month*)))) | ||||||
|  |       (cal-month-diff b e-))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns sum of periods lengths. | ||||||
|  |   (define (cal-periods-duration l) | ||||||
|  |     (apply + (map cal-period->duration l))) | ||||||
|  | 
 | ||||||
|  |   ;; True if month belongs to given month period - start inclusive, end | ||||||
|  |   ;; exclusive. | ||||||
|  |   (define (cal-month-in-period? p . ml) | ||||||
|  |     (let ((m (if (null? ml) | ||||||
|  | 		 (*current-month*) | ||||||
|  | 		 (cal-ensure-month (car ml)))) | ||||||
|  | 	  (before (cal-ensure-month (cal-period-before p) #t)) | ||||||
|  | 	  (since (cal-ensure-month (cal-period-since p)))) | ||||||
|  |       (and (or (not before) | ||||||
|  | 	       (cal-month<? m before)) | ||||||
|  | 	   (not (cal-month<? m since))))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns true if given month is in at least one of the periods | ||||||
|  |   ;; given. Defaults to current month. | ||||||
|  |   (define (cal-month-in-periods? 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) | ||||||
|  | 		#t | ||||||
|  | 		(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 | ||||||
|  |   (define (cal-day-in-period? p . dl) | ||||||
|  |     (let ((d (if (null? dl) | ||||||
|  | 		 (*current-day*) | ||||||
|  | 		 (cal-ensure-day (car dl)))) | ||||||
|  | 	  (before (cal-ensure-day (cal-period-before p))) | ||||||
|  | 	  (since (cal-ensure-day (cal-period-since p)))) | ||||||
|  |       (and (or (not before) | ||||||
|  | 	       (cal-day<? d before)) | ||||||
|  | 	   (not (cal-day<? d since))))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns true if the day belongs to at least one period | ||||||
|  |   (define (cal-day-in-periods? ps . dl) | ||||||
|  |     (let ((d (if (null? dl) | ||||||
|  | 		 (*current-day*) | ||||||
|  | 		 (cal-ensure-day (car dl))))) | ||||||
|  |       (let loop ((ps ps)) | ||||||
|  | 	(if (null? ps) | ||||||
|  | 	    #f | ||||||
|  | 	    (if (cal-day-in-period? (car ps) d) | ||||||
|  | 		#t | ||||||
|  | 		(loop (cdr ps))))))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns string representing a month period with possibly open end. | ||||||
|  |   (define (cal-period->string p) | ||||||
|  |     (sprintf "~A..~A" | ||||||
|  | 	     (cal-day/month->string (cal-period-since p)) | ||||||
|  | 	     (cal-day/month->string (cal-period-before p)))) | ||||||
|  | 
 | ||||||
|  |   ;; Returns a string representing a list of periods. | ||||||
|  |   (define (cal-periods->string ps) | ||||||
|  |     (string-intersperse | ||||||
|  |      (map cal-period->string ps) | ||||||
|  |      ", ")) | ||||||
|  | 
 | ||||||
|  |   ;; Finds a period the month matches and returns it. If no period | ||||||
|  |   ;; matches, it returns #f. | ||||||
|  |   (define (cal-periods-match 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))))))) | ||||||
|  | 
 | ||||||
|  |   ;; Creates lookup table from definition source | ||||||
|  |   (define (make-cal-period-lookup-table source) | ||||||
|  |     (let loop ((lst source) | ||||||
|  | 	       (res '()) | ||||||
|  | 	       (prev #f)) | ||||||
|  |       (if (null? lst) | ||||||
|  | 	  (reverse | ||||||
|  | 	   (cons (cons (make-cal-period (apply make-cal-month (car prev)) #f) | ||||||
|  | 		       (cdr prev)) | ||||||
|  | 		 res)) | ||||||
|  | 	  (loop (cdr lst) | ||||||
|  | 		(if prev | ||||||
|  | 		    (cons (cons (make-cal-period (apply make-cal-month (car prev)) | ||||||
|  | 						 (apply make-cal-month (caar lst))) | ||||||
|  | 				(cdr prev)) | ||||||
|  | 			  res) | ||||||
|  | 		    res) | ||||||
|  | 		(car lst))))) | ||||||
|  | 
 | ||||||
|  |   ;; Looks up current month and returns associated definitions | ||||||
|  |   (define (lookup-by-cal-period table) | ||||||
|  |     (let loop ((lst table)) | ||||||
|  |       (if (null? lst) | ||||||
|  | 	  #f | ||||||
|  | 	  (if (cal-month-in-period? (caar lst)) | ||||||
|  | 	      (cdar lst) | ||||||
|  | 	      (loop (cdr lst)))))) | ||||||
|  | 
 | ||||||
|  |   ;; Wrapper that accepts either day or month and returns testable month | ||||||
|  |   (define (cal-ensure-month v . stop?s) | ||||||
|  |     (if v | ||||||
|  | 	(if (cal-month? v) | ||||||
|  | 	    v | ||||||
|  | 	    (if (cal-day? v) | ||||||
|  | 		(apply cal-day->month v stop?s) | ||||||
|  | 		#f)) | ||||||
|  | 	#f)) | ||||||
|  | 
 | ||||||
|  |   ;; Ensures day for checking the periods | ||||||
|  |   (define (cal-ensure-day v) | ||||||
|  |     (if v | ||||||
|  | 	(if (cal-day? v) | ||||||
|  | 	    v | ||||||
|  | 	    (if (cal-month? v) | ||||||
|  | 		(make-cal-day (cal-month-year v) | ||||||
|  | 			      (cal-month-month v) | ||||||
|  | 			      1) | ||||||
|  | 		#f)) | ||||||
|  | 	#f)) | ||||||
|  | 
 | ||||||
|  |   ;; Performs self-tests of the period module. | ||||||
|  |   (define (cal-period-tests!) | ||||||
|  |     (run-tests | ||||||
|  |      cal-period | ||||||
|  |      (test-equal? sort-period-markers | ||||||
|  | 		  (sort-period-markers | ||||||
|  | 		   `((start ,(make-cal-month 2023 1)) | ||||||
|  | 		     (stop ,(make-cal-month 2022 10)) | ||||||
|  | 		     (start ,(make-cal-month 2022 3)))) | ||||||
| 		  `((start ,(make-cal-month 2022 3)) | 		  `((start ,(make-cal-month 2022 3)) | ||||||
| 		    (stop ,(make-cal-month 2022 10)) | 		    (stop ,(make-cal-month 2022 10)) | ||||||
| 		    (start ,(make-cal-month 2023 1)) | 		    (start ,(make-cal-month 2023 1)))) | ||||||
| 		    (stop ,(make-cal-month 2023 4)))) |      (test-equal? period-markers->cal-periods | ||||||
| 		 `(#t | 	          (period-markers->cal-periods | ||||||
| 		   (,(make-cal-period (make-cal-month 2022 3) | 		   `((start ,(make-cal-month 2022 3)) | ||||||
| 				      (make-cal-month 2022 10) #f #f) | 		     (stop ,(make-cal-month 2022 10)) | ||||||
| 		    ,(make-cal-period (make-cal-month 2023 1) | 		     (start ,(make-cal-month 2023 1)) | ||||||
| 				      (make-cal-month 2023 4) #f #f)) | 		     (stop ,(make-cal-month 2023 4)))) | ||||||
| 		   "" | 		  `(#t | ||||||
| 		   -1)) | 		    (,(make-cal-period (make-cal-month 2022 3) | ||||||
|     (test-equal? period-markers->cal-periods-open | 				       (make-cal-month 2022 10) #f #f) | ||||||
| 	         (period-markers->cal-periods | 		     ,(make-cal-period (make-cal-month 2023 1) | ||||||
| 		  `((start ,(make-cal-month 2022 3)) | 				       (make-cal-month 2023 4) #f #f)) | ||||||
| 		    (stop ,(make-cal-month 2022 10)) | 		    "" | ||||||
| 		    (start ,(make-cal-month 2023 1)) | 		    -1)) | ||||||
| 		    (stop ,(make-cal-month 2023 4)) |      (test-equal? period-markers->cal-periods-open | ||||||
| 		    (start ,(make-cal-month 2023 5)))) | 	          (period-markers->cal-periods | ||||||
| 		 `(#t | 		   `((start ,(make-cal-month 2022 3)) | ||||||
| 		   (,(make-cal-period (make-cal-month 2022 3) | 		     (stop ,(make-cal-month 2022 10)) | ||||||
| 				      (make-cal-month 2022 10) #f #f) | 		     (start ,(make-cal-month 2023 1)) | ||||||
| 		    ,(make-cal-period (make-cal-month 2023 1) | 		     (stop ,(make-cal-month 2023 4)) | ||||||
| 				      (make-cal-month 2023 4) #f #f) | 		     (start ,(make-cal-month 2023 5)))) | ||||||
| 		    ,(make-cal-period (make-cal-month 2023 5) #f #f #f)) | 		  `(#t | ||||||
| 		   "" | 		    (,(make-cal-period (make-cal-month 2022 3) | ||||||
| 		   -1)) | 				       (make-cal-month 2022 10) #f #f) | ||||||
|     (test-eq? cal-period->duration | 		     ,(make-cal-period (make-cal-month 2023 1) | ||||||
| 	      (cal-period->duration (make-cal-period (make-cal-month 2023 1) | 				       (make-cal-month 2023 4) #f #f) | ||||||
| 						     (make-cal-month 2023 4) #f #f)) | 		     ,(make-cal-period (make-cal-month 2023 5) #f #f #f)) | ||||||
| 	      3) | 		    "" | ||||||
|     (parameterize ((*current-month* (make-cal-month 2023 4))) | 		    -1)) | ||||||
|       (test-eq? cal-period->duration |      (test-eq? cal-period->duration | ||||||
| 		(cal-period->duration (make-cal-period (make-cal-month 2023 1) #f #f #f)) | 	       (cal-period->duration (make-cal-period (make-cal-month 2023 1) | ||||||
| 		3)) | 						      (make-cal-month 2023 4) #f #f)) | ||||||
|     (test-eq? cal-periods-duration | 	       3) | ||||||
| 	      (cal-periods-duration `(,(make-cal-period (make-cal-month 2022 3) |      (parameterize ((*current-month* (make-cal-month 2023 4))) | ||||||
| 							(make-cal-month 2022 10) #f #f) |        (test-eq? cal-period->duration | ||||||
| 				      ,(make-cal-period (make-cal-month 2023 1) | 		 (cal-period->duration (make-cal-period (make-cal-month 2023 1) #f #f #f)) | ||||||
| 							(make-cal-month 2023 4) #f #f))) | 		 3)) | ||||||
| 	      10) |      (test-eq? cal-periods-duration | ||||||
|     (test-true cal-month-in-period? | 	       (cal-periods-duration `(,(make-cal-period (make-cal-month 2022 3) | ||||||
| 	       (cal-month-in-period? (make-cal-period (make-cal-month 2022 1) | 							 (make-cal-month 2022 10) #f #f) | ||||||
| 						      (make-cal-month 2022 4) #f #f) | 				       ,(make-cal-period (make-cal-month 2023 1) | ||||||
| 				     (make-cal-month 2022 3))) | 							 (make-cal-month 2023 4) #f #f))) | ||||||
|     (test-false cal-month-in-period? | 	       10) | ||||||
|  |      (test-true cal-month-in-period? | ||||||
| 		(cal-month-in-period? (make-cal-period (make-cal-month 2022 1) | 		(cal-month-in-period? (make-cal-period (make-cal-month 2022 1) | ||||||
| 						       (make-cal-month 2022 4) #f #f) | 						       (make-cal-month 2022 4) #f #f) | ||||||
| 				      (make-cal-month 2022 5))) |  | ||||||
|     (test-true cal-month-in-periods? |  | ||||||
| 	       (cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) |  | ||||||
| 							  (make-cal-month 2022 4) #f #f) |  | ||||||
| 					,(make-cal-period (make-cal-month 2023 5) |  | ||||||
| 							  (make-cal-month 2023 10) #f #f)) |  | ||||||
| 				      (make-cal-month 2022 3))) | 				      (make-cal-month 2022 3))) | ||||||
|     (test-true cal-month-in-periods? |      (test-false cal-month-in-period? | ||||||
| 	       (cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) | 		 (cal-month-in-period? (make-cal-period (make-cal-month 2022 1) | ||||||
| 							  (make-cal-month 2022 4) #f #f) | 							(make-cal-month 2022 4) #f #f) | ||||||
| 					,(make-cal-period (make-cal-month 2023 5) | 				       (make-cal-month 2022 5))) | ||||||
| 							  (make-cal-month 2023 10) #f #f)) |      (test-true cal-month-in-periods? | ||||||
| 				      (make-cal-month 2023 7))) |  | ||||||
|     (test-false cal-month-in-periods? |  | ||||||
| 		(cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) | 		(cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) | ||||||
| 							   (make-cal-month 2022 4) #f #f) | 							   (make-cal-month 2022 4) #f #f) | ||||||
| 					 ,(make-cal-period (make-cal-month 2023 5) | 					 ,(make-cal-period (make-cal-month 2023 5) | ||||||
| 							   (make-cal-month 2023 10) #f #f)) | 							   (make-cal-month 2023 10) #f #f)) | ||||||
| 				       (make-cal-month 2022 10))) | 				       (make-cal-month 2022 3))) | ||||||
|     (test-equal? cal-period->string |      (test-true cal-month-in-periods? | ||||||
| 		 (cal-period->string (make-cal-period (make-cal-month 2022 1) | 		(cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) | ||||||
| 						      (make-cal-month 2022 4) #f #f)) | 							   (make-cal-month 2022 4) #f #f) | ||||||
| 		 "2022-01..2022-04") | 					 ,(make-cal-period (make-cal-month 2023 5) | ||||||
|     (test-equal? cal-periods->string | 							   (make-cal-month 2023 10) #f #f)) | ||||||
| 	         (cal-periods->string `(,(make-cal-period (make-cal-month 2022 1) | 				       (make-cal-month 2023 7))) | ||||||
| 							  (make-cal-month 2022 4) #f #f) |      (test-false cal-month-in-periods? | ||||||
| 					,(make-cal-period (make-cal-month 2022 12) | 		 (cal-month-in-periods? `(,(make-cal-period (make-cal-month 2022 1) | ||||||
| 							  (make-cal-month 2023 2) #f #f))) | 							    (make-cal-month 2022 4) #f #f) | ||||||
| 		 "2022-01..2022-04, 2022-12..2023-02") | 					  ,(make-cal-period (make-cal-month 2023 5) | ||||||
|     (test-false cal-periods-match | 							    (make-cal-month 2023 10) #f #f)) | ||||||
| 		(cal-periods-match `(,(make-cal-period (make-cal-month 2022 1) | 					(make-cal-month 2022 10))) | ||||||
| 						       (make-cal-month 2022 4) #f #f) |      (test-equal? cal-period->string | ||||||
| 				     ,(make-cal-period (make-cal-month 2022 12) | 		  (cal-period->string (make-cal-period (make-cal-month 2022 1) | ||||||
| 						       (make-cal-month 2023 2) #f #f)) | 						       (make-cal-month 2022 4) #f #f)) | ||||||
| 				   (make-cal-month 2022 5))) | 		  "2022-01..2022-04") | ||||||
|     (test-equal? cal-periods-match |      (test-equal? cal-periods->string | ||||||
|  | 	          (cal-periods->string `(,(make-cal-period (make-cal-month 2022 1) | ||||||
|  | 							   (make-cal-month 2022 4) #f #f) | ||||||
|  | 					 ,(make-cal-period (make-cal-month 2022 12) | ||||||
|  | 							   (make-cal-month 2023 2) #f #f))) | ||||||
|  | 		  "2022-01..2022-04, 2022-12..2023-02") | ||||||
|  |      (test-false cal-periods-match | ||||||
| 		 (cal-periods-match `(,(make-cal-period (make-cal-month 2022 1) | 		 (cal-periods-match `(,(make-cal-period (make-cal-month 2022 1) | ||||||
| 							(make-cal-month 2022 4) #f #f) | 							(make-cal-month 2022 4) #f #f) | ||||||
| 				      ,(make-cal-period (make-cal-month 2022 12) | 				      ,(make-cal-period (make-cal-month 2022 12) | ||||||
| 							(make-cal-month 2023 2) #f #f)) | 							(make-cal-month 2023 2) #f #f)) | ||||||
| 				    (make-cal-month 2022 2)) | 				    (make-cal-month 2022 5))) | ||||||
| 		 (make-cal-period (make-cal-month 2022 1) |      (test-equal? cal-periods-match | ||||||
| 				  (make-cal-month 2022 4) #f #f)) | 		  (cal-periods-match `(,(make-cal-period (make-cal-month 2022 1) | ||||||
|     )) | 							 (make-cal-month 2022 4) #f #f) | ||||||
|  | 				       ,(make-cal-period (make-cal-month 2022 12) | ||||||
|  | 							 (make-cal-month 2023 2) #f #f)) | ||||||
|  | 				     (make-cal-month 2022 2)) | ||||||
|  | 		  (make-cal-period (make-cal-month 2022 1) | ||||||
|  | 				   (make-cal-month 2022 4) #f #f)) | ||||||
|  |      )) | ||||||
| 
 | 
 | ||||||
|  ) |   ) | ||||||
|  |  | ||||||
							
								
								
									
										226
									
								
								src/export-sheet.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/export-sheet.scm
									
										
									
									
									
										Normal 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) | ||||||
|  |     '(("--" . "--{}--")))) | ||||||
|  | 
 | ||||||
|  |  ) | ||||||
|  | @ -45,7 +45,9 @@ | ||||||
| 	 util-git | 	 util-git | ||||||
| 	 configuration | 	 configuration | ||||||
| 	 texts | 	 texts | ||||||
| 	 logging) | 	 logging | ||||||
|  | 	 qr-payment | ||||||
|  | 	 members-fees) | ||||||
| 
 | 
 | ||||||
|  ;; HTML entities |  ;; HTML entities | ||||||
|  (define (sanitize-html str) |  (define (sanitize-html str) | ||||||
|  | @ -88,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>") | ||||||
|  | @ -109,6 +113,13 @@ | ||||||
|      (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 "<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>") | ||||||
|  |  | ||||||
|  | @ -51,7 +51,9 @@ | ||||||
| 	dokuwiki | 	dokuwiki | ||||||
| 	racket-kwargs | 	racket-kwargs | ||||||
| 	util-string | 	util-string | ||||||
| 	mailinglist) | 	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)) | ||||||
|  | @ -66,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 | ||||||
|  | @ -181,7 +184,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" | ||||||
|  | @ -354,6 +364,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 () | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								src/mailman2.scm
									
										
									
									
									
								
							
							
						
						
									
										104
									
								
								src/mailman2.scm
									
										
									
									
									
								
							|  | @ -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)))))) |  | ||||||
| 
 |  | ||||||
|  ) |  | ||||||
							
								
								
									
										123
									
								
								src/mbase-query.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/mbase-query.scm
									
										
									
									
									
										Normal 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)))))) | ||||||
|  | 
 | ||||||
|  |   ) | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -41,6 +41,7 @@ | ||||||
|   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 |   get-expected-income-string | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
|  | @ -208,6 +209,19 @@ | ||||||
| 	    (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) |  (define (get-expected-income-string mb) | ||||||
|    (let* ((flst |    (let* ((flst | ||||||
| 	   (map (compose member-calendar-entry->fee make-member-calendar-entry) | 	   (map (compose member-calendar-entry->fee make-member-calendar-entry) | ||||||
|  |  | ||||||
|  | @ -380,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) | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								src/qr-payment.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/qr-payment.scm
									
										
									
									
									
										Normal 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))) | ||||||
|  | 
 | ||||||
|  |   ) | ||||||
|  | @ -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.17 (c) 2023-2024 Brmlab, z.s.") |  (define banner-line "HackerBase 1.19-dev (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 " | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue