invoice 2025 init commit

This commit is contained in:
laurent roro 2025-04-26 17:33:27 +02:00
parent c2947f6dc8
commit 34b92d5f97
2 changed files with 373 additions and 0 deletions

117
2025/invoice_2025.typ Normal file
View File

@ -0,0 +1,117 @@
#import "invoice_template.typ": *
#let invoice_recip(name,vat,country,town,postal_code,address) = (
name: name,
adress: address,
postal_code: postal_code,
town: town,
country: country,
vat_number: vat,
)
// start at invoice number 0
#let invoice_data = invoice_enumerate(start:0,(
// festival Allemagne
(
date: datetime(day: 28, month: 2, year: 2025),
recipient: invoice_recip(
[Hochschule Flensburg],
none,
[Germany],[Flensburg],[D-24943],[Kanzleistraße 91-93]
),
invoice: (
invoice_thing(eur(40.00))[poong screening],
),
bank: bank_wise_eur,
recu:true, //eur(40.00),
tag:("poong","screening",)
),
// festival Allemagne
(
date: datetime(day: 24, month: 4, year: 2025),
recipient: invoice_recip(
[PrevYou],
none,
[Germany],[Berlin],[],[]
),
invoice: (
invoice_thing(eur(75.00))[poong screening],
),
bank:bank_wise_eur,
recu:false, // chf(0),
tag:("poong","screening",)
),
// festival Lausanne
(
date: datetime(day: 24, month: 4, year: 2025),
recipient: invoice_recip(
[BDFIL],
none,
[Switzerland],[Lausanne],[1005],[Place de la Cathédrale 12]
),
invoice: (
invoice_thing(chf(75.00))[poong screening],
),
bank:bank_wise_chf,
recu:false, // chf(0),
tag:("poong","screening",)
),
// BaseCourt
(
date: datetime(day: 22, month: 4, year: 2025),
recipient: invoice_recip(
[Base court],
none,
[Switzerland],[Lausanne],[1006],[Av. de la Rasude 2]
),
invoice: (
invoice_thing(chf(60.00))[poong screening cinétransat],
invoice_thing(chf(60.00))[poong screening],
invoice_thing(chf(60.00))[poong screening],
),
bank:bank_wise_chf,
recu:false, // chf(0),
tag:("poong","screening",),
),
// Salades
(
date: datetime(day: 24, month: 4, year: 2025),
recipient: invoice_recip(
[TODO],
none,
[Switzerland],[TODO],[TODO],[TODO]
),
invoice: (
invoice_thing(chf(200.00))[animation kickstarter Salades],
),
bank:bank_wise_chf,
recu:false, // chf(0),
),
// UNIGE
(
date: datetime(day: 24, month: 4, year: 2025),
recipient: invoice_recip(
[UNIGE],
none,
[Switzerland],[Geneva],[],[]
),
invoice: (
invoice_thing(chf(1100.00))[animation],
),
bank:bank_wise_chf,
recu:false, // chf(0),
),
))
#for i in invoice_data {invoice_render(i)}
#invoice_render_bilan(invoice_data)
// #invoice_data.map( i => invoice_render(i))
// #invoice_render(invoice_data.last())

256
2025/invoice_template.typ Normal file
View File

