Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
|
|
|
import { initCommissionCalculator } from '../../assets/modules/commission-calculator.js'
|
|
|
|
|
|
|
|
|
|
describe('initCommissionCalculator', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
document.body.innerHTML = ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does nothing without calculator element', () => {
|
|
|
|
|
expect(() => initCommissionCalculator()).not.toThrow()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('does nothing without price input', () => {
|
|
|
|
|
document.body.innerHTML = '<div id="commission-calculator" data-eticket-rate="5" data-stripe-rate="1.5" data-stripe-fixed="0.25"></div>'
|
|
|
|
|
expect(() => initCommissionCalculator()).not.toThrow()
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-21 12:28:30 +01:00
|
|
|
it('uses default stripe rate and fixed when not provided', () => {
|
|
|
|
|
document.body.innerHTML = `
|
|
|
|
|
<input type="number" id="billet_price" value="10">
|
|
|
|
|
<div id="commission-calculator" data-eticket-rate="0">
|
|
|
|
|
<span id="calc-price"></span>
|
|
|
|
|
<span id="calc-eticket"></span>
|
|
|
|
|
<span id="calc-stripe"></span>
|
|
|
|
|
<span id="calc-total"></span>
|
|
|
|
|
<span id="calc-net"></span>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
initCommissionCalculator()
|
|
|
|
|
|
|
|
|
|
// Default stripe: 10 * 1.5% + 0.25 = 0.40
|
|
|
|
|
expect(document.getElementById('calc-stripe').textContent).toBe('- 0,40 \u20AC')
|
|
|
|
|
})
|
|
|
|
|
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
function setupCalculator(eticketRate = '5', price = '') {
|
|
|
|
|
document.body.innerHTML = `
|
|
|
|
|
<input type="number" id="billet_price" value="${price}">
|
|
|
|
|
<div id="commission-calculator" data-eticket-rate="${eticketRate}" data-stripe-rate="1.5" data-stripe-fixed="0.25">
|
|
|
|
|
<span id="calc-price"></span>
|
|
|
|
|
<span id="calc-eticket"></span>
|
|
|
|
|
<span id="calc-stripe"></span>
|
|
|
|
|
<span id="calc-total"></span>
|
|
|
|
|
<span id="calc-net"></span>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
initCommissionCalculator()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('shows zero values when price is empty', () => {
|
|
|
|
|
setupCalculator('5', '')
|
|
|
|
|
|
|
|
|
|
expect(document.getElementById('calc-price').textContent).toBe('0,00 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-net').textContent).toBe('0,00 \u20AC')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calculates commissions for 10 EUR with 5% eticket rate', () => {
|
|
|
|
|
setupCalculator('5', '10')
|
|
|
|
|
|
|
|
|
|
// E-Ticket: 10 * 5% = 0.50
|
|
|
|
|
// Stripe: 10 * 1.5% + 0.25 = 0.40
|
|
|
|
|
// Total: 0.90
|
|
|
|
|
// Net: 9.10
|
|
|
|
|
expect(document.getElementById('calc-price').textContent).toBe('10,00 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-eticket').textContent).toBe('- 0,50 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-stripe').textContent).toBe('- 0,40 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-total').textContent).toBe('- 0,90 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-net').textContent).toBe('9,10 \u20AC')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('calculates with 0% eticket rate', () => {
|
|
|
|
|
setupCalculator('0', '20')
|
|
|
|
|
|
|
|
|
|
// E-Ticket: 0
|
|
|
|
|
// Stripe: 20 * 1.5% + 0.25 = 0.55
|
|
|
|
|
// Net: 19.45
|
|
|
|
|
expect(document.getElementById('calc-eticket').textContent).toBe('- 0,00 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-stripe').textContent).toBe('- 0,55 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-net').textContent).toBe('19,45 \u20AC')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('updates on input event', () => {
|
|
|
|
|
setupCalculator('5', '0')
|
|
|
|
|
|
|
|
|
|
const input = document.getElementById('billet_price')
|
|
|
|
|
input.value = '15'
|
|
|
|
|
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
|
|
|
|
|
|
|
|
// E-Ticket: 15 * 5% = 0.75
|
|
|
|
|
// Stripe: 15 * 1.5% + 0.25 = 0.475 → 0.48
|
|
|
|
|
// Total: 1.225 → 1.23 (but floating point...)
|
|
|
|
|
expect(document.getElementById('calc-price').textContent).toBe('15,00 \u20AC')
|
|
|
|
|
expect(document.getElementById('calc-eticket').textContent).toBe('- 0,75 \u20AC')
|
|
|
|
|
|
|
|
|
|
const net = document.getElementById('calc-net').textContent
|
|
|
|
|
expect(net).toContain('\u20AC')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('net is never negative', () => {
|
|
|
|
|
setupCalculator('99', '0.01')
|
|
|
|
|
|
|
|
|
|
// With 99% commission the net would be very low or negative after stripe
|
|
|
|
|
const net = document.getElementById('calc-net').textContent
|
|
|
|
|
expect(net).toBe('0,00 \u20AC')
|
|
|
|
|
})
|
|
|
|
|
})
|