@ -0,0 +1,256 @@
#let format_invoice_num(num, pre:4) = {
let a = calc.max(0,pre - str(num).len())
return a*"0" + str(num)
}
#let format_num(num, decimal: ".", thousands: "'") = {
let parts = str(num).split(".")
let decimal_part = if parts.len() == 2 { parts.at(1) }
let integer_part = parts.at(0).rev().clusters().enumerate()
.map((item) => {
let (index, value) = item
return value + if calc.rem(index, 3) == 0 and index != 0 {
thousands
}
}).rev().join("")
// TODO
return integer_part + if decimal_part != none { decimal + decimal_part }
}
#let translations = (
français: (
sender: "ÉMETTEUR",
recipient: "DESTINATAIRE",
invoice: "FACTURE",
invoice_number: "Facture :",
date: "Date :",
due_date: "Échéance :",
description: "Description",
amount: "Montant",
total: "Total :",
vat_notice: "Association non soumise à la TVA selon l'Art. 10 LTVA",
payment_terms: "Cette facture est payable dans les 30 jours suivant sa réception.",
banking_info: "COORDONNÉES BANCAIRES",
bank: "Banque :",
account_holder: "Titulaire :",
account_notice: "Compte au nom d'un membre de l'association",
association_notice: "Association à but non lucratif non inscrite au registre du commerce",
),
deutsch: (
sender: "ABSENDER",
recipient: "EMPFÄNGER",
invoice: "RECHNUNG",
invoice_number: "Rechnungs-Nr.:",
date: "Datum:",
due_date: "Fälligkeitsdatum:",
description: "Beschreibung",
amount: "Betrag",
total: "Gesamtbetrag:",
vat_notice: "Verein nicht mehrwertsteuerpflichtig gemäß Art. 10 MWSTG",
payment_terms: "Diese Rechnung ist innerhalb von 30 Tagen nach Erhalt zu bezahlen.",
banking_info: "BANKVERBINDUNG",
bank: "Bank:",
account_holder: "Kontoinhaber:",
account_notice: "Konto auf den Namen eines Vereinsmitglieds",
association_notice: "Gemeinnütziger Verein, nicht im Handelsregister eingetragen",
),
english: (
sender: "FROM",
recipient: "TO",
invoice: "INVOICE",
invoice_number: "Invoice No.:",
date: "Date:",
due_date: "Due date:",
description: "Description",
amount: "Amount",
total: "Total:",
vat_notice: "Association not subject to VAT according to Art. 10 VAT Act",
payment_terms: "This invoice is payable within 30 days of receipt.",
banking_info: "BANKING DETAILS",
bank: "Bank:",
account_holder: "Account holder:",
account_notice: "Account in the name of an association member",
association_notice: "Non-profit association not registered in the commercial register",
),
)
#let bank_wise_eur = [
#let t = translations.at("english", default: translations.français)
#grid(
columns:(auto, auto),
align:(right,left),
column-gutter: 1em,
row-gutter: .5em,
[#t.bank],[ Wise, Rue du Trône 100, 3rd floor, Brussels, 1050, Belgium],
[IBAN:],[*BE22905094540247*],
[BIC/SWIFT:],[ TRWIBEB1XXX],
[#t.account_holder],[ Robin Szymczak],
[],[#t.account_notice],
)
// #t.bank Wise, Rue du Trône 100, 3rd floor, Brussels, 1050, Belgium\
// IBAN: BE22905094540247\
// BIC/SWIFT: TRWIBEB1XXX\
// #t.account_holder Robin Szymczak\
]
#let bank_wise_chf = [
#let t = translations.at("english", default: translations.français)
#grid(
columns:(auto, auto),
align:(right,left),
column-gutter: 1em,
row-gutter: .5em,
[#t.bank],[ Wise Payments Limited, 1st Floor, Worship Square, 65 Clifton Street, London, EC2A 4JE, United Kingdom ],
[IBAN:],[*GB51TRWI23080140876698*],
[BIC/SWIFT:],[ TRWIGB2LXXX ],
[#t.account_holder],[ Robin Szymczak ],
[],[#t.account_notice],
)
]
// on stock en centimes, et on arrondit
#let chf(value) = (CHF:calc.round(value*100)/100)
#let eur(value) = (EUR:calc.round(value*100)/100)
#let usd(value) = (USD:calc.round(value*100)/100)
// #let money(value,currency) = (value:decimal(value),currency:currency)
// #let money_add(m1,m2) = if m1.currency != m2.currency : fail
#let money_render(m) = {
let cs = m.keys().sorted().map(currency => {
let value = m.at(currency)
let a = int(value)
let b1 = int(100*(value - a))
let b0 = if b1 < 10 [0] else []
let b = [#b0#b1]
let c = currency
return [#format_num(a)\.#b #c]
})
cs.join([ \+ ])
}
#let invoice_thing(price,description) = (price:price,description:description)
#let invoice_enumerate(start:0, invoice_data) = invoice_data.enumerate(start:start).map(e => {
e.at(1).invoice_number=format_invoice_num(e.at(0))
e.at(1)
})
#let invoice_render(
invoice,
language: "english", // Options: "français", "deutsch", "english"
) = [
#set page(
footer: align(center)[
#line(length: 100%, stroke: 0.5pt + gray)
#text(size:9pt)[Cheap Motion Pictures - 1 rue de l'Ale - 1003 Lausanne - contact\@cheapmo.ch]
],
)
#set text(font: "Arial", size: 11pt)
// Sélection du dictionnaire en fonction de la langue
#let t = translations.at(language, default: translations.français)
// Titre et informations de la facture
#grid(
columns: (auto,1fr, 1fr),
gutter:10pt,
image("cheapmo-logo.png",width:60pt),
align(left)[
#text(weight: "bold", size: 14pt)[Cheap Motion Pictures ]\
#text(style: "italic")[#t.association_notice]
],
align(right)[
#t.invoice_number *2025-#(invoice.invoice_number)*\
#t.date #(invoice.date ).display("[day]/[month]/[year]")\
#t.due_date *#(invoice.date + duration(days: 30)).display("[day]/[month]/[year]")*\
],
)
#v(15pt)
#grid(
columns: (1fr, 1fr),
// Informations de l'émetteur
align(left)[
#par(text(weight: "bold", size: 14pt)[#t.sender])
Cheap Motion Pictures\
Rue de l'Ale 1\
1003 Lausanne, Suisse\
Tél: +41 77 471 11 34\
Email: contact\@cheapmo.ch
],
// Informations du destinataire
align(right)[
#par(text(weight: "bold", size: 14pt)[#t.recipient])
#invoice.recipient.name\
#invoice.recipient.adress\
#invoice.recipient.postal_code #invoice.recipient.town, #invoice.recipient.country
#if invoice.recipient.vat_number != none [VAT: #invoice.recipient.vat_number] else []
],
)
#let things = invoice.invoice.fold((),(l,e)=>(..l,e.description,money_render(e.price)))
#let total = money_render(invoice.invoice.fold((:),(l,e)=> {
for currency in e.price.keys() {
l.insert(currency, l.at(currency,default:0) + e.price.at(currency))
}
return l
}))
#v(15pt)
#table(
columns: (1fr, auto),
inset: (x:10pt,y:8pt),
align: (left, right),
table.header(
table.cell(fill:rgb(245, 245, 245))[*#t.description*],
table.cell(fill:rgb(245, 245, 245))[*#t.amount* ],
),
..things,
table.cell(fill:silver,align:right)[TOTAL],
table.cell(fill:silver,align:right)[*#total*]
)
#v(15pt)
// Informations complémentaires
#t.vat_notice \
#t.payment_terms
// Coordonnées bancaires avec notification spéciale
= #t.banking_info
#invoice.bank
#if invoice.recu {
place(horizon+center,rotate(-60deg,text(fill:red.transparentize(50%),size:300pt,[PAID]),reflow:true))
}
]
#let invoice_render_bilan(invoices) = {
let content = ()
for invoice in invoices {
let total = money_render(invoice.invoice.fold((:),(l,e)=> {
for currency in e.price.keys() {
l.insert(currency, l.at(currency,default:0) + e.price.at(currency))
}
return l
}))
content.push(invoice.recipient.name)
content.push([2025-#invoice.invoice_number])
content.push([#total])
content.push([])
// #table(
// [#i.recipient.name],
// [#i.invoice],
// )
}
table(
columns: (1fr, auto, auto, auto),
align: (auto, auto, right, right),
[*name*],[*invoice*],[*amount*],[*received*],
..content
)
}