mirror of
https://github.com/pretix/pretix.git
synced 2026-03-26 16:22:28 +00:00
Compare commits
21 Commits
questions-
...
py313
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bb13d591b | ||
|
|
4fed47fb9b | ||
|
|
c143d50290 | ||
|
|
88cd715ece | ||
|
|
3513de6a45 | ||
|
|
fd6d3934c0 | ||
|
|
222b453b43 | ||
|
|
617a0f5dc7 | ||
|
|
12f53ec2c3 | ||
|
|
5449285624 | ||
|
|
68bf7d44f2 | ||
|
|
a31db20804 | ||
|
|
1bd08cf3aa | ||
|
|
fbea13227f | ||
|
|
b3ff32d345 | ||
|
|
ed0611253e | ||
|
|
41af5fae17 | ||
|
|
42f61d74fa | ||
|
|
4923e0be31 | ||
|
|
e63bc09216 | ||
|
|
f8bbb3d3bb |
@@ -1,6 +1,5 @@
|
||||
doc/
|
||||
env/
|
||||
node_modules/
|
||||
res/
|
||||
local/
|
||||
.git/
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Packaging
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
python-version: ["3.13"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
@@ -46,7 +46,4 @@ jobs:
|
||||
- name: Run build
|
||||
run: python -m build
|
||||
- name: Check files
|
||||
run: |
|
||||
for pat in 'static.dist/vite/widget/widget.js' 'static.dist/vite/control/assets/checkinrules/main-' 'static.dist/vite/control/assets/webcheckin/main-'; do
|
||||
unzip -l dist/pretix*whl | grep -q "$pat" || { echo "Missing: $pat"; exit 1; }
|
||||
done
|
||||
run: unzip -l dist/pretix*whl | grep node_modules || exit 1
|
||||
|
||||
8
.github/workflows/strings.yml
vendored
8
.github/workflows/strings.yml
vendored
@@ -24,10 +24,10 @@ jobs:
|
||||
name: Check gettext syntax
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -49,10 +49,10 @@ jobs:
|
||||
name: Spellcheck
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
12
.github/workflows/style.yml
vendored
12
.github/workflows/style.yml
vendored
@@ -24,10 +24,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -44,10 +44,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -64,10 +64,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
python-version: 3.13
|
||||
- name: Install Dependencies
|
||||
run: pip3 install licenseheaders
|
||||
- name: Run licenseheaders
|
||||
|
||||
51
.github/workflows/tests.yml
vendored
51
.github/workflows/tests.yml
vendored
@@ -23,13 +23,15 @@ jobs:
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.13"]
|
||||
python-version: ["3.11", "3.13", "3.14"]
|
||||
database: [sqlite, postgres]
|
||||
exclude:
|
||||
- database: sqlite
|
||||
python-version: "3.10"
|
||||
- database: sqlite
|
||||
python-version: "3.11"
|
||||
- database: sqlite
|
||||
python-version: "3.12"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
@@ -70,7 +72,7 @@ jobs:
|
||||
run: make all compress
|
||||
- name: Run tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --ignore=tests/e2e --maxfail=100
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --maxfail=100
|
||||
- name: Run concurrency tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reuse-db
|
||||
@@ -81,47 +83,4 @@ jobs:
|
||||
file: src/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
if: matrix.database == 'postgres' && matrix.python-version == '3.11'
|
||||
e2e:
|
||||
runs-on: ubuntu-22.04
|
||||
name: E2E Tests
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: pretix
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U postgres -d pretix"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y gettext
|
||||
- name: Install Python dependencies
|
||||
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
|
||||
- name: Install JS dependencies
|
||||
working-directory: ./src
|
||||
run: make npminstall
|
||||
- name: Compile
|
||||
working-directory: ./src
|
||||
run: make all compress
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install
|
||||
- name: Run E2E tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10
|
||||
if: matrix.database == 'postgres' && matrix.python-version == '3.13'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,5 @@ local/
|
||||
.project
|
||||
.pydevproject
|
||||
.DS_Store
|
||||
node_modules/
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/*
|
||||
12
Dockerfile
12
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11-bookworm
|
||||
FROM python:3.13-trixie
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
@@ -20,11 +20,11 @@ RUN apt-get update && \
|
||||
supervisor \
|
||||
libmaxminddb0 \
|
||||
libmaxminddb-dev \
|
||||
zlib1g-dev && \
|
||||
zlib1g-dev \
|
||||
nodejs \
|
||||
npm && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
dpkg-reconfigure locales && \
|
||||
locale-gen C.UTF-8 && \
|
||||
/usr/sbin/update-locale LANG=C.UTF-8 && \
|
||||
@@ -49,10 +49,6 @@ COPY deployment/docker/production_settings.py /pretix/src/production_settings.py
|
||||
COPY pyproject.toml /pretix/pyproject.toml
|
||||
COPY _build /pretix/_build
|
||||
COPY src /pretix/src
|
||||
COPY package.json /pretix/package.json
|
||||
COPY package-lock.json /pretix/package-lock.json
|
||||
COPY tsconfig.json /pretix/tsconfig.json
|
||||
COPY vite.config.ts /pretix/vite.config.ts
|
||||
|
||||
RUN pip3 install -U \
|
||||
pip \
|
||||
|
||||
@@ -48,8 +48,3 @@ recursive-include src Makefile
|
||||
recursive-exclude doc *
|
||||
recursive-exclude deployment *
|
||||
recursive-exclude res *
|
||||
|
||||
include package.json
|
||||
include package-lock.json
|
||||
include tsconfig.json
|
||||
include vite.config.ts
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import js from '@eslint/js'
|
||||
import ts from 'typescript-eslint'
|
||||
import stylistic from '@stylistic/eslint-plugin'
|
||||
import vue from 'eslint-plugin-vue'
|
||||
import vuePug from 'eslint-plugin-vue-pug'
|
||||
|
||||
const ignores = globalIgnores([
|
||||
'**/node_modules',
|
||||
'**/dist'
|
||||
])
|
||||
|
||||
export default defineConfig([
|
||||
ignores,
|
||||
...ts.config(
|
||||
js.configs.recommended,
|
||||
ts.configs.recommended
|
||||
),
|
||||
stylistic.configs.customize({
|
||||
indent: 'tab',
|
||||
braceStyle: '1tbs',
|
||||
quoteProps: 'as-needed'
|
||||
}),
|
||||
...vue.configs['flat/recommended'],
|
||||
...vuePug.configs['flat/recommended'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
localStorage: false,
|
||||
$: 'readonly',
|
||||
$$: 'readonly',
|
||||
$ref: 'readonly',
|
||||
$computed: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
},
|
||||
|
||||
rules: {
|
||||
'no-debugger': 'off',
|
||||
curly: 0,
|
||||
'no-return-assign': 0,
|
||||
'no-console': 'off',
|
||||
'vue/require-default-prop': 0,
|
||||
'vue/require-v-for-key': 0,
|
||||
'vue/valid-v-for': 'warn',
|
||||
'vue/no-reserved-keys': 0,
|
||||
'vue/no-setup-props-destructure': 0,
|
||||
'vue/multi-word-component-names': 0,
|
||||
'vue/max-attributes-per-line': 0,
|
||||
'vue/attribute-hyphenation': ['warn', 'never'],
|
||||
'vue/v-on-event-hyphenation': ['warn', 'never'],
|
||||
'import/first': 0,
|
||||
'@typescript-eslint/ban-ts-comment': 0,
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'no-use-before-define': 'off',
|
||||
'no-var': 'error',
|
||||
|
||||
'@typescript-eslint/no-use-before-define': ['error', {
|
||||
typedefs: false,
|
||||
functions: false,
|
||||
}],
|
||||
|
||||
'@typescript-eslint/no-unused-vars': ['error', {
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
caughtErrors: 'all',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true
|
||||
}],
|
||||
|
||||
'@stylistic/comma-dangle': 0,
|
||||
'@stylistic/space-before-function-paren': ['error', 'always'],
|
||||
'@stylistic/max-statements-per-line': ['error', { max: 1, ignoredNodes: ['BreakStatement'] }],
|
||||
'@stylistic/member-delimiter-style': 0,
|
||||
'@stylistic/arrow-parens': 0,
|
||||
'@stylistic/generator-star-spacing': 0,
|
||||
'@stylistic/yield-star-spacing': ['error', 'after'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'src/pretix/static/pretixcontrol/js/ui/checkinrules/**/*.vue',
|
||||
'src/pretix/plugins/webcheckin/**/*.vue',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
moment: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'src/pretix/static/pretixpresale/widget/**/*.{ts,vue}',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
LANG: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
4784
package-lock.json
generated
4784
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"name": "pretix",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"homepage": "https://github.com/pretix/pretix#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pretix/pretix/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pretix/pretix.git"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"doc": "doc"
|
||||
},
|
||||
"scripts": {
|
||||
"dev:control": "vite",
|
||||
"dev:widget": "vite src/pretix/static/pretixpresale/widget",
|
||||
"build": "npm run build:control -s && npm run build:widget -s",
|
||||
"build:control": "vite build",
|
||||
"build:widget": "vite build src/pretix/static/pretixpresale/widget",
|
||||
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/moment": "^2.11.29",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"@vue/eslint-config-typescript": "^14.7.0",
|
||||
"@vue/language-plugin-pug": "^3.2.5",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
|
||||
"globals": "^17.4.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass-embedded": "^1.98.0",
|
||||
"stylus": "^0.64.0",
|
||||
"typescript-eslint": "^8.57.0",
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ name = "pretix"
|
||||
dynamic = ["version"]
|
||||
description = "Reinventing presales, one ticket at a time"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
license = {file = "LICENSE"}
|
||||
keywords = ["tickets", "web", "shop", "ecommerce"]
|
||||
authors = [
|
||||
@@ -19,10 +19,11 @@ classifiers = [
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
"Environment :: Web Environment",
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Framework :: Django :: 4.2",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Framework :: Django :: 5.2",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
@@ -36,7 +37,7 @@ dependencies = [
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==4.2.*,>=4.2.26",
|
||||
"Django[argon2]==5.2.*",
|
||||
"django-bootstrap3==26.1",
|
||||
"django-compressor==4.6.0",
|
||||
"django-countries==8.2.*",
|
||||
@@ -59,7 +60,7 @@ dependencies = [
|
||||
"dnspython==2.8.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
"geoip2==5.*",
|
||||
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||
"importlib_metadata==9.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||
"isoweek",
|
||||
"jsonschema",
|
||||
"kombu==5.6.*",
|
||||
@@ -123,7 +124,6 @@ dev = [
|
||||
"pytest-mock==3.15.*",
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.8.*",
|
||||
"pytest-playwright",
|
||||
"pytest==9.0.*",
|
||||
"responses",
|
||||
]
|
||||
|
||||
@@ -37,9 +37,4 @@ ignore =
|
||||
CONTRIBUTING.md
|
||||
Dockerfile
|
||||
SECURITY.md
|
||||
eslint.config.mjs
|
||||
package-lock.json
|
||||
package.json
|
||||
tsconfig.json
|
||||
vite.config.js
|
||||
|
||||
|
||||
12
src/Makefile
12
src/Makefile
@@ -9,10 +9,10 @@ localegen:
|
||||
./manage.py makemessages --keep-pot --ignore "pretix/static/npm_dir/*" $(LNGS)
|
||||
./manage.py makemessages --keep-pot -d djangojs --ignore "pretix/static/npm_dir/*" --ignore "pretix/helpers/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static.dist/*" --ignore "data/*" --ignore "pretix/static/rrule/*" --ignore "build/*" $(LNGS)
|
||||
|
||||
staticfiles: npminstall npmbuild jsi18n
|
||||
staticfiles: jsi18n
|
||||
./manage.py collectstatic --noinput
|
||||
|
||||
compress:
|
||||
compress: npminstall
|
||||
./manage.py compress
|
||||
|
||||
jsi18n: localecompile
|
||||
@@ -25,8 +25,8 @@ coverage:
|
||||
coverage run -m py.test
|
||||
|
||||
npminstall:
|
||||
npm ci
|
||||
|
||||
npmbuild:
|
||||
npm run build
|
||||
# keep this in sync with pretix/_build.py!
|
||||
mkdir -p pretix/static.dist/node_prefix/
|
||||
cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/
|
||||
npm ci --prefix=pretix/static.dist/node_prefix
|
||||
|
||||
|
||||
@@ -37,11 +37,9 @@ INSTALLED_APPS = [
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.humanize',
|
||||
# pretix needs to go before staticfiles
|
||||
# so we can override the runserver command
|
||||
'pretix.base',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'pretix.base',
|
||||
'pretix.control',
|
||||
'pretix.presale',
|
||||
'pretix.multidomain',
|
||||
@@ -245,6 +243,7 @@ STORAGES = {
|
||||
|
||||
COMPRESS_PRECOMPILERS = (
|
||||
('text/x-scss', 'django_libsass.SassCompiler'),
|
||||
('text/vue', 'pretix.helpers.compressor.VueCompiler'),
|
||||
)
|
||||
|
||||
COMPRESS_OFFLINE_CONTEXT = {
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from setuptools.command.build import build
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
project_root = os.path.abspath(os.path.join(here, '..', '..'))
|
||||
npm_installed = False
|
||||
|
||||
|
||||
@@ -35,14 +35,14 @@ def npm_install():
|
||||
global npm_installed
|
||||
|
||||
if not npm_installed:
|
||||
subprocess.check_call('npm ci', shell=True, cwd=project_root)
|
||||
# keep this in sync with Makefile!
|
||||
node_prefix = os.path.join(here, 'static.dist', 'node_prefix')
|
||||
os.makedirs(node_prefix, exist_ok=True)
|
||||
shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True)
|
||||
subprocess.check_call('npm ci', shell=True, cwd=node_prefix)
|
||||
npm_installed = True
|
||||
|
||||
|
||||
def npm_build():
|
||||
subprocess.check_call('npm run build', shell=True, cwd=project_root)
|
||||
|
||||
|
||||
class CustomBuild(build):
|
||||
def run(self):
|
||||
if "src" not in os.listdir(".") or "pretix" not in os.listdir("src"):
|
||||
@@ -62,7 +62,6 @@ class CustomBuild(build):
|
||||
settings.COMPRESS_OFFLINE = True
|
||||
|
||||
npm_install()
|
||||
npm_build()
|
||||
management.call_command('compilemessages', verbosity=1)
|
||||
management.call_command('compilejsi18n', verbosity=1)
|
||||
management.call_command('collectstatic', verbosity=1, interactive=False)
|
||||
|
||||
@@ -47,5 +47,3 @@ HAS_MEMCACHED = False
|
||||
HAS_CELERY = False
|
||||
HAS_GEOIP = False
|
||||
SENTRY_ENABLED = False
|
||||
VITE_DEV_MODE = False
|
||||
VITE_IGNORE = False
|
||||
|
||||
@@ -51,7 +51,6 @@ from pretix.base.models import (
|
||||
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
SalesChannel,
|
||||
)
|
||||
from pretix.base.models.items import Questionnaire, QuestionnaireChild
|
||||
|
||||
|
||||
class InlineItemVariationSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
||||
@@ -625,160 +624,6 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
return question
|
||||
|
||||
|
||||
class QuestionRefField(serializers.PrimaryKeyRelatedField):
|
||||
def to_representation(self, qc):
|
||||
if not qc:
|
||||
return None
|
||||
elif qc.system_question:
|
||||
return qc.system_question
|
||||
elif qc.user_question_id:
|
||||
return qc.user_question_id
|
||||
else:
|
||||
return None
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if type(data) == int:
|
||||
return {'user_question': super().to_internal_value(data), 'system_question': None}
|
||||
elif type(data) == str or data is None:
|
||||
return {'user_question': None, 'system_question': data}
|
||||
else:
|
||||
self.fail('incorrect_type', data_type=type(data).__name__)
|
||||
|
||||
def use_pk_only_optimization(self):
|
||||
return self.source == '*'
|
||||
|
||||
|
||||
class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer):
|
||||
question = QuestionRefField(source='*', queryset=Question.objects.none())
|
||||
dependency_question = QuestionRefField(allow_null=True, required=False, queryset=Question.objects.none())
|
||||
|
||||
class Meta:
|
||||
model = QuestionnaireChild
|
||||
fields = ('question', 'required', 'label', 'help_text', 'dependency_question', 'dependency_values')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["question"].queryset = self.context["event"].questions.all()
|
||||
self.fields["dependency_question"].queryset = self.context["event"].questions.all()
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
event = self.context['event']
|
||||
|
||||
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||
full_data.update(data)
|
||||
|
||||
if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
|
||||
raise ValidationError('Dependencies are not supported during check-in.')
|
||||
|
||||
dep = full_data.get('dependency_question')
|
||||
if dep:
|
||||
if dep.ask_during_checkin:
|
||||
raise ValidationError(_('Question cannot depend on a question asked during check-in.'))
|
||||
|
||||
seen_ids = {self.instance.pk} if self.instance else set()
|
||||
while dep:
|
||||
if dep.pk in seen_ids:
|
||||
raise ValidationError(_('Circular dependency between questions detected.'))
|
||||
seen_ids.add(dep.pk)
|
||||
dep = dep.dependency_question
|
||||
|
||||
return data
|
||||
|
||||
def validate_dependency_question(self, value):
|
||||
if value:
|
||||
if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
|
||||
raise ValidationError('Question dependencies can only be set to boolean or choice questions.')
|
||||
if value == self.instance:
|
||||
raise ValidationError('A question cannot depend on itself.')
|
||||
return value
|
||||
|
||||
|
||||
class QuestionnaireSerializer(I18nAwareModelSerializer):
|
||||
limit_sales_channels = serializers.SlugRelatedField(
|
||||
slug_field="identifier",
|
||||
queryset=SalesChannel.objects.none(),
|
||||
required=False,
|
||||
allow_empty=True,
|
||||
many=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Questionnaire
|
||||
fields = ('id', 'type', 'internal_name', 'items', 'position', 'all_sales_channels', 'limit_sales_channels', 'children')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.fields['children'] = InlineQuestionnaireChildSerializer(many=True, required=True, context=kwargs['context'], partial=False)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
event = self.context['event']
|
||||
|
||||
#full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
|
||||
#full_data.update(data)
|
||||
|
||||
#if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
|
||||
# raise ValidationError('Dependencies are not supported during check-in.')
|
||||
|
||||
#if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
|
||||
# raise ValidationError(_('This type of question cannot be asked during check-in.'))
|
||||
|
||||
#if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
|
||||
# raise ValidationError(_('This type of question cannot be shown during check-in.'))
|
||||
|
||||
#Question.clean_items(event, full_data.get('items') or [])
|
||||
return data
|
||||
|
||||
def validate_children(self, value):
|
||||
prev_questions = {}
|
||||
for child in value:
|
||||
if child.get('dependency_question'):
|
||||
if (child['dependency_question']['user_question'] or child['dependency_question']['system_question']) not in prev_questions:
|
||||
raise ValidationError('A question can only depend on a previous question from the same questionnaire.')
|
||||
|
||||
if child['user_question']:
|
||||
prev_questions[child['user_question']] = child
|
||||
if child['system_question']:
|
||||
prev_questions[child['system_question']] = child
|
||||
return value
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
children_data = validated_data.pop('children') if 'children' in validated_data else []
|
||||
questionnaire = super().create(validated_data)
|
||||
self.set_children(questionnaire, children_data)
|
||||
return questionnaire
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
children_data = validated_data.pop('children', None)
|
||||
questionnaire = super().update(instance, validated_data)
|
||||
if children_data is not None:
|
||||
self.set_children(questionnaire, children_data)
|
||||
return questionnaire
|
||||
|
||||
def set_children(self, questionnaire, new_data):
|
||||
result = []
|
||||
child_serializer = self.fields['children'].child
|
||||
existing = questionnaire.children.all()
|
||||
for i, d in enumerate(new_data):
|
||||
d['questionnaire'] = questionnaire
|
||||
d['position'] = i + 1
|
||||
d.setdefault('required', False)
|
||||
d.setdefault('help_text', None)
|
||||
d.setdefault('dependency_question', None)
|
||||
d.setdefault('dependency_values', None)
|
||||
updatable = min(len(existing), len(new_data))
|
||||
for i in range(0, updatable):
|
||||
result.append(child_serializer.update(existing[i], new_data[i]))
|
||||
for i in range(updatable, len(new_data)):
|
||||
result.append(child_serializer.create(new_data[i]))
|
||||
for i in range(updatable, len(existing)):
|
||||
existing[i].delete()
|
||||
return result
|
||||
|
||||
|
||||
class QuotaSerializer(I18nAwareModelSerializer):
|
||||
available = serializers.BooleanField(read_only=True)
|
||||
available_number = serializers.IntegerField(read_only=True)
|
||||
|
||||
@@ -79,8 +79,7 @@ event_router.register(r'subevents', event.SubEventViewSet)
|
||||
event_router.register(r'clone', event.CloneEventViewSet)
|
||||
event_router.register(r'items', item.ItemViewSet)
|
||||
event_router.register(r'categories', item.ItemCategoryViewSet)
|
||||
event_router.register(r'datafields', item.QuestionViewSet)
|
||||
event_router.register(r'questionnaires', item.QuestionnaireViewSet)
|
||||
event_router.register(r'questions', item.QuestionViewSet)
|
||||
event_router.register(r'discounts', discount.DiscountViewSet)
|
||||
event_router.register(r'quotas', item.QuotaViewSet)
|
||||
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
||||
|
||||
@@ -47,14 +47,13 @@ from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
|
||||
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer,
|
||||
QuestionOptionSerializer, QuestionSerializer, QuestionnaireSerializer, QuotaSerializer,
|
||||
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer,
|
||||
)
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime,
|
||||
ItemVariation, Question, QuestionOption, Quota,
|
||||
)
|
||||
from pretix.base.models.items import Questionnaire
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.helpers.i18n import i18ncomp
|
||||
@@ -539,51 +538,6 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class QuestionnaireViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = QuestionnaireSerializer
|
||||
queryset = Questionnaire.objects.none()
|
||||
#filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
#filterset_class = QuestionFilter
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position', 'id')
|
||||
permission = None
|
||||
write_permission = 'event.items:write'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.questionnaires.prefetch_related('children').all()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.questionnaire.added',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.questionnaire.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.event.questionnaire.deleted',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
|
||||
class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
|
||||
pass
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import pycountry
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.gis.geoip2 import GeoIP2
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.validators import (
|
||||
@@ -102,6 +101,7 @@ from pretix.helpers.countries import (
|
||||
from pretix.helpers.escapejson import escapejson_attr
|
||||
from pretix.helpers.http import get_client_ip
|
||||
from pretix.helpers.i18n import get_format_without_seconds
|
||||
from pretix.helpers.security import get_geoip
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -393,7 +393,7 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
|
||||
|
||||
def guess_country_from_request(request, event):
|
||||
if settings.HAS_GEOIP:
|
||||
g = GeoIP2()
|
||||
g = get_geoip()
|
||||
try:
|
||||
res = g.country(get_client_ip(request))
|
||||
if res['country_code'] and len(res['country_code']) == 2:
|
||||
|
||||
@@ -36,8 +36,9 @@ from django.core.management.commands.makemigrations import Command as Parent
|
||||
|
||||
from ._migrations import monkeypatch_migrations
|
||||
|
||||
monkeypatch_migrations()
|
||||
|
||||
|
||||
class Command(Parent):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
monkeypatch_migrations()
|
||||
return super().handle(*args, **kwargs)
|
||||
|
||||
@@ -64,7 +64,7 @@ class Command(BaseCommand):
|
||||
if not periodic_task.receivers or periodic_task.sender_receivers_cache.get(self) is NO_RECEIVERS:
|
||||
return
|
||||
|
||||
for receiver in periodic_task._live_receivers(self):
|
||||
for receiver in periodic_task._live_receivers(self)[0]:
|
||||
name = f'{receiver.__module__}.{receiver.__name__}'
|
||||
if options['list_tasks']:
|
||||
print(name)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""This command supersedes the Django-inbuilt runserver command.
|
||||
|
||||
It runs the local frontend server, if node is installed and the setting
|
||||
is set.
|
||||
"""
|
||||
import atexit
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.management.commands.runserver import (
|
||||
Command as Parent,
|
||||
)
|
||||
from django.utils.autoreload import DJANGO_AUTORELOAD_ENV
|
||||
|
||||
|
||||
class Command(Parent):
|
||||
def handle(self, *args, **options):
|
||||
# Only start Vite in the non-main process of the autoreloader
|
||||
if settings.VITE_DEV_MODE and os.environ.get(DJANGO_AUTORELOAD_ENV) != "true":
|
||||
# Start the vite server in the background
|
||||
vite_server = subprocess.Popen(
|
||||
["npm", "run", "dev:control"],
|
||||
cwd=Path(__file__).parent.parent.parent.parent.parent
|
||||
)
|
||||
|
||||
def cleanup():
|
||||
vite_server.terminate()
|
||||
try:
|
||||
vite_server.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
vite_server.kill()
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
super().handle(*args, **options)
|
||||
@@ -280,11 +280,11 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
|
||||
h = {
|
||||
'default-src': ["{static}"],
|
||||
'script-src': ["{static}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
||||
'script-src': ['{static}'],
|
||||
'object-src': ["'none'"],
|
||||
'frame-src': ['{static}'],
|
||||
'style-src': ["{static}", "{media}"] + (["'unsafe-inline'"] if settings.VITE_DEV_MODE else []),
|
||||
'connect-src': ["{dynamic}", "{media}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
|
||||
'style-src': ["{static}", "{media}"],
|
||||
'connect-src': ["{dynamic}", "{media}"],
|
||||
'img-src': ["{static}", "{media}", "data:"] + img_src,
|
||||
'font-src': ["{static}"] + list(font_src),
|
||||
'media-src': ["{static}", "data:"],
|
||||
|
||||
@@ -41,16 +41,20 @@ class Migration(migrations.Migration):
|
||||
name='datetime',
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='logentry',
|
||||
index_together={('datetime', 'id')},
|
||||
migrations.AddIndex(
|
||||
'logentry',
|
||||
models.Index(fields=('datetime', 'id'), name="pretixbase__datetim_b1fe5a_idx"),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='order',
|
||||
index_together={('datetime', 'id'), ('last_modified', 'id')},
|
||||
migrations.AddIndex(
|
||||
'order',
|
||||
models.Index(fields=["datetime", "id"], name="pretixbase__datetim_66aff0_idx"),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='transaction',
|
||||
index_together={('datetime', 'id')},
|
||||
migrations.AddIndex(
|
||||
'order',
|
||||
models.Index(fields=["last_modified", "id"], name="pretixbase__last_mo_4ebf8b_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
'transaction',
|
||||
models.Index(fields=('datetime', 'id'), name="pretixbase__datetim_b20405_idx"),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -61,7 +61,10 @@ class Migration(migrations.Migration):
|
||||
options={
|
||||
'ordering': ('identifier', 'type', 'organizer'),
|
||||
'unique_together': {('identifier', 'type', 'organizer')},
|
||||
'index_together': {('identifier', 'type', 'organizer'), ('updated', 'id')},
|
||||
'indexes': [
|
||||
models.Index(fields=('identifier', 'type', 'organizer'), name='reusable_medium_organizer_index'),
|
||||
models.Index(fields=('updated', 'id'), name="pretixbase__updated_093277_idx")
|
||||
],
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
|
||||
@@ -9,31 +9,6 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameIndex(
|
||||
model_name="logentry",
|
||||
new_name="pretixbase__datetim_b1fe5a_idx",
|
||||
old_fields=("datetime", "id"),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name="order",
|
||||
new_name="pretixbase__datetim_66aff0_idx",
|
||||
old_fields=("datetime", "id"),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name="order",
|
||||
new_name="pretixbase__last_mo_4ebf8b_idx",
|
||||
old_fields=("last_modified", "id"),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name="reusablemedium",
|
||||
new_name="pretixbase__updated_093277_idx",
|
||||
old_fields=("updated", "id"),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name="transaction",
|
||||
new_name="pretixbase__datetim_b20405_idx",
|
||||
old_fields=("datetime", "id"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="attendeeprofile",
|
||||
name="id",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Generated by Django 4.2.10 on 2024-04-02 15:16
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -10,8 +10,8 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name="reusablemedium",
|
||||
index_together=set(),
|
||||
migrations.RemoveIndex(
|
||||
"reusablemedium",
|
||||
'reusable_medium_organizer_index',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-19 14:24
|
||||
import json
|
||||
from collections import namedtuple
|
||||
from itertools import chain, groupby
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import i18nfield.fields
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
import pretix.base.models.base
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
FakeQuestion = namedtuple(
|
||||
'FakeQuestion', 'id question position required'
|
||||
)
|
||||
|
||||
|
||||
def get_fake_questions(settings):
|
||||
def b(s):
|
||||
return s == 'True'
|
||||
fq = []
|
||||
sqo = json.loads(settings.get('system_question_order', '{}'))
|
||||
_ = LazyI18nString.from_gettext
|
||||
|
||||
if b(settings.get('attendee_names_asked', 'True')):
|
||||
fq.append(FakeQuestion('attendee_name_parts', _('Attendee name'), sqo.get('attendee_name_parts', 0), b(settings.get('attendee_names_required'))))
|
||||
|
||||
if b(settings.get('attendee_emails_asked')):
|
||||
fq.append(FakeQuestion('attendee_email', _('Attendee email'), sqo.get('attendee_email', 0), b(settings.get('attendee_emails_required'))))
|
||||
|
||||
if b(settings.get('attendee_company_asked')):
|
||||
fq.append(FakeQuestion('company', _('Company'), sqo.get('company', 0), b(settings.get('attendee_company_required'))))
|
||||
|
||||
if b(settings.get('attendee_addresses_asked')):
|
||||
fq.append(FakeQuestion('street', _('Street'), sqo.get('street', 0), b(settings.get('attendee_addresses_required'))))
|
||||
fq.append(FakeQuestion('zipcode', _('ZIP code'), sqo.get('zipcode', 0), b(settings.get('attendee_addresses_required'))))
|
||||
fq.append(FakeQuestion('city', _('City'), sqo.get('city', 0), b(settings.get('attendee_addresses_required'))))
|
||||
fq.append(FakeQuestion('country', _('Country'), sqo.get('country', 0), b(settings.get('attendee_addresses_required'))))
|
||||
return fq
|
||||
|
||||
|
||||
def migrate_questions_forward(apps, schema_editor):
|
||||
Event = apps.get_model("pretixbase", "Event")
|
||||
Item = apps.get_model("pretixbase", "Item")
|
||||
Question = apps.get_model("pretixbase", "Question")
|
||||
Questionnaire = apps.get_model("pretixbase", "Questionnaire")
|
||||
QuestionnaireChild = apps.get_model("pretixbase", "QuestionnaireChild")
|
||||
EventSettingsStore = apps.get_model('pretixbase', 'Event_SettingsStore')
|
||||
|
||||
for event in Event.objects.iterator():
|
||||
# get relevant settings
|
||||
settings = {
|
||||
setting.key: setting.value for setting in EventSettingsStore.objects.filter(object_id=event.id, key__in=(
|
||||
'system_question_order', 'attendee_names_asked', 'attendee_names_required', 'attendee_emails_asked', 'attendee_emails_required',
|
||||
'attendee_company_asked', 'attendee_company_required', 'attendee_addresses_asked', 'attendee_addresses_required',
|
||||
))
|
||||
}
|
||||
|
||||
# get all questions (user-defined and system provided), along with the products for which they're asked
|
||||
questions = event.questions.all()
|
||||
children = sorted(chain((
|
||||
(item, q.position, q.id, q)
|
||||
for q in questions
|
||||
for item in q.items.values_list('id', 'internal_name', 'name')
|
||||
), (
|
||||
(item, q.position, None, q)
|
||||
for q in get_fake_questions(settings)
|
||||
for item in event.items.filter(personalized=True).values_list('id', 'internal_name', 'name')
|
||||
)), key=lambda t: (t[0], t[1], t[2]))
|
||||
|
||||
# group by item, creating a unique questionnaire per item
|
||||
item_questionnaires = (([t[3] for t in children], item_id) for item_id, children in groupby(children, key=lambda t: t[0]))
|
||||
|
||||
# group again, merging all questionnaires with identical children
|
||||
merged_questionnaires = groupby(sorted(item_questionnaires, key=lambda t: [q.id for q in t[0]]), key=lambda t: t[0])
|
||||
for children, iterator in merged_questionnaires:
|
||||
items = [item for _c, item in iterator]
|
||||
|
||||
# create questionnaires and children
|
||||
questionnaire = Questionnaire.objects.create(
|
||||
event=event, type='PS', position=0, all_sales_channels=True,
|
||||
internal_name=', '.join(str(iname or name) for (id, iname, name) in items)
|
||||
)
|
||||
questionnaire.items.set([id for (id, iname, name) in items])
|
||||
deps = {}
|
||||
for position, child in enumerate(children):
|
||||
if isinstance(child, FakeQuestion):
|
||||
QuestionnaireChild.objects.create(
|
||||
questionnaire=questionnaire,
|
||||
position=position + 1,
|
||||
system_question=child.id,
|
||||
required=child.required,
|
||||
label=child.question,
|
||||
)
|
||||
else:
|
||||
deps[child.id] = QuestionnaireChild.objects.create(
|
||||
questionnaire=questionnaire,
|
||||
position=position + 1,
|
||||
user_question=child,
|
||||
required=child.required,
|
||||
label=child.question,
|
||||
help_text=child.help_text,
|
||||
dependency_question=deps[child.dependency_question.id] if child.dependency_question else None,
|
||||
dependency_values=child.dependency_values,
|
||||
)
|
||||
|
||||
|
||||
def migrate_questions_backward(apps, schema_editor):
|
||||
pass # as long as we don't delete the old columns, this is a no op. after that, it gets complicated...
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0298_pluggable_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Questionnaire',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('internal_name', models.CharField(max_length=255)),
|
||||
('type', models.CharField(max_length=5)),
|
||||
('position', models.PositiveIntegerField(default=0)),
|
||||
('all_sales_channels', models.BooleanField(default=True)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questionnaires', to='pretixbase.event')),
|
||||
('items', models.ManyToManyField(related_name='questionnaires', to='pretixbase.item')),
|
||||
('limit_sales_channels', models.ManyToManyField(to='pretixbase.saleschannel')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionnaireChild',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('position', models.PositiveIntegerField(default=0)),
|
||||
('system_question', models.CharField(max_length=25, null=True)),
|
||||
('required', models.BooleanField(default=False)),
|
||||
('label', i18nfield.fields.I18nTextField()),
|
||||
('help_text', i18nfield.fields.I18nTextField(null=True)),
|
||||
('dependency_values', pretix.base.models.fields.MultiStringField(default=[])),
|
||||
('dependency_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dependent_questions', to='pretixbase.questionnairechild')),
|
||||
('questionnaire', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pretixbase.questionnaire')),
|
||||
('user_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='references', to='pretixbase.question')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.RunPython(
|
||||
migrate_questions_forward,
|
||||
migrate_questions_backward,
|
||||
),
|
||||
# TODO remove old columns from Question model
|
||||
]
|
||||
@@ -1595,12 +1595,10 @@ class ItemBundle(models.Model):
|
||||
|
||||
class Question(LoggedModel):
|
||||
"""
|
||||
A question is a data field that can be used to extend an order or a ticket by custom
|
||||
information, e.g. "Attendee age". To be actually useful, questions need to be added to
|
||||
one or multiple Questionnaires. The answers may be found in QuestionAnswers, attached
|
||||
to Orders, OrderPositions or CartPositions.
|
||||
|
||||
A question can allow one of several input types, currently:
|
||||
A question is an input field that can be used to extend a ticket by custom information,
|
||||
e.g. "Attendee age". The answers are found next to the position. The answers may be found
|
||||
in QuestionAnswers, attached to OrderPositions/CartPositions. A question can allow one of
|
||||
several input types, currently:
|
||||
|
||||
* a number (``TYPE_NUMBER``)
|
||||
* a one-line string (``TYPE_STRING``)
|
||||
@@ -1669,7 +1667,7 @@ class Question(LoggedModel):
|
||||
related_name="questions",
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
question = I18nTextField( # to be renamed to 'internal_name'
|
||||
question = I18nTextField(
|
||||
verbose_name=_("Question")
|
||||
)
|
||||
identifier = models.CharField(
|
||||
@@ -1684,7 +1682,7 @@ class Question(LoggedModel):
|
||||
),
|
||||
],
|
||||
)
|
||||
help_text = I18nTextField( # to be removed
|
||||
help_text = I18nTextField(
|
||||
verbose_name=_("Help text"),
|
||||
help_text=_("If the question needs to be explained or clarified, do it here!"),
|
||||
null=True, blank=True,
|
||||
@@ -1694,22 +1692,22 @@ class Question(LoggedModel):
|
||||
choices=TYPE_CHOICES,
|
||||
verbose_name=_("Question type")
|
||||
)
|
||||
required = models.BooleanField( # to be removed, -> QuestionnaireChild
|
||||
required = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("Required question")
|
||||
)
|
||||
items = models.ManyToManyField( # to be removed, -> Questionnaire
|
||||
items = models.ManyToManyField(
|
||||
Item,
|
||||
related_name='questions',
|
||||
verbose_name=_("Products"),
|
||||
blank=True,
|
||||
help_text=_('This question will be asked to buyers of the selected products')
|
||||
)
|
||||
position = models.PositiveIntegerField( # to be removed, -> Questionnaire + QuestionnaireChild
|
||||
position = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name=_("Position")
|
||||
)
|
||||
ask_during_checkin = models.BooleanField( # to be removed
|
||||
ask_during_checkin = models.BooleanField(
|
||||
verbose_name=_('Ask during check-in instead of in the ticket buying process'),
|
||||
help_text=_('Not supported by all check-in apps for all question types.'),
|
||||
default=False
|
||||
@@ -1719,7 +1717,7 @@ class Question(LoggedModel):
|
||||
help_text=_('Not supported by all check-in apps for all question types.'),
|
||||
default=False
|
||||
)
|
||||
hidden = models.BooleanField( # to be removed
|
||||
hidden = models.BooleanField(
|
||||
verbose_name=_('Hidden question'),
|
||||
help_text=_('This question will only show up in the backend.'),
|
||||
default=False
|
||||
@@ -1728,10 +1726,10 @@ class Question(LoggedModel):
|
||||
verbose_name=_('Print answer on invoices'),
|
||||
default=False
|
||||
)
|
||||
dependency_question = models.ForeignKey( # to be removed, -> QuestionnaireChild
|
||||
dependency_question = models.ForeignKey(
|
||||
'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
|
||||
)
|
||||
dependency_values = MultiStringField(default=[]) # to be removed, -> QuestionnaireChild
|
||||
dependency_values = MultiStringField(default=[])
|
||||
valid_number_min = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
|
||||
verbose_name=_('Minimum value'),
|
||||
help_text=_('Currently not supported in our apps and during check-in'))
|
||||
@@ -1765,9 +1763,9 @@ class Question(LoggedModel):
|
||||
objects = ScopedManager(organizer='event__organizer')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Data field")
|
||||
verbose_name_plural = _("Data fields")
|
||||
ordering = ('question', 'id')
|
||||
verbose_name = _("Question")
|
||||
verbose_name_plural = _("Questions")
|
||||
ordering = ('position', 'id')
|
||||
unique_together = (('event', 'identifier'),)
|
||||
|
||||
def __str__(self):
|
||||
@@ -1992,103 +1990,6 @@ class QuestionOption(models.Model):
|
||||
ordering = ('position', 'id')
|
||||
|
||||
|
||||
class Questionnaire(LoggedModel):
|
||||
TYPE_ORDER_SALE = "OS"
|
||||
TYPE_ORDER_POSITION_SALE = "PS"
|
||||
TYPE_ORDER_POSITION_ATTENDEE_ONLY = "PA"
|
||||
TYPE_ORDER_POSITION_CHECKIN = "PC"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_ORDER_SALE, _("Order-wide, before purchase")),
|
||||
(TYPE_ORDER_POSITION_SALE, _("Per product, before purchase")),
|
||||
(TYPE_ORDER_POSITION_ATTENDEE_ONLY, _("Per product, via attendee link")),
|
||||
(TYPE_ORDER_POSITION_CHECKIN, _("Per product, at check-in")),
|
||||
)
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
related_name="questionnaires",
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
internal_name = models.CharField(
|
||||
verbose_name=_("Internal name"),
|
||||
max_length=255,
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=5,
|
||||
choices=TYPE_CHOICES,
|
||||
verbose_name=_("Questionnaire type")
|
||||
)
|
||||
items = models.ManyToManyField(
|
||||
Item,
|
||||
related_name='questionnaires',
|
||||
verbose_name=_("Products"),
|
||||
blank=True,
|
||||
help_text=_('This questionnaire will be asked to buyers of the selected products')
|
||||
)
|
||||
position = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name=_("Position")
|
||||
)
|
||||
all_sales_channels = models.BooleanField(
|
||||
verbose_name=_("Sell on all sales channels the product is sold on"),
|
||||
default=True,
|
||||
)
|
||||
limit_sales_channels = models.ManyToManyField(
|
||||
"SalesChannel",
|
||||
verbose_name=_("Restrict to specific sales channels"),
|
||||
help_text=_('The sales channel selection for the product as a whole takes precedence, so if a sales channel is '
|
||||
'selected here but not on product level, the variation will not be available.'),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
|
||||
class QuestionnaireChild(LoggedModel):
|
||||
SYSTEM_QUESTION_CHOICES = (
|
||||
('attendee_name_parts', _('Attendee name')),
|
||||
('attendee_email', _('Attendee email')),
|
||||
('company', _('Company')),
|
||||
('street', _('Street')),
|
||||
('zipcode', _('ZIP code')),
|
||||
('city', _('City')),
|
||||
('country', _('Country')),
|
||||
)
|
||||
questionnaire = models.ForeignKey(
|
||||
Questionnaire,
|
||||
related_name="children",
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
position = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name=_("Position")
|
||||
)
|
||||
user_question = models.ForeignKey(
|
||||
Question,
|
||||
related_name="references",
|
||||
on_delete=models.CASCADE,
|
||||
null=True, blank=True,
|
||||
)
|
||||
system_question = models.CharField(
|
||||
max_length=25,
|
||||
choices=SYSTEM_QUESTION_CHOICES,
|
||||
null=True, blank=True,
|
||||
)
|
||||
required = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("Required question")
|
||||
)
|
||||
label = I18nTextField(
|
||||
verbose_name=_("Question")
|
||||
)
|
||||
help_text = I18nTextField(
|
||||
verbose_name=_("Help text"),
|
||||
help_text=_("If the question needs to be explained or clarified, do it here!"),
|
||||
null=True, blank=True,
|
||||
)
|
||||
dependency_question = models.ForeignKey(
|
||||
'QuestionnaireChild', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
|
||||
)
|
||||
dependency_values = MultiStringField(default=[])
|
||||
|
||||
|
||||
class Quota(LoggedModel):
|
||||
"""
|
||||
A quota is a "pool of tickets". It is there to limit the number of items
|
||||
|
||||
@@ -88,7 +88,7 @@ class LogEntry(models.Model):
|
||||
|
||||
class Meta:
|
||||
ordering = ('-datetime', '-id')
|
||||
indexes = [models.Index(fields=["datetime", "id"])]
|
||||
indexes = [models.Index(fields=["datetime", "id"], name="pretixbase__datetim_b1fe5a_idx")]
|
||||
|
||||
def display(self):
|
||||
from pretix.base.logentrytype_registry import log_entry_types
|
||||
|
||||
@@ -122,7 +122,7 @@ class ReusableMedium(LoggedModel):
|
||||
class Meta:
|
||||
unique_together = (("identifier", "type", "organizer"),)
|
||||
indexes = [
|
||||
models.Index(fields=("updated", "id")),
|
||||
models.Index(fields=("updated", "id"), name="pretixbase__updated_093277_idx"),
|
||||
]
|
||||
ordering = "identifier", "type", "organizer"
|
||||
|
||||
|
||||
@@ -336,8 +336,8 @@ class Order(LockModel, LoggedModel):
|
||||
verbose_name_plural = _("Orders")
|
||||
ordering = ("-datetime", "-pk")
|
||||
indexes = [
|
||||
models.Index(fields=["datetime", "id"]),
|
||||
models.Index(fields=["last_modified", "id"]),
|
||||
models.Index(fields=["datetime", "id"], name="pretixbase__datetim_66aff0_idx"),
|
||||
models.Index(fields=["last_modified", "id"], name="pretixbase__last_mo_4ebf8b_idx"),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["organizer", "code"], name="order_organizer_code_uniq"),
|
||||
@@ -3080,7 +3080,7 @@ class Transaction(models.Model):
|
||||
class Meta:
|
||||
ordering = 'datetime', 'pk'
|
||||
indexes = [
|
||||
models.Index(fields=['datetime', 'id'])
|
||||
models.Index(fields=['datetime', 'id'], name="pretixbase__datetim_b20405_idx")
|
||||
]
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,6 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Any, Callable, Generic, List, Tuple, TypeVar
|
||||
|
||||
@@ -48,6 +49,8 @@ from .plugins import (
|
||||
PLUGIN_LEVEL_ORGANIZER,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app_cache = {}
|
||||
T = TypeVar('T')
|
||||
|
||||
@@ -60,23 +63,25 @@ def _populate_app_cache():
|
||||
|
||||
def get_defining_app(o):
|
||||
# If sentry packed this in a wrapper, unpack that
|
||||
if "sentry" in o.__module__:
|
||||
module = getattr(o, "__module__", None)
|
||||
if module and "sentry" in module:
|
||||
o = o.__wrapped__
|
||||
|
||||
if hasattr(o, "__mocked_app"):
|
||||
return o.__mocked_app
|
||||
|
||||
# Find the Django application this belongs to
|
||||
searchpath = o.__module__
|
||||
searchpath = module or getattr(o.__class__, "__module__", None) or ""
|
||||
|
||||
# Core modules are always active
|
||||
if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
|
||||
if searchpath and any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
|
||||
return 'CORE'
|
||||
|
||||
if not app_cache:
|
||||
_populate_app_cache()
|
||||
|
||||
while True:
|
||||
app = None
|
||||
while searchpath:
|
||||
app = app_cache.get(searchpath)
|
||||
if "." not in searchpath or app:
|
||||
break
|
||||
@@ -157,7 +162,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
|
||||
if not app_cache:
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
for receiver in self._live_receivers(sender)[0]:
|
||||
if self._is_receiver_active(sender, receiver):
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
responses.append((receiver, response))
|
||||
@@ -179,7 +184,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
|
||||
if not app_cache:
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
for receiver in self._live_receivers(sender)[0]:
|
||||
if self._is_receiver_active(sender, receiver):
|
||||
named[chain_kwarg_name] = response
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
@@ -204,7 +209,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
|
||||
if not app_cache:
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
for receiver in self._live_receivers(sender)[0]:
|
||||
if self._is_receiver_active(sender, receiver):
|
||||
try:
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
@@ -214,17 +219,35 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
|
||||
def _sorted_receivers(self, sender):
|
||||
orig_list = self._live_receivers(sender)
|
||||
def asend(self, sender: T, **named):
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def asend_robust(self, sender: T, **named):
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def _live_receivers(self, sender):
|
||||
orig_list, orig_async_list = super()._live_receivers(sender)
|
||||
|
||||
if orig_async_list:
|
||||
logger.error('Async receivers are not supported.')
|
||||
raise NotImplementedError
|
||||
|
||||
def _getattr_fallback_to_class(obj, key):
|
||||
return getattr(obj, key, getattr(obj.__class__, key))
|
||||
|
||||
def _is_core_module(receiver):
|
||||
m = _getattr_fallback_to_class(receiver, "__module__")
|
||||
return any(m.startswith(c) for c in settings.CORE_MODULES)
|
||||
|
||||
sorted_list = sorted(
|
||||
orig_list,
|
||||
key=lambda receiver: (
|
||||
0 if any(receiver.__module__.startswith(m) for m in settings.CORE_MODULES) else 1,
|
||||
receiver.__module__,
|
||||
receiver.__name__,
|
||||
0 if _is_core_module(receiver) else 1,
|
||||
_getattr_fallback_to_class(receiver, "__module__"),
|
||||
_getattr_fallback_to_class(receiver, "__name__"),
|
||||
)
|
||||
)
|
||||
return sorted_list
|
||||
return sorted_list, []
|
||||
|
||||
|
||||
class EventPluginSignal(PluginSignal[Event]):
|
||||
@@ -300,23 +323,41 @@ class GlobalSignal(django.dispatch.Signal):
|
||||
if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
|
||||
return response
|
||||
|
||||
for receiver in self._live_receivers(sender):
|
||||
for receiver in self._live_receivers(sender)[0]:
|
||||
named[chain_kwarg_name] = response
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
return response
|
||||
|
||||
def asend(self, sender: T, **named):
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def asend_robust(self, sender: T, **named):
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
def _live_receivers(self, sender):
|
||||
# Ensure consistent sorting of receivers
|
||||
orig_list = super()._live_receivers(sender)
|
||||
orig_list, orig_async_list = super()._live_receivers(sender)
|
||||
|
||||
if orig_async_list:
|
||||
logger.error('Async receivers are not supported.')
|
||||
raise NotImplementedError
|
||||
|
||||
def _getattr_fallback_to_class(obj, key):
|
||||
return getattr(obj, key, getattr(obj.__class__, key))
|
||||
|
||||
def _is_core_module(receiver):
|
||||
m = _getattr_fallback_to_class(receiver, "__module__")
|
||||
return any(m.startswith(c) for c in settings.CORE_MODULES)
|
||||
|
||||
sorted_list = sorted(
|
||||
orig_list,
|
||||
key=lambda receiver: (
|
||||
0 if any(receiver.__module__.startswith(m) for m in settings.CORE_MODULES) else 1,
|
||||
receiver.__module__,
|
||||
receiver.__name__,
|
||||
0 if _is_core_module(receiver) else 1,
|
||||
_getattr_fallback_to_class(receiver, "__module__"),
|
||||
_getattr_fallback_to_class(receiver, "__name__"),
|
||||
)
|
||||
)
|
||||
return sorted_list
|
||||
return sorted_list, []
|
||||
|
||||
|
||||
class DeprecatedSignal(GlobalSignal):
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import json
|
||||
import logging
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
_MANIFEST = {}
|
||||
# TODO more os.path.join ?
|
||||
MANIFEST_PATH = settings.STATIC_ROOT + "/vite/control/.vite/manifest.json"
|
||||
MANIFEST_BASE = "vite/control/"
|
||||
|
||||
# We're building the manifest if we don't have a dev server running AND if we're
|
||||
# not currently running `rebuild` (which creates the manifest in the first place).
|
||||
if not settings.VITE_DEV_MODE and not settings.VITE_IGNORE:
|
||||
try:
|
||||
with open(MANIFEST_PATH) as fp:
|
||||
_MANIFEST = json.load(fp)
|
||||
except Exception as e:
|
||||
LOGGER.warning(f"Error reading vite manifest at {MANIFEST_PATH}: {str(e)}")
|
||||
|
||||
|
||||
def generate_script_tag(path, attrs):
|
||||
all_attrs = " ".join(f'{key}="{value}"' for key, value in attrs.items())
|
||||
if settings.VITE_DEV_MODE:
|
||||
src = urljoin(settings.VITE_DEV_SERVER, path)
|
||||
else:
|
||||
src = urljoin(settings.STATIC_URL, path)
|
||||
return f'<script {all_attrs} src="{src}"></script>'
|
||||
|
||||
|
||||
def generate_css_tags(asset, already_processed=None):
|
||||
"""Recursively builds all CSS tags used in a given asset.
|
||||
|
||||
Ignore the side effects."""
|
||||
tags = []
|
||||
manifest_entry = _MANIFEST[asset]
|
||||
if already_processed is None:
|
||||
already_processed = []
|
||||
|
||||
# Put our own CSS file first for specificity
|
||||
if "css" in manifest_entry:
|
||||
for css_path in manifest_entry["css"]:
|
||||
if css_path not in already_processed:
|
||||
full_path = urljoin(settings.STATIC_URL, MANIFEST_BASE + css_path)
|
||||
tags.append(f'<link rel="stylesheet" href="{full_path}" />')
|
||||
already_processed.append(css_path)
|
||||
|
||||
# Import each file only one by way of side effects in already_processed
|
||||
if "imports" in manifest_entry:
|
||||
for import_path in manifest_entry["imports"]:
|
||||
tags += generate_css_tags(import_path, already_processed)
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
@mark_safe
|
||||
def vite_asset(path):
|
||||
"""
|
||||
Generates one <script> tag and <link> tags for each of the CSS dependencies.
|
||||
"""
|
||||
|
||||
if not path:
|
||||
return ""
|
||||
|
||||
if settings.VITE_DEV_MODE:
|
||||
return generate_script_tag(path, {"type": "module"})
|
||||
|
||||
manifest_entry = _MANIFEST.get(path)
|
||||
if not manifest_entry:
|
||||
raise RuntimeError(f"Cannot find {path} in Vite manifest at {MANIFEST_PATH}")
|
||||
|
||||
tags = generate_css_tags(path)
|
||||
tags.append(
|
||||
generate_script_tag(
|
||||
MANIFEST_BASE + manifest_entry["file"], {"type": "module", "crossorigin": ""}
|
||||
)
|
||||
)
|
||||
return "".join(tags)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
@mark_safe
|
||||
def vite_hmr():
|
||||
if not settings.VITE_DEV_MODE:
|
||||
return ""
|
||||
return generate_script_tag("@vite/client", {"type": "module"})
|
||||
@@ -63,7 +63,7 @@ from pretix.base.forms import (
|
||||
from pretix.base.models import Event, Organizer, TaxRule, Team
|
||||
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
|
||||
from pretix.base.models.organizer import TeamQuerySet
|
||||
from pretix.base.models.tax import TAX_CODE_LISTS
|
||||
from pretix.base.models.tax import TAX_CODE_LISTS, VAT_ID_COUNTRIES
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.base.services.placeholders import FormPlaceholderMixin
|
||||
from pretix.base.settings import (
|
||||
@@ -531,6 +531,13 @@ class EventUpdateForm(I18nModelForm):
|
||||
|
||||
class EventSettingsValidationMixin:
|
||||
|
||||
def clean_invoice_address_from_vat_id(self):
|
||||
value = self.cleaned_data.get('invoice_address_from_vat_id')
|
||||
country = self.cleaned_data.get('invoice_address_from_country')
|
||||
if value and country and country not in VAT_ID_COUNTRIES:
|
||||
return None
|
||||
return value
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
settings_dict = self.obj.settings.freeze()
|
||||
|
||||
@@ -104,12 +104,6 @@ class GlobalSettingsForm(SettingsForm):
|
||||
help_text=_("Will be served at {domain}/.well-known/apple-developer-merchantid-domain-association").format(
|
||||
domain=settings.SITE_URL
|
||||
)
|
||||
)),
|
||||
('widget_vite_origins', forms.CharField(
|
||||
widget=forms.Textarea(attrs={'rows': '3'}),
|
||||
required=False,
|
||||
label=_("Vite widget origins"),
|
||||
help_text=_("One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget."),
|
||||
))
|
||||
])
|
||||
responses = register_global_settings.send(self)
|
||||
|
||||
@@ -850,9 +850,6 @@ class OrganizerPluginStateLogEntryType(LogEntryType):
|
||||
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
|
||||
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
|
||||
'pretix.event.question.option.changed': _('An answer option has been changed.'),
|
||||
'pretix.event.questionnaire.added': _('A questionnaire has been created.'),
|
||||
'pretix.event.questionnaire.deleted': _('A questionnaire has been deleted.'),
|
||||
'pretix.event.questionnaire.changed': _('A questionnaire has been changed.'),
|
||||
'pretix.event.permissions.added': _('A user has been added to the event team.'),
|
||||
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
|
||||
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),
|
||||
|
||||
@@ -182,12 +182,12 @@ def get_event_navigation(request: HttpRequest):
|
||||
'active': 'event.items.categories' in url.url_name,
|
||||
},
|
||||
{
|
||||
'label': _('Questionnaires'),
|
||||
'url': reverse('control:event.items.questionnaires', kwargs={
|
||||
'label': _('Questions'),
|
||||
'url': reverse('control:event.items.questions', kwargs={
|
||||
'event': request.event.slug,
|
||||
'organizer': request.event.organizer.slug,
|
||||
}),
|
||||
'active': 'event.items.questionnaires' in url.url_name or 'event.items.questions' in url.url_name,
|
||||
'active': 'event.items.questions' in url.url_name,
|
||||
},
|
||||
{
|
||||
'label': _('Discounts'),
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
{% load bootstrap3 %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
{% load vite %}
|
||||
{% block title %}
|
||||
{% if checkinlist %}
|
||||
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
|
||||
@@ -75,8 +74,45 @@
|
||||
{% bootstrap_field form.ignore_in_statistics layout="control" %}
|
||||
|
||||
<h3>{% trans "Custom check-in rule" %}</h3>
|
||||
<div id="rules-editor">
|
||||
<!-- Vue app mount point -->
|
||||
<div id="rules-editor" class="form-inline">
|
||||
<div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a href="#rules-edit" role="tab" data-toggle="tab">
|
||||
<span class="fa fa-edit"></span>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a href="#rules-viz" role="tab" data-toggle="tab">
|
||||
<span class="fa fa-eye"></span>
|
||||
{% trans "Visualize" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="rules-edit">
|
||||
<checkin-rules-editor></checkin-rules-editor>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="rules-viz">
|
||||
<checkin-rules-visualization></checkin-rules-visualization>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="alert alert-info" v-if="missingItems.length">
|
||||
<p>
|
||||
{% trans "Your rule always filters by product or variation, but the following products or variations are not contained in any of your rule parts so people with these tickets will not get in:" %}
|
||||
</p>
|
||||
<ul>
|
||||
<li v-for="h in missingItems">{{ "{" }}{h}{{ "}" }}</li>
|
||||
</ul>
|
||||
<p>
|
||||
{% trans "Please double-check if this was intentional." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="disabled-withoutjs sr-only">
|
||||
{{ form.rules }}
|
||||
@@ -91,6 +127,11 @@
|
||||
</form>
|
||||
{{ items|json_script:"items" }}
|
||||
|
||||
{% if DEBUG %}
|
||||
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
|
||||
{% else %}
|
||||
<script type="text/javascript" src="{% static "vuejs/vue.min.js" %}"></script>
|
||||
{% endif %}
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "d3/d3.v6.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "d3/d3-color.v2.js" %}"></script>
|
||||
@@ -103,6 +144,15 @@
|
||||
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts" %}
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/datetimefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/timefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/lookup-select2.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rule.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-editor.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
{% load getitem %}
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
{% load vite %}
|
||||
{% block title %}{% trans "Check-in simulator" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>
|
||||
@@ -125,9 +124,11 @@
|
||||
{% endif %}
|
||||
{% if result.rule_graph %}
|
||||
<div id="rules-editor" class="form-inline">
|
||||
<!-- Vue app mount point -->
|
||||
<div role="tabpanel" class="tab-pane" id="rules-viz">
|
||||
<checkin-rules-visualization></checkin-rules-visualization>
|
||||
</div>
|
||||
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
|
||||
</div>
|
||||
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,6 +152,10 @@
|
||||
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts" %}
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,113 +12,142 @@
|
||||
{% endblock %}
|
||||
{% block inside %}
|
||||
{% if question %}
|
||||
<h1>{% blocktrans with name=question.question %}Data field: {{ name }}{% endblocktrans %}</h1>
|
||||
<h1>{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}</h1>
|
||||
{% else %}
|
||||
<h1>{% trans "Data field" %}</h1>
|
||||
<h1>{% trans "Question" %}</h1>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.question layout="control" %}
|
||||
{% bootstrap_field form.type layout="control" %}
|
||||
<div id="valid-number">
|
||||
{% bootstrap_field form.valid_number_min layout="control" %}
|
||||
{% bootstrap_field form.valid_number_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-date">
|
||||
{% bootstrap_field form.valid_date_min layout="control" %}
|
||||
{% bootstrap_field form.valid_date_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-datetime">
|
||||
{% bootstrap_field form.valid_datetime_min layout="control" %}
|
||||
{% bootstrap_field form.valid_datetime_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-string">
|
||||
{% bootstrap_field form.valid_string_length_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-file">
|
||||
{% bootstrap_field form.valid_file_portrait layout="control" %}
|
||||
</div>
|
||||
<div id="answer-options">
|
||||
<h3>{% trans "Answer options" %}</h3>
|
||||
<noscript>
|
||||
<p>{% trans "Only applicable if you choose 'Choose one/multiple from a list' above." %}</p>
|
||||
</noscript>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}" data-formset-delete-confirm-text="{% trans "If you delete an answer option, you will no longer be able to see statistical data on customers who previously selected this option, and when such customers edit their answers, they need to select a different option." %}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
<div data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% blocktrans trimmed with id=form.instance.identifier %}
|
||||
Answer option {{ id }}
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.answer layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right flip">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="tabbed-form">
|
||||
<fieldset>
|
||||
<legend>{% trans "General" %}</legend>
|
||||
{% bootstrap_field form.question layout="control" %}
|
||||
{% bootstrap_field form.type layout="control" %}
|
||||
{% bootstrap_field form.items layout="control" %}
|
||||
{% bootstrap_field form.required layout="control" %}
|
||||
<div class="alert alert-info alert-required-boolean">
|
||||
{% blocktrans trimmed %}
|
||||
If you mark a Yes/No question as required, it means that the user has to select Yes and No is not
|
||||
accepted. If you want to allow both options, do not make this field required.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% trans "New answer option" %}
|
||||
</span>
|
||||
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
|
||||
<div id="valid-number">
|
||||
{% bootstrap_field form.valid_number_min layout="control" %}
|
||||
{% bootstrap_field form.valid_number_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-date">
|
||||
{% bootstrap_field form.valid_date_min layout="control" %}
|
||||
{% bootstrap_field form.valid_date_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-datetime">
|
||||
{% bootstrap_field form.valid_datetime_min layout="control" %}
|
||||
{% bootstrap_field form.valid_datetime_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-string">
|
||||
{% bootstrap_field form.valid_string_length_max layout="control" %}
|
||||
</div>
|
||||
<div id="valid-file">
|
||||
{% bootstrap_field form.valid_file_portrait layout="control" %}
|
||||
</div>
|
||||
<div id="answer-options">
|
||||
<h3>{% trans "Answer options" %}</h3>
|
||||
<noscript>
|
||||
<p>{% trans "Only applicable if you choose 'Choose one/multiple from a list' above." %}</p>
|
||||
</noscript>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}" data-formset-delete-confirm-text="{% trans "If you delete an answer option, you will no longer be able to see statistical data on customers who previously selected this option, and when such customers edit their answers, they need to select a different option." %}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for form in formset %}
|
||||
<div data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ form.id }}
|
||||
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% blocktrans trimmed with id=form.instance.identifier %}
|
||||
Answer option {{ id }}
|
||||
{% endblocktrans %}
|
||||
</span>
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.answer layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right flip">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 text-right flip">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new option" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ formset.empty_form.id }}
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="row question-option-row">
|
||||
<div class="col-xs-10">
|
||||
<span class="text-muted">
|
||||
{% trans "New answer option" %}
|
||||
</span>
|
||||
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-xs-2 text-right flip">
|
||||
<span> </span><br>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new option" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced" %}</legend>
|
||||
{% bootstrap_field form.help_text layout="control" %}
|
||||
{% bootstrap_field form.identifier layout="control" %}
|
||||
{% bootstrap_field form.ask_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.show_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.hidden layout="control" %}
|
||||
{% bootstrap_field form.print_on_invoice layout="control" %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_dependency_question">
|
||||
{% trans "Question dependency" %}
|
||||
<br><span class="optional">{% trans "Optional" context "form" %}</span>
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field form.dependency_question layout="inline" form_group_class="inner" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<script type="text/plain" id="dependency_value_val">{{ form.instance.dependency_values|escapejson_dumps }}</script>
|
||||
{% bootstrap_field form.dependency_values layout="inline" form_group_class="inner" %}
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% bootstrap_field form.identifier layout="control" %}
|
||||
{% bootstrap_field form.ask_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.show_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.hidden layout="control" %}
|
||||
{% bootstrap_field form.print_on_invoice layout="control" %}
|
||||
|
||||
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load static %}
|
||||
{% load icon %}
|
||||
{% load compress %}
|
||||
{% load vite %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Questionnaires" %}
|
||||
{% endblock %}
|
||||
{% block inside %}
|
||||
<a href="{% url "control:event.items.questions" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default pull-right">
|
||||
{% icon "wrench" %} {% trans "Manage data fields" %}
|
||||
</a>
|
||||
<h1>{% trans "Questionnaires" %}</h1>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Questions allow your attendees to fill in additional data about their ticket. If you provide food, one
|
||||
example might be to ask your users about dietary requirements.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div id="questionnaires-editor">
|
||||
<!-- Vue app mount point -->
|
||||
</div>
|
||||
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/questionnaires/index.ts" %}
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Data fields" %}{% endblock %}
|
||||
{% block title %}{% trans "Questions" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>{% trans "Data fields" %}</h1>
|
||||
<h1>{% trans "Questions" %}</h1>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Questions allow your attendees to fill in additional data about their ticket. If you provide food, one
|
||||
@@ -12,7 +12,7 @@
|
||||
{% csrf_token %}
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<p>
|
||||
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new data field" %}
|
||||
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
@@ -20,27 +20,39 @@
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Internal name" %}</th>
|
||||
<th>{% trans "Question" %}</th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th class="iconcol"></th>
|
||||
<th class="iconcol"></th>
|
||||
<th class="iconcol"></th>
|
||||
<th>{% trans "Products" %}</th>
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<th class="action-col-2"></th>
|
||||
{% endif %}
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody data-dnd-url="{% url "control:event.items.questions.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
{% for q in questions %}
|
||||
<tr>
|
||||
<tr data-dnd-id="{{ q.id }}">
|
||||
<td>
|
||||
<strong>
|
||||
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}">
|
||||
{{ q.question }}
|
||||
</a>
|
||||
{% if q.pk %}
|
||||
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}">
|
||||
{% endif %}
|
||||
{{ q.question }}
|
||||
{% if q.pk %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</strong><br>
|
||||
<small class="text-muted">{{ q.identifier }}</small>
|
||||
</td>
|
||||
<td>
|
||||
{{ q.get_type_display }}
|
||||
{% if q.pk %}
|
||||
{{ q.get_type_display }}
|
||||
{% else %}
|
||||
{% trans "System question" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if q.required %}
|
||||
@@ -51,17 +63,42 @@
|
||||
{% if q.pk and q.ask_during_checkin %}
|
||||
<span class="fa fa-check-square text-muted" data-toggle="tooltip" title="{% trans "Ask during check-in" %}"></span>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{% if q.pk and q.hidden %}
|
||||
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip" title="{% trans "Hidden question" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if q.pk %}
|
||||
<ul>
|
||||
{% for item in q.items.all %}
|
||||
<li>
|
||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<small>{% trans "All personalized products" %}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<td class="dnd-container">
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-right flip">
|
||||
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% if q.pk %}
|
||||
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if 'event.settings.general:write' in request.eventpermset %}
|
||||
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
|
||||
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% if page_obj.has_previous %}
|
||||
{% if page_obj.previous_page_number > 1 %}
|
||||
<li>
|
||||
<a href="?{% url_replace request 'page' page_obj.num_pages %}" title="{% trans "Go to page 1" %}">
|
||||
<a href="?{% url_replace request 'page' 1 %}" title="{% trans "Go to page 1" %}">
|
||||
<span class="fa fa-angle-double-left"></span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -348,7 +348,6 @@ urlpatterns = [
|
||||
re_path(r'^questions/(?P<question>\d+)/change$', item.QuestionUpdate.as_view(),
|
||||
name='event.items.questions.edit'),
|
||||
re_path(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
|
||||
re_path(r'^questionnaires/$', item.QuestionnairesEditor.as_view(), name='event.items.questionnaires'),
|
||||
re_path(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
|
||||
re_path(r'^quotas/(?P<quota>\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'),
|
||||
re_path(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'),
|
||||
|
||||
@@ -55,7 +55,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic import ListView, TemplateView
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django_countries.fields import Country
|
||||
|
||||
@@ -435,7 +435,87 @@ class QuestionList(ListView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
questions = list(ctx['questions'])
|
||||
questions = []
|
||||
|
||||
if self.request.event.settings.attendee_names_asked:
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='attendee_name_parts',
|
||||
question=_('Attendee name'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'attendee_name_parts', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_names_required,
|
||||
)
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_emails_asked:
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='attendee_email',
|
||||
question=_('Attendee email'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'attendee_email', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_emails_required,
|
||||
)
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_company_asked:
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='company',
|
||||
question=_('Company'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'company', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_company_required,
|
||||
)
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_addresses_asked:
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='street',
|
||||
question=_('Street'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'street', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='zipcode',
|
||||
question=_('ZIP code'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'zipcode', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='city',
|
||||
question=_('City'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'city', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
FakeQuestion(
|
||||
id='country',
|
||||
question=_('Country'),
|
||||
position=self.request.event.settings.system_question_order.get(
|
||||
'country', 0
|
||||
),
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
|
||||
questions += list(ctx['questions'])
|
||||
questions.sort(key=lambda q: q.position)
|
||||
ctx['questions'] = questions
|
||||
return ctx
|
||||
@@ -751,11 +831,6 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
|
||||
return ret
|
||||
|
||||
|
||||
class QuestionnairesEditor(EventPermissionRequiredMixin, TemplateView):
|
||||
permission = 'can_change_items'
|
||||
template_name = 'pretixcontrol/items/questionnaires.html'
|
||||
|
||||
|
||||
class QuotaList(PaginationMixin, ListView):
|
||||
model = Quota
|
||||
context_object_name = 'quotas'
|
||||
|
||||
63
src/pretix/helpers/compressor.py
Normal file
63
src/pretix/helpers/compressor.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
|
||||
from compressor.exceptions import FilterError
|
||||
from compressor.filters import CompilerFilter
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class VueCompiler(CompilerFilter):
|
||||
# Based on work (c) Laura Klünder in https://github.com/codingcatgirl/django-vue-rollup
|
||||
# Released under Apache License 2.0
|
||||
|
||||
def __init__(self, content, attrs, **kwargs):
|
||||
config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'static', 'npm_dir')
|
||||
node_path = os.path.join(settings.STATIC_ROOT, 'node_prefix', 'node_modules')
|
||||
self.rollup_bin = os.path.join(node_path, 'rollup', 'dist', 'bin', 'rollup')
|
||||
rollup_config = os.path.join(config_dir, 'rollup.config.js')
|
||||
if not os.path.exists(self.rollup_bin) and not settings.DEBUG:
|
||||
raise FilterError("Rollup not installed or pretix not built properly, please run 'make npminstall' in source root.")
|
||||
command = (
|
||||
' '.join((
|
||||
'NODE_PATH=' + shlex.quote(node_path),
|
||||
shlex.quote(self.rollup_bin),
|
||||
'-c',
|
||||
shlex.quote(rollup_config))
|
||||
) +
|
||||
' --input {infile} -n {export_name} --file {outfile}'
|
||||
)
|
||||
super().__init__(content, command=command, **kwargs)
|
||||
|
||||
def input(self, **kwargs):
|
||||
if self.filename is None:
|
||||
raise FilterError('VueCompiler can only compile files, not inline code.')
|
||||
if not os.path.exists(self.rollup_bin):
|
||||
raise FilterError("Rollup not installed, please run 'make npminstall' in source root.")
|
||||
self.options += (('export_name', re.sub(
|
||||
r'^([a-z])|[^a-z0-9A-Z]+([a-zA-Z0-9])?',
|
||||
lambda s: s.group(0)[-1].upper(),
|
||||
os.path.basename(self.filename).split('.')[0]
|
||||
)),)
|
||||
return super().input(**kwargs)
|
||||
@@ -25,7 +25,7 @@ import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login as auth_login
|
||||
from django.contrib.gis.geoip2 import GeoIP2
|
||||
from django.contrib.gis import geoip2
|
||||
from django.core.cache import cache
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -63,14 +63,20 @@ def get_user_agent_hash(request):
|
||||
_geoip = None
|
||||
|
||||
|
||||
def _get_country(request):
|
||||
def get_geoip() -> geoip2.GeoIP2:
|
||||
# See https://code.djangoproject.com/ticket/36988#ticket
|
||||
global _geoip
|
||||
|
||||
if not _geoip:
|
||||
_geoip = GeoIP2()
|
||||
geoip2.SUPPORTED_DATABASE_TYPES.add("Geoacumen-Country")
|
||||
|
||||
if not _geoip:
|
||||
_geoip = geoip2.GeoIP2()
|
||||
return _geoip
|
||||
|
||||
|
||||
def _get_country(request):
|
||||
try:
|
||||
res = _geoip.country(get_client_ip(request))
|
||||
res = get_geoip().country(get_client_ip(request))
|
||||
except AddressNotFoundError:
|
||||
return None
|
||||
return res['country_code']
|
||||
|
||||
@@ -5,7 +5,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-17 14:27+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 02:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"de/>\n"
|
||||
@@ -8453,7 +8453,7 @@ msgstr "Zahlungseinstellungen"
|
||||
#: pretix/base/permissions.py:203
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:120
|
||||
msgid "Tax settings"
|
||||
msgstr "Steuer-Einstellungen"
|
||||
msgstr "Steuereinstellungen"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
msgid "Invoicing settings"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-17 14:34+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 02:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
@@ -8446,7 +8446,7 @@ msgstr "Zahlungseinstellungen"
|
||||
#: pretix/base/permissions.py:203
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:120
|
||||
msgid "Tax settings"
|
||||
msgstr "Steuer-Einstellungen"
|
||||
msgstr "Steuereinstellungen"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
msgid "Invoicing settings"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-03 20:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"es/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16.1\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -408,10 +408,8 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py:482
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "Información de la cuenta"
|
||||
msgstr "Invitación a crear una cuenta"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:503
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -3966,10 +3964,9 @@ msgid "Peppol participant ID"
|
||||
msgstr "Identificador de participante Peppol"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
#, fuzzy
|
||||
#| msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr "El ID de participante de Peppol no está registrado en la red Peppol."
|
||||
msgstr ""
|
||||
"El identificador de participante de Peppol no coincide con el número de IVA."
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -4991,7 +4988,7 @@ msgstr ""
|
||||
#: pretix/base/models/event.py:607 pretix/base/models/organizer.py:94
|
||||
msgid "The slug may only contain letters, numbers, dots and dashes."
|
||||
msgstr ""
|
||||
"La URL semántica solo puede contener letras, números, puntos y guiones."
|
||||
"El nombre de archivo solo puede contener letras, números, puntos y guiones."
|
||||
|
||||
#: pretix/base/models/event.py:624 pretix/base/models/event.py:1551
|
||||
msgid "Show in lists"
|
||||
@@ -6893,10 +6890,8 @@ msgstr ""
|
||||
"pocos minutos para activar para todos los usuarios."
|
||||
|
||||
#: pretix/base/models/organizer.py:378
|
||||
#, fuzzy
|
||||
#| msgid "Event permissions"
|
||||
msgid "All event permissions"
|
||||
msgstr "Permisos para eventos"
|
||||
msgstr "Todos los permisos del evento"
|
||||
|
||||
#: pretix/base/models/organizer.py:379
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
@@ -6904,10 +6899,8 @@ msgid "Event permissions"
|
||||
msgstr "Permisos para eventos"
|
||||
|
||||
#: pretix/base/models/organizer.py:380
|
||||
#, fuzzy
|
||||
#| msgid "Organizer permissions"
|
||||
msgid "All organizer permissions"
|
||||
msgstr "Permisos del organizador"
|
||||
msgstr "Todos los permisos de organizador"
|
||||
|
||||
#: pretix/base/models/organizer.py:381
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
@@ -8414,36 +8407,32 @@ msgstr "Su archivo de diseño no es un diseño válido. Mensaje de error: {}"
|
||||
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
|
||||
msgctxt "permission_level"
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "Ver"
|
||||
|
||||
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
|
||||
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
|
||||
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Save and check"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "Guardar y verificar"
|
||||
msgstr "Ver y modificar"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
#, fuzzy
|
||||
#| msgid "API tokens"
|
||||
msgid "API only"
|
||||
msgstr "Tokens de API"
|
||||
msgstr "Solo API"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
msgstr ""
|
||||
"Esta opción del menú solo aparecerá si el usuario tiene permiso para acceder "
|
||||
"a la configuración general."
|
||||
|
||||
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
|
||||
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
|
||||
#: pretix/base/permissions.py:330
|
||||
#, fuzzy
|
||||
#| msgid "Revoke access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "Revocar el acceso"
|
||||
msgstr "Sin acceso"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
@@ -8457,6 +8446,8 @@ msgid ""
|
||||
"This includes access to all settings not listed explicitly below, including "
|
||||
"plugin settings."
|
||||
msgstr ""
|
||||
"Esto incluye el acceso a todos los ajustes que no se enumeran explícitamente "
|
||||
"a continuación, incluidos los ajustes de los complementos."
|
||||
|
||||
#: pretix/base/permissions.py:197
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:6
|
||||
@@ -8470,100 +8461,79 @@ msgid "Tax settings"
|
||||
msgstr "Configuración de impuestos"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice settings"
|
||||
msgid "Invoicing settings"
|
||||
msgstr "Configuración de la factura"
|
||||
msgstr "Configuración de facturación"
|
||||
|
||||
#: pretix/base/permissions.py:215
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event series date added"
|
||||
msgid "Event series dates"
|
||||
msgstr "Fechas de la serie de eventos añadidas"
|
||||
msgstr "Fechas de la serie de eventos"
|
||||
|
||||
#: pretix/base/permissions.py:221
|
||||
#, fuzzy
|
||||
#| msgid "Product name and variation"
|
||||
msgid "Products, quotas and questions"
|
||||
msgstr "Nombre del producto y variación"
|
||||
msgstr "Productos, cuotas y preguntas"
|
||||
|
||||
#: pretix/base/permissions.py:224
|
||||
msgid "Also includes related objects like categories or discounts."
|
||||
msgstr ""
|
||||
msgstr "También incluye elementos relacionados, como categorías o descuentos."
|
||||
|
||||
#: pretix/base/permissions.py:232
|
||||
#, fuzzy
|
||||
#| msgid "All check-ins"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "Todos los check-ins"
|
||||
msgstr "Solo check-in"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
#, fuzzy
|
||||
#| msgid "View full log"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr "Ver log completo"
|
||||
msgstr "Ver todo"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Valid check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "Check-in válido"
|
||||
msgstr "Ver todo y check-in"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
#, fuzzy
|
||||
#| msgid "View all upcoming events"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and change"
|
||||
msgstr "Ver todos los próximos eventos"
|
||||
msgstr "Ver todo y modificar"
|
||||
|
||||
#: pretix/base/permissions.py:236
|
||||
msgid "Includes the ability to cancel and refund individual orders."
|
||||
msgstr ""
|
||||
msgstr "Incluye la posibilidad de cancelar y reembolsar pedidos individuales."
|
||||
|
||||
#: pretix/base/permissions.py:238
|
||||
#, fuzzy
|
||||
#| msgid "An entry has been added to the waiting list."
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr "Se ha añadido una entrada a la lista de espera."
|
||||
msgstr "También incluye elementos relacionados, como la lista de espera."
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgid "Generate cancellation"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "Generar cancelación"
|
||||
msgstr "Cancelación total del evento o de la fecha"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
#, fuzzy
|
||||
#| msgid "Sale not allowed"
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr "Venta no permitida"
|
||||
msgstr "No está permitido"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Allowed titles"
|
||||
msgctxt "permission_level"
|
||||
msgid "Allowed"
|
||||
msgstr "Titulos permitidos"
|
||||
msgstr "Permitido"
|
||||
|
||||
#: pretix/base/permissions.py:268
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Acceder a los eventos existentes"
|
||||
|
||||
#: pretix/base/permissions.py:269
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing and create new events"
|
||||
msgstr ""
|
||||
msgstr "Acceder a eventos existentes y crear nuevos eventos"
|
||||
|
||||
#: pretix/base/permissions.py:271
|
||||
msgid ""
|
||||
"The level of access to events is determined in detail by the settings below."
|
||||
msgstr ""
|
||||
"El nivel de acceso a los eventos se determina en detalle mediante los "
|
||||
"ajustes que se indican a continuación."
|
||||
|
||||
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
|
||||
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
|
||||
@@ -8582,12 +8552,17 @@ msgid ""
|
||||
"This includes access to all organizer-level functionality not listed "
|
||||
"explicitly below, including plugin settings."
|
||||
msgstr ""
|
||||
"Esto incluye el acceso a todas las funciones de nivel de organizador que no "
|
||||
"se enumeran explícitamente a continuación, incluida la configuración de los "
|
||||
"complementos."
|
||||
|
||||
#: pretix/base/permissions.py:287
|
||||
msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Incluye la posibilidad de conceder permisos adicionales a otra persona "
|
||||
"(incluido uno mismo)."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8605,12 +8580,12 @@ msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Incluye la posibilidad de conceder acceso a eventos y datos a los que uno "
|
||||
"mismo no tiene acceso."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Seating plan"
|
||||
msgid "Seating plans"
|
||||
msgstr "Plan de asientos"
|
||||
msgstr "Planes de asientos"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
@@ -9254,12 +9229,12 @@ msgid "Czech National Bank"
|
||||
msgstr "Banco Nacional Checo"
|
||||
|
||||
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Export not found or you do not have sufficient permission to perform this "
|
||||
"export."
|
||||
msgstr "No tiene permiso suficiente para realizar esta exportación."
|
||||
msgstr ""
|
||||
"No se ha encontrado la exportación o no tienes los permisos suficientes para "
|
||||
"realizarla."
|
||||
|
||||
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
|
||||
#: pretix/base/services/export.py:357
|
||||
@@ -13735,25 +13710,7 @@ msgid "Contact"
|
||||
msgstr "Contacto"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13788,7 +13745,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Atentamente,\n"
|
||||
"\n"
|
||||
"Tu equipo de pretix\n"
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
|
||||
msgid ""
|
||||
@@ -14350,16 +14307,12 @@ msgid "This is an event series"
|
||||
msgstr "Esta es una serie de eventos"
|
||||
|
||||
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You do not have sufficient permission to enable plugins that need to be "
|
||||
#| "enabled for the entire organizer account."
|
||||
msgid ""
|
||||
"You do not have a sufficient level of access on the event you selected to "
|
||||
"copy it to the desired organizer."
|
||||
msgstr ""
|
||||
"No tienes permisos suficientes para habilitar los plugins que deben "
|
||||
"habilitarse para toda su cuenta de organizador."
|
||||
"No tiene los permisos necesarios para copiar el evento que ha seleccionado "
|
||||
"al organizador deseado."
|
||||
|
||||
#: pretix/control/forms/event.py:143
|
||||
msgid ""
|
||||
@@ -14439,6 +14392,8 @@ msgid ""
|
||||
"You cannot choose a team that would give you more access than you have on "
|
||||
"the event you are copying."
|
||||
msgstr ""
|
||||
"No puede elegir un equipo que le otorgue más permisos de los que ya tiene en "
|
||||
"el evento que está copiando."
|
||||
|
||||
#: pretix/control/forms/event.py:344
|
||||
msgid "Copy configuration from"
|
||||
@@ -14454,6 +14409,8 @@ msgid ""
|
||||
"You cannot choose an event on which you have less access than the team you "
|
||||
"selected in the previous step."
|
||||
msgstr ""
|
||||
"No puede elegir un evento al que tenga menos derechos de acceso que el "
|
||||
"equipo que seleccionó en el paso anterior."
|
||||
|
||||
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
|
||||
#: pretix/control/forms/subevents.py:411
|
||||
@@ -16816,7 +16773,7 @@ msgstr "Sólo puede establecer un dominio de organizador."
|
||||
|
||||
#: pretix/control/forms/organizer.py:322
|
||||
msgid "Provided by a plugin"
|
||||
msgstr ""
|
||||
msgstr "Proporcionado por un plugin"
|
||||
|
||||
#: pretix/control/forms/organizer.py:438
|
||||
msgid ""
|
||||
@@ -17708,10 +17665,8 @@ msgid "The order has been denied (comment: \"{comment}\")."
|
||||
msgstr "El pedido ha sido denegado (comentario: \"{comment}\")."
|
||||
|
||||
#: pretix/control/logdisplay.py:521
|
||||
#, fuzzy
|
||||
#| msgid "The order has been overpaid."
|
||||
msgid "The customer VAT ID has been verified."
|
||||
msgstr "El pedido ha sido pagado de más."
|
||||
msgstr "Se ha verificado el número de identificación fiscal del cliente."
|
||||
|
||||
#: pretix/control/logdisplay.py:522
|
||||
#, python-brace-format
|
||||
@@ -18015,10 +17970,9 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} ha sido invitado al equipo."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Invite for {user} has been resent."
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "La invitación para {user} ha sido reenviada."
|
||||
msgstr "Se ha eliminado la invitación para {user}."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, python-brace-format
|
||||
@@ -20338,21 +20292,7 @@ msgid "Add property"
|
||||
msgstr "Añadir propiedad"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "%(reason)s\n"
|
||||
#| "\n"
|
||||
#| " %(code)s\n"
|
||||
#| "\n"
|
||||
#| "Please do never give this code to another person. Our support team will "
|
||||
#| "never ask for this code.\n"
|
||||
#| "\n"
|
||||
#| "If this code was not requested by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20381,7 +20321,7 @@ msgstr ""
|
||||
"inmediato.\n"
|
||||
"\n"
|
||||
"Atentamente,\n"
|
||||
"El equipo de pretix\n"
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
|
||||
#, python-format
|
||||
@@ -20419,17 +20359,7 @@ msgstr ""
|
||||
"Tu equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you requested a new password. Please go to the following page to reset "
|
||||
#| "your password:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20449,27 +20379,10 @@ msgstr ""
|
||||
"%(url)s \n"
|
||||
"\n"
|
||||
"Saludos, \n"
|
||||
"El equipo de pretix\n"
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you have been invited to a team on pretix, a platform to perform event\n"
|
||||
#| "ticket sales.\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "Team: %(team)s\n"
|
||||
#| "\n"
|
||||
#| "If you want to join that team, just click on the following link:\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "If you do not want to join, you can safely ignore or delete this email.\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20491,7 +20404,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hola, \n"
|
||||
"\n"
|
||||
"usted ha sido invitado a un equipo en pretix, una plataforma para realizar \n"
|
||||
"usted ha sido invitado a un equipo de %(instance)s, una plataforma para "
|
||||
"realizar \n"
|
||||
"ventas de entradas de eventos. \n"
|
||||
"\n"
|
||||
"Organizador: %(organizer)s\n"
|
||||
@@ -20504,7 +20418,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Saludos cordiales, \n"
|
||||
"\n"
|
||||
"Su equipo Pretix\n"
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
|
||||
#, python-format
|
||||
@@ -20542,24 +20456,7 @@ msgstr ""
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "this is to inform you that the account information of your pretix account "
|
||||
#| "has been\n"
|
||||
#| "changed. In particular, the following changes have been performed:\n"
|
||||
#| "\n"
|
||||
#| "%(messages)s\n"
|
||||
#| "\n"
|
||||
#| "If this change was not performed by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "You can review and change your account settings here:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20580,8 +20477,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hola, \n"
|
||||
"\n"
|
||||
"esto es para informarle que la información de su cuenta pretix ha sido "
|
||||
"cambiada . \n"
|
||||
"esto es para informarle que la información de su cuenta del equipo de %"
|
||||
"(instance)s ha sido cambiada. \n"
|
||||
"En particular, se han realizado las siguientes modificaciones: \n"
|
||||
"\n"
|
||||
"%(messages)s\n"
|
||||
@@ -20594,7 +20491,7 @@ msgstr ""
|
||||
"%(url)s \n"
|
||||
"\n"
|
||||
"Saludos cordiales, \n"
|
||||
"su equipo de Pretix\n"
|
||||
"El equipo de %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email_setup.html:8
|
||||
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
|
||||
@@ -20864,10 +20761,8 @@ msgstr ""
|
||||
"entradas, puede hacerlo a través de esta opción."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
|
||||
#, fuzzy
|
||||
#| msgid "Permissions"
|
||||
msgid "No permission"
|
||||
msgstr "Permisos"
|
||||
msgstr "No tiene permiso"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
|
||||
@@ -23426,10 +23321,8 @@ msgid "Edit question"
|
||||
msgstr "Editar pregunta"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:55
|
||||
#, fuzzy
|
||||
#| msgid "You do not have permission to view this content."
|
||||
msgid "No permission to view answers."
|
||||
msgstr "No tienes permiso para ver este contenido."
|
||||
msgstr "No tiene permiso para ver las respuestas."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:63
|
||||
msgid "No matching answers found."
|
||||
@@ -25078,10 +24971,8 @@ msgstr "No hay próxima ejecución programada"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:37
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
|
||||
#, fuzzy
|
||||
#| msgid "Exporter not found"
|
||||
msgid "Exporter not found or no permission"
|
||||
msgstr "Exportador no encontrado"
|
||||
msgstr "No se ha encontrado el exportador o no se dispone de permiso"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:42
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
|
||||
@@ -25124,10 +25015,8 @@ msgstr "Recomendado para nuevos usuarios"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:120
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
|
||||
#, fuzzy
|
||||
#| msgid "There are no add-ons available for this product."
|
||||
msgid "There are no exporters available for you."
|
||||
msgstr "No hay complementos disponibles para este producto."
|
||||
msgstr "No hay exportadores disponibles para usted."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
|
||||
@@ -27078,7 +26967,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
|
||||
#, python-format
|
||||
msgid "max. %(size)s, smaller is better"
|
||||
msgstr ""
|
||||
msgstr "máx. %(size)s; cuanto más pequeño, mejor"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
|
||||
msgid "Download current background"
|
||||
@@ -29861,16 +29750,12 @@ msgstr ""
|
||||
"los errores."
|
||||
|
||||
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Your user account does not have sufficient permission to run this report, "
|
||||
#| "therefore you cannot schedule it."
|
||||
msgid ""
|
||||
"Your user account does not have sufficient permission to run this report, "
|
||||
"therefore you cannot change it."
|
||||
msgstr ""
|
||||
"Su cuenta de usuario no tiene permisos suficientes para ejecutar este "
|
||||
"informe, por lo que no puede programarlo."
|
||||
"Su cuenta de usuario no tiene los permisos suficientes para ejecutar este "
|
||||
"informe, por lo que no puede modificarlo."
|
||||
|
||||
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
|
||||
msgid ""
|
||||
@@ -33945,7 +33830,7 @@ msgstr "iDEAL via Stripe"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
@@ -36930,6 +36815,9 @@ msgid ""
|
||||
"the shop that affect quotas, such as the validity period of carts and "
|
||||
"vouchers."
|
||||
msgstr ""
|
||||
"Tenga en cuenta que la hora modificada no se tiene en cuenta en aquellos "
|
||||
"aspectos de la tienda que afectan a los límites, como el periodo de validez "
|
||||
"de los carritos y los vales."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
|
||||
msgid "Enable time machine"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||
"PO-Revision-Date: 2026-01-27 14:51+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/es/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -60,7 +60,7 @@ msgstr "PayPal Paga Después"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
|
||||
@@ -4,16 +4,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-03 20:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"fr/>\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.16.1\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -409,10 +409,8 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py:482
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "Informations sur le compte"
|
||||
msgstr "Invitation à créer un compte"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:503
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -3971,12 +3969,8 @@ msgid "Peppol participant ID"
|
||||
msgstr "Identifiant participant Peppol"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
#, fuzzy
|
||||
#| msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr ""
|
||||
"L'identifiant Peppol du participant n'est pas enregistré sur le réseau "
|
||||
"Peppol."
|
||||
msgstr "L'identifiant Peppol ne correspond pas à votre numéro de TVA."
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -6929,10 +6923,8 @@ msgstr ""
|
||||
"les utilisateurs."
|
||||
|
||||
#: pretix/base/models/organizer.py:378
|
||||
#, fuzzy
|
||||
#| msgid "Event permissions"
|
||||
msgid "All event permissions"
|
||||
msgstr "Autorisations de l'événement"
|
||||
msgstr "Toutes les autorisations relatives aux événements"
|
||||
|
||||
#: pretix/base/models/organizer.py:379
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
@@ -6940,10 +6932,8 @@ msgid "Event permissions"
|
||||
msgstr "Autorisations de l'événement"
|
||||
|
||||
#: pretix/base/models/organizer.py:380
|
||||
#, fuzzy
|
||||
#| msgid "Organizer permissions"
|
||||
msgid "All organizer permissions"
|
||||
msgstr "Autorisations de l'organisateur"
|
||||
msgstr "Toutes les autorisations d'organisateur"
|
||||
|
||||
#: pretix/base/models/organizer.py:381
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
@@ -8467,36 +8457,32 @@ msgstr ""
|
||||
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
|
||||
msgctxt "permission_level"
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "Voir"
|
||||
|
||||
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
|
||||
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
|
||||
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Save and check"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "Enregistrer et vérifier"
|
||||
msgstr "Afficher et modifier"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
#, fuzzy
|
||||
#| msgid "API tokens"
|
||||
msgid "API only"
|
||||
msgstr "Tokens API"
|
||||
msgstr "API uniquement"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
msgstr ""
|
||||
"Cet élément de menu n'apparaîtra que si l'utilisateur dispose d'une "
|
||||
"autorisation pour les paramètres généraux."
|
||||
|
||||
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
|
||||
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
|
||||
#: pretix/base/permissions.py:330
|
||||
#, fuzzy
|
||||
#| msgid "Revoke access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "Révoquer l'accès"
|
||||
msgstr "Accès impossible"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
@@ -8510,6 +8496,8 @@ msgid ""
|
||||
"This includes access to all settings not listed explicitly below, including "
|
||||
"plugin settings."
|
||||
msgstr ""
|
||||
"Cela inclut l'accès à tous les paramètres qui ne sont pas explicitement "
|
||||
"mentionnés ci-dessous, y compris les paramètres des extensions."
|
||||
|
||||
#: pretix/base/permissions.py:197
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:6
|
||||
@@ -8523,100 +8511,81 @@ msgid "Tax settings"
|
||||
msgstr "Paramètres relatifs aux taxes"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice settings"
|
||||
msgid "Invoicing settings"
|
||||
msgstr "Paramètres de facturation"
|
||||
|
||||
#: pretix/base/permissions.py:215
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event series date added"
|
||||
msgid "Event series dates"
|
||||
msgstr "Une nouvelle date a été ajouté"
|
||||
msgstr "Dates de la série d'événements"
|
||||
|
||||
#: pretix/base/permissions.py:221
|
||||
#, fuzzy
|
||||
#| msgid "Product name and variation"
|
||||
msgid "Products, quotas and questions"
|
||||
msgstr "Dénomination et variantes du produit"
|
||||
msgstr "Produits, quotas et questions"
|
||||
|
||||
#: pretix/base/permissions.py:224
|
||||
msgid "Also includes related objects like categories or discounts."
|
||||
msgstr ""
|
||||
"Cela inclut également les éléments associés, tels que les catégories ou les "
|
||||
"remises."
|
||||
|
||||
#: pretix/base/permissions.py:232
|
||||
#, fuzzy
|
||||
#| msgid "All check-ins"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "Tous les enregistrements"
|
||||
msgstr "Enregistrement uniquement"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
#, fuzzy
|
||||
#| msgid "View full log"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr "Voir le journal complet"
|
||||
msgstr "Tout afficher"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Valid check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "Enregistrement valide"
|
||||
msgstr "Tout afficher et les check-in"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
#, fuzzy
|
||||
#| msgid "View all upcoming events"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and change"
|
||||
msgstr "Voir tous les événements à venir"
|
||||
msgstr "Tout afficher et modifier"
|
||||
|
||||
#: pretix/base/permissions.py:236
|
||||
msgid "Includes the ability to cancel and refund individual orders."
|
||||
msgstr ""
|
||||
msgstr "Permet d'annuler et de rembourser des commandes individuelles."
|
||||
|
||||
#: pretix/base/permissions.py:238
|
||||
#, fuzzy
|
||||
#| msgid "An entry has been added to the waiting list."
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr "Une entrée a été ajoutée à la liste d'attente."
|
||||
msgstr "Comprend également les éléments connexes, tels que la liste d'attente."
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgid "Generate cancellation"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "Générer annulation"
|
||||
msgstr "Annulation complète de l'événement ou de la date"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
#, fuzzy
|
||||
#| msgid "Sale not allowed"
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr "Vente non autorisée"
|
||||
msgstr "Interdit"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Allowed titles"
|
||||
msgctxt "permission_level"
|
||||
msgid "Allowed"
|
||||
msgstr "Titres autorisés"
|
||||
msgstr "Autorisé"
|
||||
|
||||
#: pretix/base/permissions.py:268
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Accéder aux événements existants"
|
||||
|
||||
#: pretix/base/permissions.py:269
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing and create new events"
|
||||
msgstr ""
|
||||
msgstr "Accéder aux événements existants et en créer de nouveaux"
|
||||
|
||||
#: pretix/base/permissions.py:271
|
||||
msgid ""
|
||||
"The level of access to events is determined in detail by the settings below."
|
||||
msgstr ""
|
||||
"Le niveau d'accès aux événements est défini en détail par les paramètres ci-"
|
||||
"dessous."
|
||||
|
||||
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
|
||||
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
|
||||
@@ -8635,12 +8604,17 @@ msgid ""
|
||||
"This includes access to all organizer-level functionality not listed "
|
||||
"explicitly below, including plugin settings."
|
||||
msgstr ""
|
||||
"Cela inclut l'accès à toutes les fonctionnalités de niveau organisateur qui "
|
||||
"ne sont pas explicitement mentionnées ci-dessous, y compris les paramètres "
|
||||
"des extensions."
|
||||
|
||||
#: pretix/base/permissions.py:287
|
||||
msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Permet d'accorder des autorisations supplémentaires à une autre personne (y "
|
||||
"compris à soi-même)."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8658,12 +8632,12 @@ msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Permet de donner accès à des événements et à des données auxquels on n'a pas "
|
||||
"soi-même accès."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Seating plan"
|
||||
msgid "Seating plans"
|
||||
msgstr "Plan de salle"
|
||||
msgstr "Plans de salle"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
@@ -9314,14 +9288,12 @@ msgid "Czech National Bank"
|
||||
msgstr "Banque nationale tchèque"
|
||||
|
||||
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Export not found or you do not have sufficient permission to perform this "
|
||||
"export."
|
||||
msgstr ""
|
||||
"Vous ne disposez pas des autorisations suffisantes pour effectuer cette "
|
||||
"exportation."
|
||||
"L'exportation est introuvable ou vous ne disposez pas des autorisations "
|
||||
"nécessaires pour effectuer cette exportation."
|
||||
|
||||
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
|
||||
#: pretix/base/services/export.py:357
|
||||
@@ -13860,25 +13832,7 @@ msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13913,7 +13867,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Sinceres salutations\n"
|
||||
"\n"
|
||||
"Votre équipe pretix\n"
|
||||
"Votre équipe pretix%(instance)s\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
|
||||
msgid ""
|
||||
@@ -14482,16 +14436,12 @@ msgid "This is an event series"
|
||||
msgstr "C'est une série d'événements"
|
||||
|
||||
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You do not have sufficient permission to enable plugins that need to be "
|
||||
#| "enabled for the entire organizer account."
|
||||
msgid ""
|
||||
"You do not have a sufficient level of access on the event you selected to "
|
||||
"copy it to the desired organizer."
|
||||
msgstr ""
|
||||
"Vous ne disposez pas des autorisations suffisantes pour activer les plugins "
|
||||
"qui doivent être activés pour l'ensemble du compte organisateur."
|
||||
"Vous ne disposez pas des droits d'accès suffisants pour copier l'événement "
|
||||
"que vous avez sélectionné vers l'organisateur souhaité."
|
||||
|
||||
#: pretix/control/forms/event.py:143
|
||||
msgid ""
|
||||
@@ -14574,6 +14524,9 @@ msgid ""
|
||||
"You cannot choose a team that would give you more access than you have on "
|
||||
"the event you are copying."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas choisir une équipe qui vous donnerait plus de droits "
|
||||
"d'accès que ceux dont vous disposez actuellement pour l'événement que vous "
|
||||
"copiez."
|
||||
|
||||
#: pretix/control/forms/event.py:344
|
||||
msgid "Copy configuration from"
|
||||
@@ -14589,6 +14542,9 @@ msgid ""
|
||||
"You cannot choose an event on which you have less access than the team you "
|
||||
"selected in the previous step."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas sélectionner un événement pour lequel vous disposez de "
|
||||
"moins de droits d'accès que l'équipe que vous avez choisie à l'étape "
|
||||
"précédente."
|
||||
|
||||
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
|
||||
#: pretix/control/forms/subevents.py:411
|
||||
@@ -15543,7 +15499,7 @@ msgstr "Recherche d'un participant…"
|
||||
#: pretix/control/forms/filter.py:2039
|
||||
#: pretix/plugins/checkinlists/exporters.py:106
|
||||
msgid "Check-in status"
|
||||
msgstr "Statut d'enregistrement"
|
||||
msgstr "État de l'enregistrement"
|
||||
|
||||
#: pretix/control/forms/filter.py:2041
|
||||
#: pretix/plugins/checkinlists/exporters.py:108
|
||||
@@ -15556,7 +15512,7 @@ msgstr "Tous les participants"
|
||||
#: pretix/plugins/checkinlists/exporters.py:109
|
||||
#: pretix/plugins/checkinlists/exporters.py:501
|
||||
msgid "Checked in"
|
||||
msgstr "Enregistré"
|
||||
msgstr "Enregistrement effectué"
|
||||
|
||||
#: pretix/control/forms/filter.py:2043
|
||||
#: pretix/plugins/checkinlists/exporters.py:110
|
||||
@@ -15574,7 +15530,7 @@ msgstr "Enregistré mais laissé"
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:178
|
||||
#: pretix/plugins/checkinlists/exporters.py:112
|
||||
msgid "Not checked in"
|
||||
msgstr "Non enregistré"
|
||||
msgstr "Enregistrement non effectué"
|
||||
|
||||
#: pretix/control/forms/filter.py:2064
|
||||
msgctxt "subevent"
|
||||
@@ -16969,7 +16925,7 @@ msgstr "Vous ne pouvez définir qu'un seul domaine d'organisateur."
|
||||
|
||||
#: pretix/control/forms/organizer.py:322
|
||||
msgid "Provided by a plugin"
|
||||
msgstr ""
|
||||
msgstr "Fourni par un plugin"
|
||||
|
||||
#: pretix/control/forms/organizer.py:438
|
||||
msgid ""
|
||||
@@ -17856,10 +17812,8 @@ msgid "The order has been denied (comment: \"{comment}\")."
|
||||
msgstr "L’ordonnance a été refusée (commentaire : \"{comment}\")."
|
||||
|
||||
#: pretix/control/logdisplay.py:521
|
||||
#, fuzzy
|
||||
#| msgid "The order has been overpaid."
|
||||
msgid "The customer VAT ID has been verified."
|
||||
msgstr "La commande a été payée en trop."
|
||||
msgstr "Le numéro d'identification TVA du client a été vérifié."
|
||||
|
||||
#: pretix/control/logdisplay.py:522
|
||||
#, python-brace-format
|
||||
@@ -18160,10 +18114,9 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} a été invité dans l'équipe."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Invite for {user} has been resent."
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "L'invitation pour {user} a été renvoyée."
|
||||
msgstr "L'invitation destinée à {user} a été supprimée."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, python-brace-format
|
||||
@@ -20484,21 +20437,7 @@ msgid "Add property"
|
||||
msgstr "Ajouter une propriété"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "%(reason)s\n"
|
||||
#| "\n"
|
||||
#| " %(code)s\n"
|
||||
#| "\n"
|
||||
#| "Please do never give this code to another person. Our support team will "
|
||||
#| "never ask for this code.\n"
|
||||
#| "\n"
|
||||
#| "If this code was not requested by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20527,7 +20466,7 @@ msgstr ""
|
||||
"immédiatement.\n"
|
||||
"\n"
|
||||
"Cordialement,\n"
|
||||
"L’équipe pretix\n"
|
||||
"Votre équipe pretix%(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
|
||||
#, python-format
|
||||
@@ -20565,17 +20504,7 @@ msgstr ""
|
||||
"Votre équipe %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you requested a new password. Please go to the following page to reset "
|
||||
#| "your password:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20595,27 +20524,10 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Sincères salutations, \n"
|
||||
"Votre équipe Pretix\n"
|
||||
"Votre équipe pretix%(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you have been invited to a team on pretix, a platform to perform event\n"
|
||||
#| "ticket sales.\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "Team: %(team)s\n"
|
||||
#| "\n"
|
||||
#| "If you want to join that team, just click on the following link:\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "If you do not want to join, you can safely ignore or delete this email.\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20637,8 +20549,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Bonjour,\n"
|
||||
"\n"
|
||||
"vous avez été invité à faire partie d'une équipe sur pretix, une plateforme "
|
||||
"pour réaliser un événement\n"
|
||||
"vous avez été invité à faire partie de l'équipe pretix%(instance)s, une "
|
||||
"plateforme pour réaliser un événement\n"
|
||||
"de vente de billets.\n"
|
||||
"\n"
|
||||
"Organisateur : %(organizer)s\n"
|
||||
@@ -20652,7 +20564,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Sincères salutations, \n"
|
||||
"\n"
|
||||
"Votre équipe Pretix\n"
|
||||
"Votre équipe pretix%(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
|
||||
#, python-format
|
||||
@@ -20690,24 +20602,7 @@ msgstr ""
|
||||
"Votre équipe %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "this is to inform you that the account information of your pretix account "
|
||||
#| "has been\n"
|
||||
#| "changed. In particular, the following changes have been performed:\n"
|
||||
#| "\n"
|
||||
#| "%(messages)s\n"
|
||||
#| "\n"
|
||||
#| "If this change was not performed by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "You can review and change your account settings here:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20728,8 +20623,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Bonjour,\n"
|
||||
"\n"
|
||||
"Ceci est pour vous informer que les informations de votre compte pretix ont "
|
||||
"été\n"
|
||||
"Ceci est pour vous informer que les informations de votre compte %(instance)"
|
||||
"s ont été\n"
|
||||
"modifié. Les modifications suivantes ont été apportées :\n"
|
||||
"\n"
|
||||
"%(messages)s\n"
|
||||
@@ -20742,7 +20637,7 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Sincères salutations, \n"
|
||||
"Votre équipe Pretix\n"
|
||||
"Votre équipe pretix%(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email_setup.html:8
|
||||
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
|
||||
@@ -21010,10 +20905,8 @@ msgstr ""
|
||||
"tous les billets, vous pouvez le faire via cette option."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
|
||||
#, fuzzy
|
||||
#| msgid "Permissions"
|
||||
msgid "No permission"
|
||||
msgstr "Autorisations"
|
||||
msgstr "Pas d'autorisation"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
|
||||
@@ -23594,10 +23487,8 @@ msgid "Edit question"
|
||||
msgstr "Modifier la question"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:55
|
||||
#, fuzzy
|
||||
#| msgid "You do not have permission to view this content."
|
||||
msgid "No permission to view answers."
|
||||
msgstr "Vous n'avez pas l'autorisation de consulter ce contenu."
|
||||
msgstr "Vous n'êtes pas autorisé à consulter les réponses."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:63
|
||||
msgid "No matching answers found."
|
||||
@@ -25263,10 +25154,8 @@ msgstr "Pas de prochaine exécution programmée"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:37
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
|
||||
#, fuzzy
|
||||
#| msgid "Exporter not found"
|
||||
msgid "Exporter not found or no permission"
|
||||
msgstr "Exportateur introuvable"
|
||||
msgstr "Exportateur introuvable ou autorisation refusée"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:42
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
|
||||
@@ -25309,10 +25198,8 @@ msgstr "Recommandé pour les nouveaux utilisateurs"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:120
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
|
||||
#, fuzzy
|
||||
#| msgid "There are no add-ons available for this product."
|
||||
msgid "There are no exporters available for you."
|
||||
msgstr "Il n' y a pas d'add-ons disponibles pour ce produit."
|
||||
msgstr "Aucun exportateur n'est disponible pour vous."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
|
||||
@@ -27277,7 +27164,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
|
||||
#, python-format
|
||||
msgid "max. %(size)s, smaller is better"
|
||||
msgstr ""
|
||||
msgstr "max. %(size)s, le plus petit possible"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
|
||||
msgid "Download current background"
|
||||
@@ -30089,16 +29976,12 @@ msgstr ""
|
||||
"plus de détails sur les erreurs."
|
||||
|
||||
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Your user account does not have sufficient permission to run this report, "
|
||||
#| "therefore you cannot schedule it."
|
||||
msgid ""
|
||||
"Your user account does not have sufficient permission to run this report, "
|
||||
"therefore you cannot change it."
|
||||
msgstr ""
|
||||
"Votre compte utilisateur ne dispose pas des autorisations suffisantes pour "
|
||||
"exécuter ce rapport, vous ne pouvez donc pas le planifier."
|
||||
"Votre compte utilisateur ne dispose pas des autorisations nécessaires pour "
|
||||
"exécuter ce rapport ; vous ne pouvez donc pas le modifier."
|
||||
|
||||
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
|
||||
msgid ""
|
||||
@@ -34212,7 +34095,7 @@ msgstr "iDEAL via Stripe"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
@@ -37253,6 +37136,9 @@ msgid ""
|
||||
"the shop that affect quotas, such as the validity period of carts and "
|
||||
"vouchers."
|
||||
msgstr ""
|
||||
"Veuillez noter que la modification de l'heure n'est pas prise en compte pour "
|
||||
"les aspects de la boutique qui ont une incidence sur les quotas, tels que la "
|
||||
"durée de validité des paniers et des bons d'achat."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
|
||||
msgid "Enable time machine"
|
||||
|
||||
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||
"PO-Revision-Date: 2026-01-27 14:51+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"fr/>\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -59,7 +59,7 @@ msgstr "PayPal Pay Later"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
|
||||
@@ -7,10 +7,10 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-14 22:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
|
||||
">\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
|
||||
"\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -407,10 +407,8 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py:482
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "Accountinformatie"
|
||||
msgstr "Uitnodiging voor account"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:503
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -3948,10 +3946,8 @@ msgid "Peppol participant ID"
|
||||
msgstr "Peppol-deelnemer-ID"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
#, fuzzy
|
||||
#| msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr "De Peppol-deelnemer-ID is niet geregistreerd op het Peppol-netwerk."
|
||||
msgstr "Het Peppol-deelnemersnummer komt niet overeen met uw btw-nummer."
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -6874,10 +6870,8 @@ msgstr ""
|
||||
"minuten duren voordat de instelling voor alle gebruikers van kracht wordt."
|
||||
|
||||
#: pretix/base/models/organizer.py:378
|
||||
#, fuzzy
|
||||
#| msgid "Event permissions"
|
||||
msgid "All event permissions"
|
||||
msgstr "Evenementrechten"
|
||||
msgstr "Alle evenementrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:379
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
@@ -6885,10 +6879,8 @@ msgid "Event permissions"
|
||||
msgstr "Evenementrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:380
|
||||
#, fuzzy
|
||||
#| msgid "Organizer permissions"
|
||||
msgid "All organizer permissions"
|
||||
msgstr "Organisatorrechten"
|
||||
msgstr "Alle rechten van de organisator"
|
||||
|
||||
#: pretix/base/models/organizer.py:381
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
@@ -8393,36 +8385,32 @@ msgstr "Uw lay-outbestand is geen geldige lay-out. Foutmelding: {}"
|
||||
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
|
||||
msgctxt "permission_level"
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "Lezen"
|
||||
|
||||
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
|
||||
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
|
||||
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Save and check"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "Opslaan en controleren"
|
||||
msgstr "Bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
#, fuzzy
|
||||
#| msgid "API tokens"
|
||||
msgid "API only"
|
||||
msgstr "API-tokens"
|
||||
msgstr "alleen API"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
msgstr ""
|
||||
"Dit menu-item wordt alleen weergegeven als de gebruiker rechten heeft voor "
|
||||
"algemene instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
|
||||
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
|
||||
#: pretix/base/permissions.py:330
|
||||
#, fuzzy
|
||||
#| msgid "Revoke access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "Toegang intrekken"
|
||||
msgstr "Geen toegang"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
@@ -8436,6 +8424,8 @@ msgid ""
|
||||
"This includes access to all settings not listed explicitly below, including "
|
||||
"plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle instellingen die hieronder niet expliciet worden "
|
||||
"vermeld, inclusief instellingen voor plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:197
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:6
|
||||
@@ -8449,100 +8439,81 @@ msgid "Tax settings"
|
||||
msgstr "Belastinginstellingen"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice settings"
|
||||
msgid "Invoicing settings"
|
||||
msgstr "Factuurinstellingen"
|
||||
|
||||
#: pretix/base/permissions.py:215
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event series date added"
|
||||
msgid "Event series dates"
|
||||
msgstr "Evenementenreeks: datum toegevoegd"
|
||||
msgstr "Datums van de evenementenreeks"
|
||||
|
||||
#: pretix/base/permissions.py:221
|
||||
#, fuzzy
|
||||
#| msgid "Product name and variation"
|
||||
msgid "Products, quotas and questions"
|
||||
msgstr "Productnaam en variant"
|
||||
msgstr "Producten, quota en vragen"
|
||||
|
||||
#: pretix/base/permissions.py:224
|
||||
msgid "Also includes related objects like categories or discounts."
|
||||
msgstr ""
|
||||
msgstr "Omvat ook gerelateerde objecten zoals categorieën of kortingen."
|
||||
|
||||
#: pretix/base/permissions.py:232
|
||||
#, fuzzy
|
||||
#| msgid "All check-ins"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "Alle check-ins"
|
||||
msgstr "Alleen inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
#, fuzzy
|
||||
#| msgid "View full log"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr "Toon volledige log"
|
||||
msgstr "Alles bekijken"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Valid check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "Geldige check-in"
|
||||
msgstr "Alles bekijken en inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
#, fuzzy
|
||||
#| msgid "View all upcoming events"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and change"
|
||||
msgstr "Bekijk alle aankomende evenementen"
|
||||
msgstr "Alles bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:236
|
||||
msgid "Includes the ability to cancel and refund individual orders."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om afzonderlijke bestellingen te annuleren en terug te "
|
||||
"betalen."
|
||||
|
||||
#: pretix/base/permissions.py:238
|
||||
#, fuzzy
|
||||
#| msgid "An entry has been added to the waiting list."
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr "Er is een inschrijving toegevoegd aan de wachtlijst."
|
||||
msgstr "Bevat ook aanverwante objecten, zoals de wachtlijst."
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgid "Generate cancellation"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "Genereer annulering"
|
||||
msgstr "Volledige annulering van het evenement of de datum"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
#, fuzzy
|
||||
#| msgid "Sale not allowed"
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr "Verkoop niet toegestaan"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Allowed titles"
|
||||
msgctxt "permission_level"
|
||||
msgid "Allowed"
|
||||
msgstr "Toegestane titels"
|
||||
msgstr "Toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:268
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen"
|
||||
|
||||
#: pretix/base/permissions.py:269
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing and create new events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen en nieuwe evenementen aanmaken"
|
||||
|
||||
#: pretix/base/permissions.py:271
|
||||
msgid ""
|
||||
"The level of access to events is determined in detail by the settings below."
|
||||
msgstr ""
|
||||
"De toegang tot evenementen wordt in detail bepaald door de onderstaande "
|
||||
"instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
|
||||
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
|
||||
@@ -8561,12 +8532,15 @@ msgid ""
|
||||
"This includes access to all organizer-level functionality not listed "
|
||||
"explicitly below, including plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle functies op organisatieniveau die hieronder niet "
|
||||
"expliciet worden vermeld, inclusief de instellingen van plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:287
|
||||
msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om iemand (inclusief uzelf) extra rechten te geven."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8584,12 +8558,12 @@ msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om toegang te verlenen tot evenementen en gegevens "
|
||||
"waar men zelf geen toegang toe heeft."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Seating plan"
|
||||
msgid "Seating plans"
|
||||
msgstr "Zaalplan"
|
||||
msgstr "Zaalplannen"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
@@ -9224,12 +9198,12 @@ msgid "Czech National Bank"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
|
||||
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Export not found or you do not have sufficient permission to perform this "
|
||||
"export."
|
||||
msgstr "U bent niet gemachtigd om deze export uit te voeren."
|
||||
msgstr ""
|
||||
"De export is niet gevonden of u hebt onvoldoende rechten om deze export uit "
|
||||
"te voeren."
|
||||
|
||||
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
|
||||
#: pretix/base/services/export.py:357
|
||||
@@ -13684,25 +13658,7 @@ msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13737,7 +13693,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groeten,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
|
||||
msgid ""
|
||||
@@ -14298,16 +14254,12 @@ msgid "This is an event series"
|
||||
msgstr "Dit is een evenementenreeks"
|
||||
|
||||
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You do not have sufficient permission to enable plugins that need to be "
|
||||
#| "enabled for the entire organizer account."
|
||||
msgid ""
|
||||
"You do not have a sufficient level of access on the event you selected to "
|
||||
"copy it to the desired organizer."
|
||||
msgstr ""
|
||||
"U hebt niet voldoende rechten om plug-ins in te schakelen die voor het hele "
|
||||
"organisatoraccount moeten worden ingeschakeld."
|
||||
"U hebt onvoldoende rechten voor het evenement dat u hebt geselecteerd om het "
|
||||
"naar de gewenste organisator te kopiëren."
|
||||
|
||||
#: pretix/control/forms/event.py:143
|
||||
msgid ""
|
||||
@@ -14386,6 +14338,8 @@ msgid ""
|
||||
"You cannot choose a team that would give you more access than you have on "
|
||||
"the event you are copying."
|
||||
msgstr ""
|
||||
"U kunt geen team kiezen dat u meer toegangsrechten zou geven dan u nu hebt "
|
||||
"voor het evenement dat u kopieert."
|
||||
|
||||
#: pretix/control/forms/event.py:344
|
||||
msgid "Copy configuration from"
|
||||
@@ -14401,6 +14355,8 @@ msgid ""
|
||||
"You cannot choose an event on which you have less access than the team you "
|
||||
"selected in the previous step."
|
||||
msgstr ""
|
||||
"U kunt geen evenement kiezen waarvoor u minder toegangsrechten hebt dan het "
|
||||
"team dat u in de vorige stap geselecteerd hebt."
|
||||
|
||||
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
|
||||
#: pretix/control/forms/subevents.py:411
|
||||
@@ -15357,7 +15313,7 @@ msgstr "Zoek deelnemer…"
|
||||
#: pretix/control/forms/filter.py:2039
|
||||
#: pretix/plugins/checkinlists/exporters.py:106
|
||||
msgid "Check-in status"
|
||||
msgstr "Incheckstatus"
|
||||
msgstr "Check-in-status"
|
||||
|
||||
#: pretix/control/forms/filter.py:2041
|
||||
#: pretix/plugins/checkinlists/exporters.py:108
|
||||
@@ -16770,7 +16726,7 @@ msgstr "Er kan maximaal 1 organisatordomein gekozen worden."
|
||||
|
||||
#: pretix/control/forms/organizer.py:322
|
||||
msgid "Provided by a plugin"
|
||||
msgstr ""
|
||||
msgstr "Beschikbaar gesteld via een plug-in"
|
||||
|
||||
#: pretix/control/forms/organizer.py:438
|
||||
msgid ""
|
||||
@@ -17646,10 +17602,8 @@ msgid "The order has been denied (comment: \"{comment}\")."
|
||||
msgstr "De bestelling is geweigerd (commentaar: \"{comment}\")."
|
||||
|
||||
#: pretix/control/logdisplay.py:521
|
||||
#, fuzzy
|
||||
#| msgid "The order has been overpaid."
|
||||
msgid "The customer VAT ID has been verified."
|
||||
msgstr "Er is te veel betaald voor de bestelling."
|
||||
msgstr "Het btw-nummer van de klant is geverifieerd."
|
||||
|
||||
#: pretix/control/logdisplay.py:522
|
||||
#, python-brace-format
|
||||
@@ -17951,10 +17905,9 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} is uitgenodigd voor het team."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Invite for {user} has been resent."
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "De uitnodiging voor {user} is opnieuw verstuurd."
|
||||
msgstr "De uitnodiging voor {user} is verwijderd."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, python-brace-format
|
||||
@@ -20259,21 +20212,7 @@ msgid "Add property"
|
||||
msgstr "Eigenschap toevoegen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "%(reason)s\n"
|
||||
#| "\n"
|
||||
#| " %(code)s\n"
|
||||
#| "\n"
|
||||
#| "Please do never give this code to another person. Our support team will "
|
||||
#| "never ask for this code.\n"
|
||||
#| "\n"
|
||||
#| "If this code was not requested by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20302,7 +20241,7 @@ msgstr ""
|
||||
"op.\n"
|
||||
"\n"
|
||||
"Groeten,\n"
|
||||
"Uw pretix-team\n"
|
||||
"Uw %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
|
||||
#, python-format
|
||||
@@ -20338,17 +20277,7 @@ msgstr ""
|
||||
"Uw %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you requested a new password. Please go to the following page to reset "
|
||||
#| "your password:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20368,27 +20297,10 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you have been invited to a team on pretix, a platform to perform event\n"
|
||||
#| "ticket sales.\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "Team: %(team)s\n"
|
||||
#| "\n"
|
||||
#| "If you want to join that team, just click on the following link:\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "If you do not want to join, you can safely ignore or delete this email.\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20410,7 +20322,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"U bent uitgenodigd voor een team op pretix, een platform om tickets\n"
|
||||
"U bent uitgenodigd voor een team op %(instance)s, een platform om tickets\n"
|
||||
"te verkopen.\n"
|
||||
"\n"
|
||||
"Organisator: %(organizer)s\n"
|
||||
@@ -20423,7 +20335,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
|
||||
#, python-format
|
||||
@@ -20461,24 +20373,7 @@ msgstr ""
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "this is to inform you that the account information of your pretix account "
|
||||
#| "has been\n"
|
||||
#| "changed. In particular, the following changes have been performed:\n"
|
||||
#| "\n"
|
||||
#| "%(messages)s\n"
|
||||
#| "\n"
|
||||
#| "If this change was not performed by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "You can review and change your account settings here:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20499,7 +20394,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw pretix-"
|
||||
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw %(instance)s-"
|
||||
"account.\n"
|
||||
"De volgende wijzigingen zijn gemaakt:\n"
|
||||
"\n"
|
||||
@@ -20513,7 +20408,7 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email_setup.html:8
|
||||
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
|
||||
@@ -20780,10 +20675,8 @@ msgstr ""
|
||||
"kunt u dit via deze optie doen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
|
||||
#, fuzzy
|
||||
#| msgid "Permissions"
|
||||
msgid "No permission"
|
||||
msgstr "Machtigingen"
|
||||
msgstr "Geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
|
||||
@@ -23331,10 +23224,8 @@ msgid "Edit question"
|
||||
msgstr "Vraag bewerken"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:55
|
||||
#, fuzzy
|
||||
#| msgid "You do not have permission to view this content."
|
||||
msgid "No permission to view answers."
|
||||
msgstr "U hebt geen toestemming om deze inhoud te bekijken."
|
||||
msgstr "U bent niet gemachtigd om de antwoorden te bekijken."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:63
|
||||
msgid "No matching answers found."
|
||||
@@ -24331,8 +24222,9 @@ msgid ""
|
||||
"this product was part of the discount calculation for a different product in "
|
||||
"this order."
|
||||
msgstr ""
|
||||
"De prijs van dit product is verminderd door een automatische korting, of dit "
|
||||
"product is deel van een korting voor een ander product op deze bestelling."
|
||||
"De prijs van dit product is verlaagd vanwege een automatische korting, of "
|
||||
"dit product is meegenomen in de kortingsberekening voor een ander product in "
|
||||
"deze bestelling."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:496
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:103
|
||||
@@ -24986,10 +24878,8 @@ msgstr "Geen volgende uitvoering gepland"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:37
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
|
||||
#, fuzzy
|
||||
#| msgid "Exporter not found"
|
||||
msgid "Exporter not found or no permission"
|
||||
msgstr "Exporteerder niet gevonden"
|
||||
msgstr "Exporteur niet gevonden of geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:42
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
|
||||
@@ -25032,10 +24922,8 @@ msgstr "Aangeraden voor nieuwe gebruikers"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:120
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
|
||||
#, fuzzy
|
||||
#| msgid "There are no add-ons available for this product."
|
||||
msgid "There are no exporters available for you."
|
||||
msgstr "Er zijn geen add-ons beschikbaar voor dit product."
|
||||
msgstr "Er zijn geen exporters voor u beschikbaar."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
|
||||
@@ -26973,7 +26861,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
|
||||
#, python-format
|
||||
msgid "max. %(size)s, smaller is better"
|
||||
msgstr ""
|
||||
msgstr "max. %(size)s; hoe kleiner, hoe beter"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
|
||||
msgid "Download current background"
|
||||
@@ -29751,16 +29639,12 @@ msgstr ""
|
||||
"Er was een probleem met het verwerken van uw invoer. Zie onder voor details."
|
||||
|
||||
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Your user account does not have sufficient permission to run this report, "
|
||||
#| "therefore you cannot schedule it."
|
||||
msgid ""
|
||||
"Your user account does not have sufficient permission to run this report, "
|
||||
"therefore you cannot change it."
|
||||
msgstr ""
|
||||
"Uw gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren. "
|
||||
"Daarom kunt u het niet plannen."
|
||||
"Daarom kunt u het niet wijzigen."
|
||||
|
||||
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
|
||||
msgid ""
|
||||
@@ -33816,7 +33700,7 @@ msgstr "iDEAL via Stripe"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
@@ -36802,6 +36686,9 @@ msgid ""
|
||||
"the shop that affect quotas, such as the validity period of carts and "
|
||||
"vouchers."
|
||||
msgstr ""
|
||||
"Let op: de gewijzigde tijd wordt niet meegenomen bij aspecten van de winkel "
|
||||
"die van invloed zijn op quota, zoals de geldigheidsduur van winkelwagentjes "
|
||||
"en kortingsbonnen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
|
||||
msgid "Enable time machine"
|
||||
|
||||
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||
"PO-Revision-Date: 2026-02-19 22:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"nl/>\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -59,7 +59,7 @@ msgstr "PayPal - Later betalen"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-16 18:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 23:00+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix/nl_BE/>\n"
|
||||
@@ -408,10 +408,8 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py:482
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "pretix account invitation"
|
||||
msgid "Account invitation"
|
||||
msgstr "uitnodiging voor pretix-account"
|
||||
msgstr "Uitnodiging voor account"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:503
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -3949,10 +3947,8 @@ msgid "Peppol participant ID"
|
||||
msgstr "Peppol-ID"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
#, fuzzy
|
||||
#| msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr "De ID is niet geregistreerd op het Peppol-netwerk."
|
||||
msgstr "Het Peppol-deelnemersnummer komt niet overeen met uw btw-nummer."
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -6865,10 +6861,8 @@ msgstr ""
|
||||
"voordat de instelling voor alle gebruikers van kracht wordt."
|
||||
|
||||
#: pretix/base/models/organizer.py:378
|
||||
#, fuzzy
|
||||
#| msgid "Event permissions"
|
||||
msgid "All event permissions"
|
||||
msgstr "Evenementrechten"
|
||||
msgstr "Alle evenementrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:379
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
@@ -6876,10 +6870,8 @@ msgid "Event permissions"
|
||||
msgstr "Evenementrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:380
|
||||
#, fuzzy
|
||||
#| msgid "Organizer permissions"
|
||||
msgid "All organizer permissions"
|
||||
msgstr "Organisatorrechten"
|
||||
msgstr "Alle organisatorrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:381
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
@@ -8388,36 +8380,32 @@ msgstr "Uw lay-outbestand is geen geldige lay-out. Foutmelding: {}"
|
||||
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
|
||||
msgctxt "permission_level"
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "Lezen"
|
||||
|
||||
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
|
||||
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
|
||||
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Save and check"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "Opslaan en controleren"
|
||||
msgstr "Bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
#, fuzzy
|
||||
#| msgid "API tokens"
|
||||
msgid "API only"
|
||||
msgstr "API-tokens"
|
||||
msgstr "alleen API"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
msgstr ""
|
||||
"Dit menu-item wordt alleen weergegeven als de gebruiker rechten heeft voor "
|
||||
"algemene instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
|
||||
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
|
||||
#: pretix/base/permissions.py:330
|
||||
#, fuzzy
|
||||
#| msgid "Revoke access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "Toegang intrekken"
|
||||
msgstr "Geen toegang"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
@@ -8431,6 +8419,8 @@ msgid ""
|
||||
"This includes access to all settings not listed explicitly below, including "
|
||||
"plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle instellingen die hieronder niet expliciet worden "
|
||||
"vermeld, inclusief instellingen voor plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:197
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:6
|
||||
@@ -8444,100 +8434,81 @@ msgid "Tax settings"
|
||||
msgstr "Belastinginstellingen"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice settings"
|
||||
msgid "Invoicing settings"
|
||||
msgstr "Factuurinstellingen"
|
||||
msgstr "Facturatie-instellingen"
|
||||
|
||||
#: pretix/base/permissions.py:215
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event series date added"
|
||||
msgid "Event series dates"
|
||||
msgstr "Evenementenreeks: datum toegevoegd"
|
||||
msgstr "Datums van de evenementenreeks"
|
||||
|
||||
#: pretix/base/permissions.py:221
|
||||
#, fuzzy
|
||||
#| msgid "Product name and variation"
|
||||
msgid "Products, quotas and questions"
|
||||
msgstr "Productnaam en variant"
|
||||
msgstr "Producten, quota en vragen"
|
||||
|
||||
#: pretix/base/permissions.py:224
|
||||
msgid "Also includes related objects like categories or discounts."
|
||||
msgstr ""
|
||||
msgstr "Omvat ook gerelateerde objecten zoals categorieën of kortingen."
|
||||
|
||||
#: pretix/base/permissions.py:232
|
||||
#, fuzzy
|
||||
#| msgid "All check-ins"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "Alle check-ins"
|
||||
msgstr "Alleen inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
#, fuzzy
|
||||
#| msgid "View full log"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr "Alles weergeven"
|
||||
msgstr "Alles bekijken"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Valid check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "Geldige check-in"
|
||||
msgstr "Alles bekijken en inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
#, fuzzy
|
||||
#| msgid "View all upcoming events"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and change"
|
||||
msgstr "Alle komende evenementen weergeven"
|
||||
msgstr "Alles bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:236
|
||||
msgid "Includes the ability to cancel and refund individual orders."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om afzonderlijke bestellingen te annuleren en terug te "
|
||||
"betalen."
|
||||
|
||||
#: pretix/base/permissions.py:238
|
||||
#, fuzzy
|
||||
#| msgid "An entry has been added to the waiting list."
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr "Er is een inschrijving toegevoegd aan de wachtlijst."
|
||||
msgstr "Bevat ook aanverwante objecten, zoals de wachtlijst."
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgid "Generate cancellation"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "Annulatiebewijs aanmaken"
|
||||
msgstr "Volledige annulatie van het evenement of de datum"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
#, fuzzy
|
||||
#| msgid "Sale not allowed"
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr "Verkoop niet toegestaan"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Allowed titles"
|
||||
msgctxt "permission_level"
|
||||
msgid "Allowed"
|
||||
msgstr "Toegestane titels"
|
||||
msgstr "Toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:268
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen"
|
||||
|
||||
#: pretix/base/permissions.py:269
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing and create new events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen en nieuwe evenementen aanmaken"
|
||||
|
||||
#: pretix/base/permissions.py:271
|
||||
msgid ""
|
||||
"The level of access to events is determined in detail by the settings below."
|
||||
msgstr ""
|
||||
"De toegang tot evenementen wordt in detail bepaald door de onderstaande "
|
||||
"instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
|
||||
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
|
||||
@@ -8556,12 +8527,15 @@ msgid ""
|
||||
"This includes access to all organizer-level functionality not listed "
|
||||
"explicitly below, including plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle functies op organisatieniveau die hieronder niet "
|
||||
"expliciet worden vermeld, inclusief de instellingen van plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:287
|
||||
msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om iemand (inclusief uzelf) extra rechten te geven."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8579,12 +8553,12 @@ msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om toegang te verlenen tot evenementen en gegevens "
|
||||
"waar u zelf geen toegang toe hebt."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Seating plan"
|
||||
msgid "Seating plans"
|
||||
msgstr "Zaalplan"
|
||||
msgstr "Zaalplannen"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
@@ -9220,12 +9194,12 @@ msgid "Czech National Bank"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
|
||||
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Export not found or you do not have sufficient permission to perform this "
|
||||
"export."
|
||||
msgstr "U bent niet gemachtigd om deze export uit te voeren."
|
||||
msgstr ""
|
||||
"De export is niet gevonden of u hebt onvoldoende rechten om deze export uit "
|
||||
"te voeren."
|
||||
|
||||
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
|
||||
#: pretix/base/services/export.py:357
|
||||
@@ -13492,7 +13466,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/templates/403.html:4 pretix/base/templates/403.html:8
|
||||
msgid "Permission denied"
|
||||
msgstr "Geen toestemming"
|
||||
msgstr "Toestemming geweigerd"
|
||||
|
||||
#: pretix/base/templates/403.html:9
|
||||
msgid "You do not have access to this page."
|
||||
@@ -13679,25 +13653,7 @@ msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13732,7 +13688,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groeten,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
|
||||
msgid ""
|
||||
@@ -14293,16 +14249,12 @@ msgid "This is an event series"
|
||||
msgstr "Dit is een evenementenreeks"
|
||||
|
||||
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You do not have sufficient permission to enable plugins that need to be "
|
||||
#| "enabled for the entire organizer account."
|
||||
msgid ""
|
||||
"You do not have a sufficient level of access on the event you selected to "
|
||||
"copy it to the desired organizer."
|
||||
msgstr ""
|
||||
"U beschikt niet over voldoende rechten om plug-ins te activeren die voor het "
|
||||
"hele organisatorenaccount ingeschakeld moeten zijn."
|
||||
"U hebt onvoldoende rechten voor het evenement dat u hebt geselecteerd om het "
|
||||
"naar de gewenste organisator te kopiëren."
|
||||
|
||||
#: pretix/control/forms/event.py:143
|
||||
msgid ""
|
||||
@@ -14381,6 +14333,8 @@ msgid ""
|
||||
"You cannot choose a team that would give you more access than you have on "
|
||||
"the event you are copying."
|
||||
msgstr ""
|
||||
"U kunt geen team kiezen dat u meer toegangsrechten zou geven dan u nu hebt "
|
||||
"voor het evenement dat u kopieert."
|
||||
|
||||
#: pretix/control/forms/event.py:344
|
||||
msgid "Copy configuration from"
|
||||
@@ -14396,6 +14350,8 @@ msgid ""
|
||||
"You cannot choose an event on which you have less access than the team you "
|
||||
"selected in the previous step."
|
||||
msgstr ""
|
||||
"U kunt geen evenement kiezen waarvoor u minder toegangsrechten hebt dan het "
|
||||
"team dat u in de vorige stap geselecteerd hebt."
|
||||
|
||||
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
|
||||
#: pretix/control/forms/subevents.py:411
|
||||
@@ -16763,7 +16719,7 @@ msgstr "Er kan maximaal één organisatordomein gekozen worden."
|
||||
|
||||
#: pretix/control/forms/organizer.py:322
|
||||
msgid "Provided by a plugin"
|
||||
msgstr ""
|
||||
msgstr "Beschikbaar gesteld via een plug-in"
|
||||
|
||||
#: pretix/control/forms/organizer.py:438
|
||||
msgid ""
|
||||
@@ -17638,10 +17594,8 @@ msgid "The order has been denied (comment: \"{comment}\")."
|
||||
msgstr "De bestelling is geweigerd (commentaar: \"{comment}\")."
|
||||
|
||||
#: pretix/control/logdisplay.py:521
|
||||
#, fuzzy
|
||||
#| msgid "The order has been overpaid."
|
||||
msgid "The customer VAT ID has been verified."
|
||||
msgstr "Er is te veel betaald voor de bestelling."
|
||||
msgstr "Het btw-nummer van de klant is geverifieerd."
|
||||
|
||||
#: pretix/control/logdisplay.py:522
|
||||
#, python-brace-format
|
||||
@@ -17943,10 +17897,9 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} is uitgenodigd voor het team."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Invite for {user} has been resent."
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "De uitnodiging voor {user} is opnieuw verstuurd."
|
||||
msgstr "De uitnodiging voor {user} is verwijderd."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, python-brace-format
|
||||
@@ -20255,21 +20208,7 @@ msgid "Add property"
|
||||
msgstr "Eigenschap toevoegen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "%(reason)s\n"
|
||||
#| "\n"
|
||||
#| " %(code)s\n"
|
||||
#| "\n"
|
||||
#| "Please do never give this code to another person. Our support team will "
|
||||
#| "never ask for this code.\n"
|
||||
#| "\n"
|
||||
#| "If this code was not requested by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20298,7 +20237,7 @@ msgstr ""
|
||||
"op.\n"
|
||||
"\n"
|
||||
"Vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
|
||||
#, python-format
|
||||
@@ -20334,17 +20273,7 @@ msgstr ""
|
||||
"Uw %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you requested a new password. Please go to the following page to reset "
|
||||
#| "your password:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20364,27 +20293,10 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you have been invited to a team on pretix, a platform to perform event\n"
|
||||
#| "ticket sales.\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "Team: %(team)s\n"
|
||||
#| "\n"
|
||||
#| "If you want to join that team, just click on the following link:\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "If you do not want to join, you can safely ignore or delete this email.\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20406,7 +20318,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"U bent uitgenodigd voor een team op pretix, een platform om tickets\n"
|
||||
"U bent uitgenodigd voor een team op %(instance)s, een platform om tickets\n"
|
||||
"te verkopen.\n"
|
||||
"\n"
|
||||
"Organisator: %(organizer)s\n"
|
||||
@@ -20419,7 +20331,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
|
||||
#, python-format
|
||||
@@ -20457,24 +20369,7 @@ msgstr ""
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "this is to inform you that the account information of your pretix account "
|
||||
#| "has been\n"
|
||||
#| "changed. In particular, the following changes have been performed:\n"
|
||||
#| "\n"
|
||||
#| "%(messages)s\n"
|
||||
#| "\n"
|
||||
#| "If this change was not performed by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "You can review and change your account settings here:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20495,7 +20390,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw pretix-"
|
||||
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw %(instance)s-"
|
||||
"account.\n"
|
||||
"De volgende wijzigingen zijn gemaakt:\n"
|
||||
"\n"
|
||||
@@ -20509,7 +20404,7 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email_setup.html:8
|
||||
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
|
||||
@@ -20776,10 +20671,8 @@ msgstr ""
|
||||
"kunt u dit via deze optie doen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
|
||||
#, fuzzy
|
||||
#| msgid "Permissions"
|
||||
msgid "No permission"
|
||||
msgstr "Machtigingen"
|
||||
msgstr "Geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
|
||||
@@ -23326,10 +23219,8 @@ msgid "Edit question"
|
||||
msgstr "Vraag bewerken"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:55
|
||||
#, fuzzy
|
||||
#| msgid "You do not have permission to view this content."
|
||||
msgid "No permission to view answers."
|
||||
msgstr "U bent niet gemachtigd om deze inhoud te bekijken."
|
||||
msgstr "U bent niet gemachtigd om de antwoorden te bekijken."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:63
|
||||
msgid "No matching answers found."
|
||||
@@ -24982,10 +24873,8 @@ msgstr "Geen volgende uitvoering gepland"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:37
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
|
||||
#, fuzzy
|
||||
#| msgid "Exporter not found"
|
||||
msgid "Exporter not found or no permission"
|
||||
msgstr "Exporteerder niet gevonden"
|
||||
msgstr "Exporteur niet gevonden of geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:42
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
|
||||
@@ -25029,7 +24918,7 @@ msgstr "Aanbevolen voor nieuwe gebruikers"
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:120
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
|
||||
msgid "There are no exporters available for you."
|
||||
msgstr ""
|
||||
msgstr "Er zijn geen exporters voor u beschikbaar."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
|
||||
@@ -26960,7 +26849,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
|
||||
#, python-format
|
||||
msgid "max. %(size)s, smaller is better"
|
||||
msgstr ""
|
||||
msgstr "max. %(size)s; hoe kleiner, hoe beter"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
|
||||
msgid "Download current background"
|
||||
@@ -29121,64 +29010,68 @@ msgstr "De geselecteerde vraag is verwijderd."
|
||||
|
||||
#: pretix/control/views/item.py:690
|
||||
msgid "File uploaded"
|
||||
msgstr ""
|
||||
msgstr "Bestand geüpload"
|
||||
|
||||
#: pretix/control/views/item.py:824
|
||||
msgid "The new question has been created."
|
||||
msgstr ""
|
||||
msgstr "De nieuwe vraag is aangemaakt."
|
||||
|
||||
#: pretix/control/views/item.py:903
|
||||
msgid "The new quota has been created."
|
||||
msgstr ""
|
||||
msgstr "Het nieuwe quotum is aangemaakt."
|
||||
|
||||
#: pretix/control/views/item.py:966
|
||||
msgid "Exit scans"
|
||||
msgstr ""
|
||||
msgstr "Scans bij vertrek"
|
||||
|
||||
#: pretix/control/views/item.py:973
|
||||
msgid "Vouchers and waiting list reservations"
|
||||
msgstr ""
|
||||
msgstr "Vouchers en wachtlijstreserveringen"
|
||||
|
||||
#: pretix/control/views/item.py:988
|
||||
msgid "Available quota"
|
||||
msgstr ""
|
||||
msgstr "Beschikbaar quotum"
|
||||
|
||||
#: pretix/control/views/item.py:994
|
||||
msgid "Waiting list (pending)"
|
||||
msgstr ""
|
||||
msgstr "Wachtlijst (openstaand)"
|
||||
|
||||
#: pretix/control/views/item.py:1001
|
||||
msgid "Currently for sale"
|
||||
msgstr ""
|
||||
msgstr "Momenteel te koop"
|
||||
|
||||
#: pretix/control/views/item.py:1055 pretix/control/views/item.py:1101
|
||||
#: pretix/control/views/item.py:1155
|
||||
msgid "The requested quota does not exist."
|
||||
msgstr ""
|
||||
msgstr "Het gevraagde quotum bestaat niet."
|
||||
|
||||
#: pretix/control/views/item.py:1076
|
||||
msgid "The quota has been re-opened and will not close again."
|
||||
msgstr ""
|
||||
msgstr "Het quotum is heropend en zal niet automatisch opnieuw sluiten."
|
||||
|
||||
#: pretix/control/views/item.py:1169
|
||||
msgid "The selected quota has been deleted."
|
||||
msgstr ""
|
||||
msgstr "Het geselecteerde quotum is verwijderd."
|
||||
|
||||
#: pretix/control/views/item.py:1192
|
||||
msgid "The requested item does not exist."
|
||||
msgstr ""
|
||||
msgstr "Het gevraagde item bestaat niet."
|
||||
|
||||
#: pretix/control/views/item.py:1364
|
||||
msgid ""
|
||||
"You cannot add add-ons to a product that is only available as an add-on "
|
||||
"itself."
|
||||
msgstr ""
|
||||
"U kunt geen add-ons toevoegen aan een product dat zelf alleen beschikbaar is "
|
||||
"als add-on."
|
||||
|
||||
#: pretix/control/views/item.py:1374
|
||||
msgid ""
|
||||
"You cannot add bundles to a product that is only available as an add-on "
|
||||
"itself."
|
||||
msgstr ""
|
||||
"U kunt geen bundels toevoegen aan een product dat zelf alleen beschikbaar is "
|
||||
"als bundel."
|
||||
|
||||
#: pretix/control/views/item.py:1517
|
||||
msgid ""
|
||||
@@ -29186,6 +29079,9 @@ msgid ""
|
||||
"participants won't be able to buy the bundle unless you remove this item "
|
||||
"from it."
|
||||
msgstr ""
|
||||
"U hebt dit item uitgeschakeld, maar het maakt nog steeds deel uit van een "
|
||||
"productbundel. Uw deelnemers kunnen de bundel niet kopen, tenzij u dit item "
|
||||
"uit de bundel verwijdert."
|
||||
|
||||
#: pretix/control/views/item.py:1626
|
||||
msgid ""
|
||||
@@ -29193,28 +29089,33 @@ msgid ""
|
||||
"plug-ins) did not allow it. Deleting it could break reporting or other "
|
||||
"functionality, so the product has been disabled instead."
|
||||
msgstr ""
|
||||
"Het product kon niet worden verwijderd omdat bepaalde beperkingen "
|
||||
"(bijvoorbeeld gegevens die door plug-ins aangemaakt zijn) dit niet "
|
||||
"toelieten. Het verwijderen ervan zou de rapportage of andere "
|
||||
"functionaliteiten kunnen verstoren. Daarom is het product in plaats daarvan "
|
||||
"uitgeschakeld."
|
||||
|
||||
#: pretix/control/views/item.py:1630
|
||||
msgid "The selected product has been deleted."
|
||||
msgstr ""
|
||||
msgstr "Het gekozen product is verwijderd."
|
||||
|
||||
#: pretix/control/views/item.py:1639
|
||||
msgid "The selected product has been deactivated."
|
||||
msgstr ""
|
||||
msgstr "Het gekozen product is uitgeschakeld."
|
||||
|
||||
#: pretix/control/views/mail.py:161
|
||||
#, python-brace-format
|
||||
msgid "A retry of one email was scheduled."
|
||||
msgid_plural "A retry of {num} emails was scheduled."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Er is een nieuwe verzendpoging voor één e-mail gepland."
|
||||
msgstr[1] "Er is een nieuwe verzendpoging voor {num} e-mails gepland."
|
||||
|
||||
#: pretix/control/views/mail.py:185
|
||||
#, python-brace-format
|
||||
msgid "One email was aborted and will not be sent."
|
||||
msgid_plural "{num} emails were aborted and will not be sent."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Eén e-mail is afgebroken en niet verzonden."
|
||||
msgstr[1] "{num} e-mails zijn afgebroken en niet verzonden."
|
||||
|
||||
#: pretix/control/views/mailsetup.py:200
|
||||
msgid ""
|
||||
@@ -29224,6 +29125,11 @@ msgid ""
|
||||
"the domain. You can do so through the DNS settings at the provider you "
|
||||
"registered your domain with."
|
||||
msgstr ""
|
||||
"We konden geen SPF-record vinden voor het domein dat u wilt gebruiken. Dat "
|
||||
"betekent dat de kans zeer groot is dat de meeste e-mails worden geweigerd of "
|
||||
"als spam worden gemarkeerd. We raden u ten zeerste aan om een SPF-record in "
|
||||
"te stellen voor het domein. U kunt dit doen via de DNS-instellingen bij de "
|
||||
"provider waar u uw domein geregistreerd hebt."
|
||||
|
||||
#: pretix/control/views/mailsetup.py:207
|
||||
msgid ""
|
||||
@@ -29233,19 +29139,24 @@ msgid ""
|
||||
"update the DNS settings of your domain to include this system in the SPF "
|
||||
"record."
|
||||
msgstr ""
|
||||
"We hebben een SPF-record gevonden voor het domein dat u wilt gebruiken, maar "
|
||||
"het bevat niet de e-mailserver van dit systeem. Dat betekent dat de kans "
|
||||
"zeer groot is dat de meeste e-mails geweigerd of als spam gemarkeerd worden. "
|
||||
"U moet de DNS-instellingen van uw domein bijwerken om dit systeem op te "
|
||||
"nemen in het SPF-record."
|
||||
|
||||
#: pretix/control/views/mailsetup.py:216
|
||||
msgid "The verification code was incorrect, please try again."
|
||||
msgstr ""
|
||||
msgstr "De verificatiecode was niet correct, probeer het opnieuw."
|
||||
|
||||
#: pretix/control/views/mailsetup.py:221
|
||||
msgid "Sender address verification"
|
||||
msgstr ""
|
||||
msgstr "Verificatie van het adres van de afzender"
|
||||
|
||||
#: pretix/control/views/mailsetup.py:277
|
||||
#, python-format
|
||||
msgid "An error occurred while contacting the SMTP server: %s"
|
||||
msgstr ""
|
||||
msgstr "Er is een fout opgetreden tijdens het verbinden met de SMTP-server: %s"
|
||||
|
||||
#: pretix/control/views/mailsetup.py:288
|
||||
msgid ""
|
||||
@@ -29255,29 +29166,36 @@ msgid ""
|
||||
"all of your emails since they impose a maximum number of emails per time "
|
||||
"period."
|
||||
msgstr ""
|
||||
"We raden u aan Google Mail niet te gebruiken voor transactionele e-mails. "
|
||||
"Als u in korte tijd veel e-mails probeert te verzenden, bijvoorbeeld als u "
|
||||
"informatie naar al uw ticketkopers verzendt, is de kans groot dat Google de "
|
||||
"e-mails niet aflevert, omdat Google een maximumaantal e-mails per periode "
|
||||
"instelt."
|
||||
|
||||
#: pretix/control/views/main.py:221
|
||||
msgid "You do not have permission to clone this event."
|
||||
msgstr ""
|
||||
msgstr "U hebt geen toestemming om dit evenement te klonen."
|
||||
|
||||
#: pretix/control/views/main.py:317
|
||||
#, python-brace-format
|
||||
msgid "Team {event}"
|
||||
msgstr ""
|
||||
msgstr "Team {event}"
|
||||
|
||||
#: pretix/control/views/modelimport.py:77
|
||||
msgid "Please only upload CSV files."
|
||||
msgstr ""
|
||||
msgstr "Upload alleen CSV-bestanden."
|
||||
|
||||
#: pretix/control/views/modelimport.py:80
|
||||
msgid "Please do not upload files larger than 10 MB."
|
||||
msgstr ""
|
||||
msgstr "Upload geen bestanden groter dan 10 MB."
|
||||
|
||||
#: pretix/control/views/modelimport.py:159
|
||||
msgid ""
|
||||
"We could not identify the character encoding of the CSV file. Some "
|
||||
"characters were replaced with a placeholder."
|
||||
msgstr ""
|
||||
"We konden de codering van het CSV-bestand niet detecteren. Sommige tekens "
|
||||
"zijn vervangen door een vervangingsteken."
|
||||
|
||||
#: pretix/control/views/modelimport.py:168
|
||||
msgid ""
|
||||
@@ -29285,14 +29203,17 @@ msgid ""
|
||||
"automatically. We recommend that you rename these in your source file to "
|
||||
"avoid problems during import."
|
||||
msgstr ""
|
||||
"Meerdere kolommen van het CSV-bestand hebben dezelfde naam en zijn "
|
||||
"automatisch hernoemd. We raden u aan deze in uw bronbestand te hernoemen om "
|
||||
"problemen tijdens het importeren te voorkomen."
|
||||
|
||||
#: pretix/control/views/modelimport.py:188
|
||||
msgid "The import was successful."
|
||||
msgstr ""
|
||||
msgstr "De import is gelukt."
|
||||
|
||||
#: pretix/control/views/modelimport.py:200
|
||||
msgid "We've been unable to parse the uploaded file as a CSV file."
|
||||
msgstr ""
|
||||
msgstr "We konden het geüploade bestand niet openen als een CSV-bestand."
|
||||
|
||||
#: pretix/control/views/oauth.py:69
|
||||
#, python-brace-format
|
||||
@@ -29301,6 +29222,8 @@ msgid ""
|
||||
"generated. Please copy and save it right now as it will not be shown again: "
|
||||
"{secret}"
|
||||
msgstr ""
|
||||
"Uw aanvraag is aangemaakt en er is een geheime code gegenereerd. Kopieer en "
|
||||
"bewaar deze nu meteen, want ze wordt niet nogmaals getoond: {secret}"
|
||||
|
||||
#: pretix/control/views/oauth.py:107
|
||||
#, python-brace-format
|
||||
@@ -29308,84 +29231,94 @@ msgid ""
|
||||
"A new client secret has been generated. Please copy and save it right now as "
|
||||
"it will not be shown again: {secret}"
|
||||
msgstr ""
|
||||
"De nieuwe client is aangemaakt. Kopieer en bewaar de volgende client-secret, "
|
||||
"aangezien deze niet opnieuw wordt weergegeven: {secret}"
|
||||
|
||||
#: pretix/control/views/oauth.py:169
|
||||
msgid "Access for the selected application has been revoked."
|
||||
msgstr ""
|
||||
msgstr "De toegang voor de gekozen applicatie is ingetrokken."
|
||||
|
||||
#: pretix/control/views/orders.py:196
|
||||
msgid "We could not process your input. See below for details."
|
||||
msgstr ""
|
||||
msgstr "Uw input kon niet worden verwerkt. Zie hieronder voor meer informatie."
|
||||
|
||||
#: pretix/control/views/orders.py:269
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Successfully executed the action \"{label}\" on {success} of {total} orders."
|
||||
msgstr ""
|
||||
"De actie \"{label}\" is succesvol uitgevoerd voor {success} van {total} "
|
||||
"bestellingen."
|
||||
|
||||
#: pretix/control/views/orders.py:686 pretix/presale/views/order.py:114
|
||||
#: pretix/presale/views/order.py:382 pretix/presale/views/order.py:1006
|
||||
#: pretix/presale/views/order.py:1186 pretix/presale/views/order.py:1797
|
||||
msgid "Unknown order code or not authorized to access this order."
|
||||
msgstr ""
|
||||
msgstr "Ongeldig bestelnummer of geen toegang tot deze bestelling."
|
||||
|
||||
#: pretix/control/views/orders.py:688 pretix/presale/views/order.py:1191
|
||||
msgid "Ticket download is not enabled for this product."
|
||||
msgstr ""
|
||||
msgstr "Tickets downloaden is niet ingeschakeld voor dit product."
|
||||
|
||||
#: pretix/control/views/orders.py:813
|
||||
msgid "The order has been deleted."
|
||||
msgstr ""
|
||||
msgstr "De bestelling is verwijderd."
|
||||
|
||||
#: pretix/control/views/orders.py:820
|
||||
msgid ""
|
||||
"The order could not be deleted as some constraints (e.g. data created by "
|
||||
"plug-ins) do not allow it."
|
||||
msgstr ""
|
||||
"De bestelling kon niet verwijderd worden omdat sommige beperkingen (bijv. "
|
||||
"data aangemaakt door plug-ins) het niet toestaan."
|
||||
|
||||
#: pretix/control/views/orders.py:828
|
||||
msgid "Only orders created in test mode can be deleted."
|
||||
msgstr ""
|
||||
"Alleen bestellingen die in de testmodus zijn aangemaakt, kunnen verwijderd "
|
||||
"worden."
|
||||
|
||||
#: pretix/control/views/orders.py:849
|
||||
msgid "The order has been denied and is therefore now canceled."
|
||||
msgstr ""
|
||||
msgstr "De bestelling is geweigerd en is daarom nu geannuleerd."
|
||||
|
||||
#: pretix/control/views/orders.py:889
|
||||
msgid "This payment has been canceled."
|
||||
msgstr ""
|
||||
msgstr "Deze betaling is geannuleerd."
|
||||
|
||||
#: pretix/control/views/orders.py:891
|
||||
msgid "This payment can not be canceled at the moment."
|
||||
msgstr ""
|
||||
msgstr "Deze betaling kan momenteel niet geannuleerd worden."
|
||||
|
||||
#: pretix/control/views/orders.py:917
|
||||
msgid "The refund has been canceled."
|
||||
msgstr ""
|
||||
msgstr "De terugbetaling is geannuleerd."
|
||||
|
||||
#: pretix/control/views/orders.py:919
|
||||
msgid "This refund can not be canceled at the moment."
|
||||
msgstr ""
|
||||
msgstr "Deze terugbetaling kan momenteel niet worden geannuleerd."
|
||||
|
||||
#: pretix/control/views/orders.py:953
|
||||
msgid "The refund has been processed."
|
||||
msgstr ""
|
||||
msgstr "De terugbetaling is verwerkt."
|
||||
|
||||
#: pretix/control/views/orders.py:955 pretix/control/views/orders.py:981
|
||||
msgid "This refund can not be processed at the moment."
|
||||
msgstr ""
|
||||
msgstr "Deze terugbetaling kan momenteel niet verwerkt worden."
|
||||
|
||||
#: pretix/control/views/orders.py:979
|
||||
msgid "The refund has been marked as done."
|
||||
msgstr ""
|
||||
msgstr "De terugbetaling is gemarkeerd als uitgevoerd."
|
||||
|
||||
#: pretix/control/views/orders.py:1005
|
||||
msgid "The request has been removed. If you want, you can now inform the user."
|
||||
msgstr ""
|
||||
"Het verzoek is verwijderd. Als u dit wilt, kunt u de gebruiker hier nu over "
|
||||
"informeren."
|
||||
|
||||
#: pretix/control/views/orders.py:1012
|
||||
msgid "Your cancellation request"
|
||||
msgstr ""
|
||||
msgstr "Uw annulatieverzoek"
|
||||
|
||||
#: pretix/control/views/orders.py:1013
|
||||
#, python-brace-format
|
||||
@@ -29668,12 +29601,12 @@ msgid "There was a problem processing your input. See below for error details."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Your user account does not have sufficient permission to run this report, "
|
||||
"therefore you cannot change it."
|
||||
msgstr "U bent niet gemachtigd om deze export uit te voeren."
|
||||
msgstr ""
|
||||
"Uw gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren. "
|
||||
"Daarom kunt u het niet wijzigen."
|
||||
|
||||
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
|
||||
msgid ""
|
||||
@@ -33309,7 +33242,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||
"PO-Revision-Date: 2026-02-21 03:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/nl_BE/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -60,7 +60,7 @@ msgstr "PayPal - Later betalen"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
|
||||
"PO-Revision-Date: 2026-03-14 22:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix/nl_Informal/>\n"
|
||||
@@ -410,10 +410,8 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/organizer.py:482
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "Accountinformatie"
|
||||
msgstr "Uitnodiging voor account"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:503
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -3956,10 +3954,8 @@ msgid "Peppol participant ID"
|
||||
msgstr "Peppol-deelnemers-ID"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
#, fuzzy
|
||||
#| msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr "De Peppol-deelnemers-ID is niet geregistreerd op het Peppol-netwerk."
|
||||
msgstr "Het Peppol-deelnemersnummer komt niet overeen met je btw-nummer."
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -6886,10 +6882,8 @@ msgstr ""
|
||||
"voordat de instelling voor alle gebruikers van kracht wordt."
|
||||
|
||||
#: pretix/base/models/organizer.py:378
|
||||
#, fuzzy
|
||||
#| msgid "Event permissions"
|
||||
msgid "All event permissions"
|
||||
msgstr "Evenementspermissies"
|
||||
msgstr "Alle evenementrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:379
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
@@ -6897,10 +6891,8 @@ msgid "Event permissions"
|
||||
msgstr "Evenementspermissies"
|
||||
|
||||
#: pretix/base/models/organizer.py:380
|
||||
#, fuzzy
|
||||
#| msgid "Organizer permissions"
|
||||
msgid "All organizer permissions"
|
||||
msgstr "Organisatorpermissies"
|
||||
msgstr "Alle organisatorrechten"
|
||||
|
||||
#: pretix/base/models/organizer.py:381
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
@@ -8401,36 +8393,32 @@ msgstr "Uw lay-outbestand is geen geldige lay-out. Foutmelding: {}"
|
||||
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
|
||||
msgctxt "permission_level"
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
msgstr "Lezen"
|
||||
|
||||
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
|
||||
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
|
||||
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
|
||||
#, fuzzy
|
||||
#| msgid "Save and check"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "Opslaan en controleren"
|
||||
msgstr "Bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
#, fuzzy
|
||||
#| msgid "API tokens"
|
||||
msgid "API only"
|
||||
msgstr "API-tokens"
|
||||
msgstr "alleen API"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
msgstr ""
|
||||
"Dit menu-item wordt alleen weergegeven als de gebruiker rechten heeft voor "
|
||||
"algemene instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
|
||||
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
|
||||
#: pretix/base/permissions.py:330
|
||||
#, fuzzy
|
||||
#| msgid "Revoke access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "Toegang intrekken"
|
||||
msgstr "Geen toegang"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
@@ -8444,6 +8432,8 @@ msgid ""
|
||||
"This includes access to all settings not listed explicitly below, including "
|
||||
"plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle instellingen die hieronder niet expliciet worden "
|
||||
"vermeld, inclusief instellingen voor plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:197
|
||||
#: pretix/control/templates/pretixcontrol/event/payment.html:6
|
||||
@@ -8457,100 +8447,81 @@ msgid "Tax settings"
|
||||
msgstr "Belastinginstellingen"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice settings"
|
||||
msgid "Invoicing settings"
|
||||
msgstr "Factuurinstellingen"
|
||||
msgstr "Factureringsinstellingen"
|
||||
|
||||
#: pretix/base/permissions.py:215
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event series date added"
|
||||
msgid "Event series dates"
|
||||
msgstr "Evenementenreeks: datum toegevoegd"
|
||||
msgstr "Datums van de evenementenreeks"
|
||||
|
||||
#: pretix/base/permissions.py:221
|
||||
#, fuzzy
|
||||
#| msgid "Product name and variation"
|
||||
msgid "Products, quotas and questions"
|
||||
msgstr "Productnaam en variant"
|
||||
msgstr "Producten, quota en vragen"
|
||||
|
||||
#: pretix/base/permissions.py:224
|
||||
msgid "Also includes related objects like categories or discounts."
|
||||
msgstr ""
|
||||
msgstr "Omvat ook gerelateerde objecten zoals categorieën of kortingen."
|
||||
|
||||
#: pretix/base/permissions.py:232
|
||||
#, fuzzy
|
||||
#| msgid "All check-ins"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "Alle check-ins"
|
||||
msgstr "Alleen inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
#, fuzzy
|
||||
#| msgid "View full log"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr "Toon volledige log"
|
||||
msgstr "Alles bekijken"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Valid check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "Geldige check-in"
|
||||
msgstr "Alles bekijken en inchecken"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
#, fuzzy
|
||||
#| msgid "View all upcoming events"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and change"
|
||||
msgstr "Bekijk alle aankomende evenementen"
|
||||
msgstr "Alles bekijken en wijzigen"
|
||||
|
||||
#: pretix/base/permissions.py:236
|
||||
msgid "Includes the ability to cancel and refund individual orders."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om afzonderlijke bestellingen te annuleren en terug te "
|
||||
"betalen."
|
||||
|
||||
#: pretix/base/permissions.py:238
|
||||
#, fuzzy
|
||||
#| msgid "An entry has been added to the waiting list."
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr "Een inschrijving is toegevoegd aan de wachtlijst."
|
||||
msgstr "Bevat ook aanverwante objecten, zoals de wachtlijst."
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgid "Generate cancellation"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "Genereer annulering"
|
||||
msgstr "Volledige annulering van het evenement of de datum"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
#, fuzzy
|
||||
#| msgid "Sale not allowed"
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr "Verkoop niet toegestaan"
|
||||
msgstr "Niet toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Allowed titles"
|
||||
msgctxt "permission_level"
|
||||
msgid "Allowed"
|
||||
msgstr "Te kiezen titels"
|
||||
msgstr "Toegestaan"
|
||||
|
||||
#: pretix/base/permissions.py:268
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen"
|
||||
|
||||
#: pretix/base/permissions.py:269
|
||||
msgctxt "permission_level"
|
||||
msgid "Access existing and create new events"
|
||||
msgstr ""
|
||||
msgstr "Toegang tot bestaande evenementen en nieuwe evenementen aanmaken"
|
||||
|
||||
#: pretix/base/permissions.py:271
|
||||
msgid ""
|
||||
"The level of access to events is determined in detail by the settings below."
|
||||
msgstr ""
|
||||
"De toegang tot evenementen wordt in detail bepaald door de onderstaande "
|
||||
"instellingen."
|
||||
|
||||
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
|
||||
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
|
||||
@@ -8569,12 +8540,15 @@ msgid ""
|
||||
"This includes access to all organizer-level functionality not listed "
|
||||
"explicitly below, including plugin settings."
|
||||
msgstr ""
|
||||
"Dit omvat toegang tot alle functies op organisatieniveau die hieronder niet "
|
||||
"expliciet worden vermeld, inclusief de instellingen van plug-ins."
|
||||
|
||||
#: pretix/base/permissions.py:287
|
||||
msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om iemand (inclusief jezelf) extra rechten te geven."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8592,12 +8566,12 @@ msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Biedt de mogelijkheid om toegang te verlenen tot evenementen en gegevens "
|
||||
"waar je zelf geen toegang toe hebt."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
#| msgid "Seating plan"
|
||||
msgid "Seating plans"
|
||||
msgstr "Zitplan"
|
||||
msgstr "Zaalplannen"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
@@ -9233,12 +9207,12 @@ msgid "Czech National Bank"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
|
||||
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
|
||||
#, fuzzy
|
||||
#| msgid "You do not have sufficient permission to perform this export."
|
||||
msgid ""
|
||||
"Export not found or you do not have sufficient permission to perform this "
|
||||
"export."
|
||||
msgstr "Je hebt te weinig rechten om deze export uit te voeren."
|
||||
msgstr ""
|
||||
"De export is niet gevonden of je hebt onvoldoende rechten om deze export uit "
|
||||
"te voeren."
|
||||
|
||||
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
|
||||
#: pretix/base/services/export.py:357
|
||||
@@ -13528,7 +13502,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/templates/403.html:4 pretix/base/templates/403.html:8
|
||||
msgid "Permission denied"
|
||||
msgstr "Geen toestemming"
|
||||
msgstr "Toestemming geweigerd"
|
||||
|
||||
#: pretix/base/templates/403.html:9
|
||||
msgid "You do not have access to this page."
|
||||
@@ -13717,25 +13691,7 @@ msgid "Contact"
|
||||
msgstr "Contact"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13769,7 +13725,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
|
||||
msgid ""
|
||||
@@ -14332,16 +14288,12 @@ msgid "This is an event series"
|
||||
msgstr "Dit is een evenementenreeks"
|
||||
|
||||
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You do not have sufficient permission to enable plugins that need to be "
|
||||
#| "enabled for the entire organizer account."
|
||||
msgid ""
|
||||
"You do not have a sufficient level of access on the event you selected to "
|
||||
"copy it to the desired organizer."
|
||||
msgstr ""
|
||||
"Je hebt niet voldoende rechten om plug-ins in te schakelen die voor het hele "
|
||||
"account van de organisator moeten worden ingeschakeld."
|
||||
"Je hebt onvoldoende rechten voor het evenement dat je hebt geselecteerd om "
|
||||
"het naar de gewenste organisator te kopiëren."
|
||||
|
||||
#: pretix/control/forms/event.py:143
|
||||
msgid ""
|
||||
@@ -14420,6 +14372,8 @@ msgid ""
|
||||
"You cannot choose a team that would give you more access than you have on "
|
||||
"the event you are copying."
|
||||
msgstr ""
|
||||
"Je kunt geen team selecteren dat je meer toegangsrechten zou geven dan het "
|
||||
"evenement dat je kopieert."
|
||||
|
||||
#: pretix/control/forms/event.py:344
|
||||
msgid "Copy configuration from"
|
||||
@@ -14435,6 +14389,8 @@ msgid ""
|
||||
"You cannot choose an event on which you have less access than the team you "
|
||||
"selected in the previous step."
|
||||
msgstr ""
|
||||
"Je kunt geen evenement selecteren waarvoor je minder toegangsrechten hebt "
|
||||
"dan het team dat je in de vorige stap geselecteerd hebt."
|
||||
|
||||
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
|
||||
#: pretix/control/forms/subevents.py:411
|
||||
@@ -15390,7 +15346,7 @@ msgstr "Zoek gast…"
|
||||
#: pretix/control/forms/filter.py:2039
|
||||
#: pretix/plugins/checkinlists/exporters.py:106
|
||||
msgid "Check-in status"
|
||||
msgstr "Incheckstatus"
|
||||
msgstr "Check-in-status"
|
||||
|
||||
#: pretix/control/forms/filter.py:2041
|
||||
#: pretix/plugins/checkinlists/exporters.py:108
|
||||
@@ -16799,7 +16755,7 @@ msgstr "Je kunt slechts één organisatiedomein instellen."
|
||||
|
||||
#: pretix/control/forms/organizer.py:322
|
||||
msgid "Provided by a plugin"
|
||||
msgstr ""
|
||||
msgstr "Beschikbaar gesteld via een plug-in"
|
||||
|
||||
#: pretix/control/forms/organizer.py:438
|
||||
msgid ""
|
||||
@@ -17678,10 +17634,8 @@ msgid "The order has been denied (comment: \"{comment}\")."
|
||||
msgstr "De bestelling is geweigerd (opmerking: \"{comment}\")."
|
||||
|
||||
#: pretix/control/logdisplay.py:521
|
||||
#, fuzzy
|
||||
#| msgid "The order has been overpaid."
|
||||
msgid "The customer VAT ID has been verified."
|
||||
msgstr "De bestelling is overbetaald."
|
||||
msgstr "Het btw-nummer van de klant is geverifieerd."
|
||||
|
||||
#: pretix/control/logdisplay.py:522
|
||||
#, python-brace-format
|
||||
@@ -17983,10 +17937,9 @@ msgid "{user} has been invited to the team."
|
||||
msgstr "{user} is uitgenodigd voor het team."
|
||||
|
||||
#: pretix/control/logdisplay.py:644
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Invite for {user} has been resent."
|
||||
#, python-brace-format
|
||||
msgid "Invite for {user} has been deleted."
|
||||
msgstr "De uitnodiging voor {user} is opnieuw verstuurd."
|
||||
msgstr "De uitnodiging voor {user} is verwijderd."
|
||||
|
||||
#: pretix/control/logdisplay.py:645
|
||||
#, python-brace-format
|
||||
@@ -20295,21 +20248,7 @@ msgid "Add property"
|
||||
msgstr "Eigenschap toevoegen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "%(reason)s\n"
|
||||
#| "\n"
|
||||
#| " %(code)s\n"
|
||||
#| "\n"
|
||||
#| "Please do never give this code to another person. Our support team will "
|
||||
#| "never ask for this code.\n"
|
||||
#| "\n"
|
||||
#| "If this code was not requested by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20338,7 +20277,7 @@ msgstr ""
|
||||
"contact op.\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
|
||||
#, python-format
|
||||
@@ -20375,17 +20314,7 @@ msgstr ""
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you requested a new password. Please go to the following page to reset "
|
||||
#| "your password:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20400,33 +20329,15 @@ msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"Je hebt een nieuw wachtwoord aangevraagd. Klik op de volgende link om je "
|
||||
"wachtwoord opnieuw\n"
|
||||
"in te stellen:\n"
|
||||
"wachtwoord opnieuw in te stellen:\n"
|
||||
"\n"
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "you have been invited to a team on pretix, a platform to perform event\n"
|
||||
#| "ticket sales.\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "Team: %(team)s\n"
|
||||
#| "\n"
|
||||
#| "If you want to join that team, just click on the following link:\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "If you do not want to join, you can safely ignore or delete this email.\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "\n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20448,7 +20359,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"Je bent uitgenodigd voor een team op pretix, een platform om kaartjes\n"
|
||||
"Je bent uitgenodigd voor een team op %(instance)s, een platform om kaartjes\n"
|
||||
"te verkopen.\n"
|
||||
"\n"
|
||||
"Organisator: %(organizer)s\n"
|
||||
@@ -20461,7 +20372,7 @@ msgstr ""
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
|
||||
#, python-format
|
||||
@@ -20498,24 +20409,7 @@ msgstr ""
|
||||
"Het team van %(instance)s\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "this is to inform you that the account information of your pretix account "
|
||||
#| "has been\n"
|
||||
#| "changed. In particular, the following changes have been performed:\n"
|
||||
#| "\n"
|
||||
#| "%(messages)s\n"
|
||||
#| "\n"
|
||||
#| "If this change was not performed by you, please contact us immediately.\n"
|
||||
#| "\n"
|
||||
#| "You can review and change your account settings here:\n"
|
||||
#| "\n"
|
||||
#| "%(url)s\n"
|
||||
#| "\n"
|
||||
#| "Best regards, \n"
|
||||
#| "Your pretix team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -20536,7 +20430,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hallo,\n"
|
||||
"\n"
|
||||
"Je ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in je pretix-"
|
||||
"Je ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in je %(instance)s-"
|
||||
"account.\n"
|
||||
"De volgende wijzigingen zijn gemaakt:\n"
|
||||
"\n"
|
||||
@@ -20550,7 +20444,7 @@ msgstr ""
|
||||
"%(url)s\n"
|
||||
"\n"
|
||||
"Met vriendelijke groet,\n"
|
||||
"Het pretix-team\n"
|
||||
"Het %(instance)s-team\n"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/email_setup.html:8
|
||||
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
|
||||
@@ -20821,10 +20715,8 @@ msgstr ""
|
||||
"dat met deze optie doen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
|
||||
#, fuzzy
|
||||
#| msgid "Permissions"
|
||||
msgid "No permission"
|
||||
msgstr "Permissies"
|
||||
msgstr "Geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
|
||||
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
|
||||
@@ -23375,10 +23267,8 @@ msgid "Edit question"
|
||||
msgstr "Vraag bewerken"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:55
|
||||
#, fuzzy
|
||||
#| msgid "You do not have permission to view this content."
|
||||
msgid "No permission to view answers."
|
||||
msgstr "Je hebt geen toestemming om deze inhoud te bekijken."
|
||||
msgstr "Je bent niet gemachtigd om de antwoorden te bekijken."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/question.html:63
|
||||
msgid "No matching answers found."
|
||||
@@ -24377,8 +24267,8 @@ msgid ""
|
||||
"this order."
|
||||
msgstr ""
|
||||
"De prijs van dit product is verlaagd vanwege een automatische korting, of "
|
||||
"dit product maakte deel uit van de kortingsberekening voor een ander product "
|
||||
"in deze bestelling."
|
||||
"dit product is meegenomen in de kortingsberekening voor een ander product in "
|
||||
"deze bestelling."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:496
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:103
|
||||
@@ -25034,10 +24924,8 @@ msgstr "Geen volgende run gepland"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:37
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
|
||||
#, fuzzy
|
||||
#| msgid "Exporter not found"
|
||||
msgid "Exporter not found or no permission"
|
||||
msgstr "Exporteerder niet gevonden"
|
||||
msgstr "Exporteur niet gevonden of geen toestemming"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:42
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
|
||||
@@ -25080,10 +24968,8 @@ msgstr "Aanbevolen voor nieuwe gebruikers"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html:120
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
|
||||
#, fuzzy
|
||||
#| msgid "There are no add-ons available for this product."
|
||||
msgid "There are no exporters available for you."
|
||||
msgstr "Er zijn geen add-ons beschikbaar voor dit product."
|
||||
msgstr "Er zijn geen exporters voor jou beschikbaar."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
|
||||
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
|
||||
@@ -27029,7 +26915,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
|
||||
#, python-format
|
||||
msgid "max. %(size)s, smaller is better"
|
||||
msgstr ""
|
||||
msgstr "max. %(size)s; hoe kleiner, hoe beter"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
|
||||
msgid "Download current background"
|
||||
@@ -29812,16 +29698,12 @@ msgstr ""
|
||||
"Er was een probleem met het verwerken van je invoer. Zie onder voor details."
|
||||
|
||||
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Your user account does not have sufficient permission to run this report, "
|
||||
#| "therefore you cannot schedule it."
|
||||
msgid ""
|
||||
"Your user account does not have sufficient permission to run this report, "
|
||||
"therefore you cannot change it."
|
||||
msgstr ""
|
||||
"Je gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren, "
|
||||
"daarom kun je het niet plannen."
|
||||
"Je gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren. "
|
||||
"Daarom kun je het niet wijzigen."
|
||||
|
||||
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
|
||||
msgid ""
|
||||
@@ -33872,7 +33754,7 @@ msgstr "iDEAL via Stripe"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
@@ -36858,6 +36740,9 @@ msgid ""
|
||||
"the shop that affect quotas, such as the validity period of carts and "
|
||||
"vouchers."
|
||||
msgstr ""
|
||||
"Let op: de gewijzigde tijd wordt niet meegenomen bij aspecten van de winkel "
|
||||
"die van invloed zijn op quota, zoals de geldigheidsduur van winkelwagentjes "
|
||||
"en kortingsbonnen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
|
||||
msgid "Enable time machine"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
|
||||
"PO-Revision-Date: 2026-01-29 19:43+0000\n"
|
||||
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/nl_Informal/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -60,7 +60,7 @@ msgstr "PayPal Later betalen"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
|
||||
@@ -503,7 +503,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
if cl.include_pending:
|
||||
headers.append(_('Paid'))
|
||||
|
||||
if form_data['secrets']:
|
||||
if form_data.get('secrets', False):
|
||||
headers.append(_('Secret'))
|
||||
|
||||
headers.append(_('Email'))
|
||||
@@ -603,7 +603,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
]
|
||||
if cl.include_pending:
|
||||
row.append(_('Yes') if op.order.status == Order.STATUS_PAID else _('No'))
|
||||
if form_data['secrets']:
|
||||
if form_data.get('secrets', False):
|
||||
row.append(op.secret)
|
||||
row.append(op.attendee_email or (op.addon_to.attendee_email if op.addon_to else '') or op.order.email or '')
|
||||
row.append(str(op.order.phone) if op.order.phone else '')
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import type { I18nString, SubEvent } from './i18n'
|
||||
|
||||
const settingsEl = document.getElementById('api-settings')
|
||||
const { urls } = JSON.parse(settingsEl.textContent || '{}') as { urls: {
|
||||
lists: string
|
||||
questions: string
|
||||
} }
|
||||
|
||||
// interfaces generated from api docs
|
||||
export interface PaginatedResponse<T> {
|
||||
count: number
|
||||
next: string | null
|
||||
previous: string | null
|
||||
results: T[]
|
||||
}
|
||||
|
||||
export interface CheckinList {
|
||||
id: number
|
||||
name: string
|
||||
all_products: boolean
|
||||
limit_products: number[]
|
||||
subevent: SubEvent | null
|
||||
position_count?: number
|
||||
checkin_count?: number
|
||||
include_pending: boolean
|
||||
allow_multiple_entries: boolean
|
||||
allow_entry_after_exit: boolean
|
||||
rules: Record<string, unknown>
|
||||
exit_all_at: string | null
|
||||
addon_match: boolean
|
||||
ignore_in_statistics?: boolean
|
||||
consider_tickets_used?: boolean
|
||||
}
|
||||
|
||||
export interface Checkin {
|
||||
id: number
|
||||
list: number
|
||||
datetime: string
|
||||
type: 'entry' | 'exit'
|
||||
gate: number | null
|
||||
device: number | null
|
||||
device_id: number | null
|
||||
auto_checked_in: boolean
|
||||
}
|
||||
|
||||
export interface Seat {
|
||||
id: number
|
||||
name: string
|
||||
zone_name: string
|
||||
row_name: string
|
||||
row_label: string | null
|
||||
seat_number: string
|
||||
seat_label: string | null
|
||||
seat_guid: string
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
id: number
|
||||
order: string
|
||||
positionid: number
|
||||
canceled?: boolean
|
||||
item: { id?: number; name: I18nString; internal_name?: string; admission?: boolean }
|
||||
variation: { id?: number; value: I18nString } | null
|
||||
price: string
|
||||
attendee_name: string
|
||||
attendee_name_parts: Record<string, string>
|
||||
attendee_email: string | null
|
||||
company?: string | null
|
||||
street?: string | null
|
||||
zipcode?: string | null
|
||||
city?: string | null
|
||||
country?: string | null
|
||||
state?: string | null
|
||||
voucher?: number | null
|
||||
voucher_budget_use?: string | null
|
||||
tax_rate: string
|
||||
tax_value: string
|
||||
tax_code?: string | null
|
||||
tax_rule: number | null
|
||||
secret: string
|
||||
addon_to: number | null
|
||||
subevent: SubEvent | null
|
||||
discount?: number | null
|
||||
blocked: string[] | null
|
||||
valid_from: string | null
|
||||
valid_until: string | null
|
||||
pseudonymization_id: string
|
||||
seat: Seat | null
|
||||
checkins: Checkin[]
|
||||
downloads?: { output: string; url: string }[]
|
||||
answers: Answer[]
|
||||
pdf_data?: Record<string, unknown>
|
||||
plugin_data?: Record<string, unknown>
|
||||
// Additional fields from checkin list positions endpoint
|
||||
order__status?: string
|
||||
order__valid_if_pending?: boolean
|
||||
order__require_approval?: boolean
|
||||
order__locale?: string
|
||||
require_attention?: boolean
|
||||
addons?: Addon[]
|
||||
}
|
||||
|
||||
export interface Answer {
|
||||
question: number | AnswerQuestion
|
||||
answer: string
|
||||
question_identifier: string
|
||||
options: number[]
|
||||
option_identifiers: string[]
|
||||
}
|
||||
|
||||
export interface AnswerQuestion {
|
||||
id: number
|
||||
question: I18nString
|
||||
help_text?: I18nString
|
||||
type: string
|
||||
required: boolean
|
||||
position: number
|
||||
items: number[]
|
||||
identifier: string
|
||||
ask_during_checkin: boolean
|
||||
show_during_checkin: boolean
|
||||
hidden?: boolean
|
||||
print_on_invoice?: boolean
|
||||
options: QuestionOption[]
|
||||
valid_number_min?: string | null
|
||||
valid_number_max?: string | null
|
||||
valid_date_min?: string | null
|
||||
valid_date_max?: string | null
|
||||
valid_datetime_min?: string | null
|
||||
valid_datetime_max?: string | null
|
||||
valid_file_portrait?: boolean
|
||||
valid_string_length_max?: number | null
|
||||
dependency_question?: number | null
|
||||
dependency_values?: string[]
|
||||
}
|
||||
|
||||
export interface QuestionOption {
|
||||
id: number
|
||||
identifier: string
|
||||
position: number
|
||||
answer: I18nString
|
||||
}
|
||||
|
||||
export interface Addon {
|
||||
item: { name: I18nString; internal_name?: string }
|
||||
variation: { value: I18nString } | null
|
||||
}
|
||||
|
||||
export interface CheckinStatusVariation {
|
||||
id: number
|
||||
value: string
|
||||
checkin_count: number
|
||||
position_count: number
|
||||
}
|
||||
|
||||
export interface CheckinStatusItem {
|
||||
id: number
|
||||
name: string
|
||||
checkin_count: number
|
||||
admission: boolean
|
||||
position_count: number
|
||||
variations: CheckinStatusVariation[]
|
||||
}
|
||||
|
||||
export interface CheckinStatus {
|
||||
checkin_count: number
|
||||
position_count: number
|
||||
inside_count: number
|
||||
event?: { name: string }
|
||||
items?: CheckinStatusItem[]
|
||||
}
|
||||
|
||||
export interface RedeemRequest {
|
||||
questions_supported: boolean
|
||||
canceled_supported: boolean
|
||||
ignore_unpaid: boolean
|
||||
type: 'entry' | 'exit'
|
||||
answers: Record<string, string>
|
||||
datetime?: string | null
|
||||
force?: boolean
|
||||
nonce?: string
|
||||
}
|
||||
|
||||
export interface RedeemResponseList {
|
||||
id: number
|
||||
name: string
|
||||
event: string
|
||||
subevent: number | null
|
||||
include_pending: boolean
|
||||
}
|
||||
|
||||
export interface RedeemResponse {
|
||||
status: 'ok' | 'error' | 'incomplete'
|
||||
reason?: 'invalid' | 'unpaid' | 'blocked' | 'invalid_time' | 'canceled' | 'already_redeemed' | 'product' | 'rules' | 'ambiguous' | 'revoked' | 'unapproved' | 'error'
|
||||
reason_explanation?: string | null
|
||||
position?: Position
|
||||
questions?: AnswerQuestion[]
|
||||
checkin_texts?: string[]
|
||||
require_attention?: boolean
|
||||
list?: RedeemResponseList
|
||||
}
|
||||
|
||||
const CSRF_TOKEN = document.querySelector<HTMLInputElement>('input[name=csrfmiddlewaretoken]')?.value ?? ''
|
||||
|
||||
function handleAuthError (response: Response): void {
|
||||
if ([401, 403].includes(response.status)) {
|
||||
window.location.href = '/control/login?next=' + encodeURIComponent(
|
||||
window.location.pathname + window.location.search + window.location.hash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const api = {
|
||||
// generic fetch wrapper, not sure if this should be exposed
|
||||
async fetch <T> (url: string, options?: RequestInit): Promise<T> {
|
||||
const response = await fetch(url, options)
|
||||
handleAuthError(response)
|
||||
if (!response.ok && response.status !== 400 && response.status !== 404) {
|
||||
throw new Error('HTTP status ' + response.status)
|
||||
}
|
||||
return response.json()
|
||||
},
|
||||
async fetchCheckinLists (endsAfter?: string): Promise<PaginatedResponse<CheckinList>> {
|
||||
const cutoff = endsAfter ?? moment().subtract(8, 'hours').toISOString()
|
||||
const url = `${urls.lists}?exclude=checkin_count&exclude=position_count&expand=subevent&ends_after=${cutoff}`
|
||||
return api.fetch(url)
|
||||
},
|
||||
async fetchCheckinList (listId: string): Promise<CheckinList> {
|
||||
return api.fetch(`${urls.lists}${listId}/?expand=subevent`)
|
||||
},
|
||||
async fetchNextPage<T> (nextUrl: string): Promise<PaginatedResponse<T>> {
|
||||
return api.fetch(nextUrl)
|
||||
},
|
||||
async fetchStatus (listId: number): Promise<CheckinStatus> {
|
||||
return api.fetch(`${urls.lists}${listId}/status/`)
|
||||
},
|
||||
async searchPositions (listId: number, query: string): Promise<PaginatedResponse<Position>> {
|
||||
const url = `${urls.lists}${listId}/positions/?ignore_status=true&expand=subevent&expand=item&expand=variation&check_rules=true&search=${encodeURIComponent(query)}`
|
||||
return api.fetch(url)
|
||||
},
|
||||
async redeemPosition (
|
||||
listId: number,
|
||||
positionId: string,
|
||||
data: RedeemRequest,
|
||||
untrusted: boolean = false
|
||||
): Promise<RedeemResponse> {
|
||||
let url = `${urls.lists}${listId}/positions/${encodeURIComponent(positionId)}/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question&expand=addons`
|
||||
if (untrusted) url += '&untrusted_input=true'
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': CSRF_TOKEN,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
handleAuthError(response)
|
||||
|
||||
if (response.status === 404) {
|
||||
return { status: 'error', reason: 'invalid' }
|
||||
}
|
||||
|
||||
if (!response.ok && response.status !== 400) {
|
||||
throw new Error('HTTP status ' + response.status)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { CheckinList } from '../api'
|
||||
import { formatSubevent } from '../i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
list: CheckinList
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
selected: [list: CheckinList]
|
||||
}>()
|
||||
|
||||
const subevent = computed(() => formatSubevent(props.list.subevent))
|
||||
</script>
|
||||
<template lang="pug">
|
||||
a.list-group-item(href="#", @click.prevent="$emit('selected', list)")
|
||||
.row
|
||||
.col-md-6 {{ list.name }}
|
||||
.col-md-6.text-muted {{ subevent }}
|
||||
<template>
|
||||
<a class="list-group-item" href="#" @click.prevent="$emit('selected', list)">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ list.name }}
|
||||
</div>
|
||||
<div class="col-md-6 text-muted">
|
||||
{{ subevent }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
list: Object
|
||||
},
|
||||
computed: {
|
||||
subevent () {
|
||||
if (!this.list.subevent) return '';
|
||||
const name = i18nstring_localize(this.list.subevent.name)
|
||||
const date = moment.utc(this.list.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
|
||||
return `${name} · ${date}`
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,101 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { api } from '../api'
|
||||
import type { CheckinList } from '../api'
|
||||
import { STRINGS } from '../i18n'
|
||||
import CheckinlistItem from './checkinlist-item.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
selected: [list: CheckinList]
|
||||
}>()
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref<unknown>(null)
|
||||
const lists = ref<CheckinList[] | null>(null)
|
||||
const nextUrl = ref<string | null>(null)
|
||||
|
||||
async function load () {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
if (location.hash) {
|
||||
const listId = location.hash.substring(1)
|
||||
try {
|
||||
const data = await api.fetchCheckinList(listId)
|
||||
loading.value = false
|
||||
if (data.id) {
|
||||
emit('selected', data)
|
||||
} else {
|
||||
location.hash = ''
|
||||
load()
|
||||
}
|
||||
} catch {
|
||||
location.hash = ''
|
||||
load()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const data = await api.fetchCheckinLists()
|
||||
loading.value = false
|
||||
|
||||
if (data.results) {
|
||||
lists.value = data.results
|
||||
nextUrl.value = data.next
|
||||
} else if (data.results === 0) {
|
||||
error.value = STRINGS['checkinlist.none']
|
||||
} else {
|
||||
error.value = data
|
||||
}
|
||||
} catch (e) {
|
||||
loading.value = false
|
||||
error.value = e
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNext () {
|
||||
if (!nextUrl.value) return
|
||||
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const data = await api.fetchNextPage<CheckinList>(nextUrl.value)
|
||||
loading.value = false
|
||||
|
||||
if (data.results) {
|
||||
lists.value.push(...data.results)
|
||||
nextUrl.value = data.next
|
||||
} else if (data.results === 0) {
|
||||
error.value = STRINGS['checkinlist.none']
|
||||
} else {
|
||||
error.value = data
|
||||
}
|
||||
} catch (e) {
|
||||
loading.value = false
|
||||
error.value = e
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
.panel.panel-primary.checkinlist-select
|
||||
.panel-heading
|
||||
h3.panel-title {{ STRINGS['checkinlist.select'] }}
|
||||
ul.list-group
|
||||
CheckinlistItem(
|
||||
v-for="l in lists",
|
||||
:key="l.id",
|
||||
:list="l",
|
||||
@selected="emit('selected', $event)"
|
||||
)
|
||||
li.list-group-item.text-center(v-if="loading")
|
||||
span.fa.fa-4x.fa-cog.fa-spin.loading-icon
|
||||
li.list-group-item.text-center(v-else-if="error") {{ error }}
|
||||
a.list-group-item.text-center(v-else-if="nextUrl", href="#", @click.prevent="loadNext")
|
||||
| {{ STRINGS['pagination.next'] }}
|
||||
<template>
|
||||
<div class="panel panel-primary checkinlist-select">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{{ $root.strings['checkinlist.select'] }}
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<checkinlist-item v-if="lists" v-for="l in lists" :list="l" :key="l.id" @selected="$emit('selected', l)"></checkinlist-item>
|
||||
<li v-if="loading" class="list-group-item text-center">
|
||||
<span class="fa fa-4x fa-cog fa-spin loading-icon"></span>
|
||||
</li>
|
||||
<li v-else-if="error" class="list-group-item text-center">
|
||||
{{ error }}
|
||||
</li>
|
||||
<a v-else-if="next_url" class="list-group-item text-center" href="#" @click.prevent="loadNext">
|
||||
{{ $root.strings['pagination.next'] }}
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
CheckinlistItem: CheckinlistItem.default,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: null,
|
||||
lists: null,
|
||||
next_url: null,
|
||||
}
|
||||
},
|
||||
// TODO: pagination
|
||||
mounted() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
this.loading = true
|
||||
const cutoff = moment().subtract(8, 'hours').toISOString()
|
||||
if (location.hash) {
|
||||
fetch(this.$root.api.lists + location.hash.substr(1) + '/' + '?expand=subevent')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.loading = false
|
||||
if (data.id) {
|
||||
this.$emit('selected', data)
|
||||
} else {
|
||||
location.hash = ''
|
||||
this.load()
|
||||
}
|
||||
})
|
||||
.catch(reason => {
|
||||
location.hash = ''
|
||||
this.load()
|
||||
})
|
||||
return
|
||||
}
|
||||
fetch(this.$root.api.lists + '?exclude=checkin_count&exclude=position_count&expand=subevent&ends_after=' + cutoff)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.loading = false
|
||||
if (data.results) {
|
||||
this.lists = data.results
|
||||
this.next_url = data.next
|
||||
} else if (data.results === 0) {
|
||||
this.error = this.$root.strings['checkinlist.none']
|
||||
} else {
|
||||
this.error = data
|
||||
}
|
||||
})
|
||||
.catch(reason => {
|
||||
this.loading = false
|
||||
this.error = reason
|
||||
})
|
||||
},
|
||||
loadNext() {
|
||||
this.loading = true
|
||||
fetch(this.next_url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.loading = false
|
||||
if (data.results) {
|
||||
this.lists.push(...data.results)
|
||||
this.next_url = data.next
|
||||
} else if (data.results === 0) {
|
||||
this.error = this.$root.strings['checkinlist.none']
|
||||
} else {
|
||||
this.error = data
|
||||
}
|
||||
})
|
||||
.catch(reason => {
|
||||
this.loading = false
|
||||
this.error = reason
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,64 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { dateFormat, datetimeLocale } from '../i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
modelValue?: string
|
||||
id?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const input = ref<HTMLInputElement>()
|
||||
|
||||
const opts = {
|
||||
format: dateFormat,
|
||||
locale: datetimeLocale,
|
||||
useCurrent: false,
|
||||
showClear: props.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove',
|
||||
},
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
$(input.value!).data('DateTimePicker').date(moment(val))
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$(input.value!)
|
||||
.datetimepicker(opts)
|
||||
.trigger('change')
|
||||
.on('dp.change', function (this: HTMLElement) {
|
||||
emit('update:modelValue', $(this).data('DateTimePicker').date().format('YYYY-MM-DD'))
|
||||
})
|
||||
|
||||
if (!props.modelValue) {
|
||||
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||
} else {
|
||||
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(input.value!)
|
||||
.off()
|
||||
.datetimepicker('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
input.form-control(:id="id", ref="input", :required="required")
|
||||
<template>
|
||||
<input class="form-control" :required="required">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().format("YYYY-MM-DD"));
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(moment(vm.value));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-dateformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(moment(val));
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,65 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { datetimeFormat, datetimeLocale, timezone } from '../i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
modelValue?: string
|
||||
id?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const input = ref<HTMLInputElement>()
|
||||
|
||||
const opts = {
|
||||
format: datetimeFormat,
|
||||
locale: datetimeLocale,
|
||||
timeZone: timezone,
|
||||
useCurrent: false,
|
||||
showClear: props.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove',
|
||||
},
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
$(input.value!).data('DateTimePicker').date(moment(val))
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$(input.value!)
|
||||
.datetimepicker(opts)
|
||||
.trigger('change')
|
||||
.on('dp.change', function (this: HTMLElement) {
|
||||
emit('update:modelValue', $(this).data('DateTimePicker').date().toISOString())
|
||||
})
|
||||
|
||||
if (!props.modelValue) {
|
||||
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||
} else {
|
||||
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(input.value!)
|
||||
.off()
|
||||
.datetimepicker('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
input.form-control(:id="id", ref="input", :required="required")
|
||||
<template>
|
||||
<input class="form-control" :required="required">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().toISOString());
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(moment(vm.value));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-datetimeformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
timeZone: $("body").attr("data-timezone"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(moment(val));
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,48 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Position } from '../api'
|
||||
import { STRINGS, i18nstringLocalize, formatSubevent } from '../i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
position: Position
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
selected: [position: Position]
|
||||
}>()
|
||||
|
||||
const rootEl = ref<HTMLAnchorElement>()
|
||||
|
||||
const status = computed(() => {
|
||||
if (props.position.checkins.length) return 'redeemed'
|
||||
if (props.position.order__status === 'n' && props.position.order__valid_if_pending) return 'pending_valid'
|
||||
if (props.position.order__status === 'n' && props.position.order__require_approval) return 'require_approval'
|
||||
return props.position.order__status
|
||||
})
|
||||
|
||||
const itemvar = computed(() => {
|
||||
if (props.position.variation) {
|
||||
return `${i18nstringLocalize(props.position.item.name)} – ${i18nstringLocalize(props.position.variation.value)}`
|
||||
}
|
||||
return i18nstringLocalize(props.position.item.name)
|
||||
})
|
||||
|
||||
const subevent = computed(() => formatSubevent(props.position.subevent))
|
||||
|
||||
defineExpose({ el: rootEl })
|
||||
</script>
|
||||
<template lang="pug">
|
||||
a.list-group-item.searchresult(ref="rootEl", href="#", @click.prevent="$emit('selected', position)")
|
||||
.details
|
||||
h4 {{ position.order }}-{{ position.positionid }} {{ position.attendee_name }}
|
||||
span {{ itemvar }}
|
||||
br
|
||||
span(v-if="subevent") {{ subevent }}
|
||||
br
|
||||
.secret {{ position.secret }}
|
||||
.status(:class="`status-${status}`")
|
||||
span(v-if="position.require_attention")
|
||||
span.fa.fa-warning
|
||||
br
|
||||
| {{ STRINGS[`status.${status}`] }}
|
||||
<template>
|
||||
<a class="list-group-item searchresult" href="#" @click.prevent="$emit('selected', position)" ref="a">
|
||||
<div class="details">
|
||||
<h4>{{ position.order }}-{{ position.positionid }} {{ position.attendee_name }}</h4>
|
||||
<span>{{ itemvar }}<br></span>
|
||||
<span v-if="subevent">{{ subevent }}<br></span>
|
||||
<div class="secret">{{ position.secret }}</div>
|
||||
</div>
|
||||
<div :class="`status status-${status}`">
|
||||
<span v-if="position.require_attention"><span class="fa fa-warning"></span><br></span>
|
||||
{{ $root.strings[`status.${status}`] }}
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
position: Object
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
if (this.position.checkins.length) return 'redeemed';
|
||||
if (this.position.order__status === 'n' && this.position.order__valid_if_pending) return 'pending_valid';
|
||||
if (this.position.order__status === 'n' && this.position.order__require_approval) return 'require_approval';
|
||||
return this.position.order__status
|
||||
},
|
||||
itemvar() {
|
||||
if (this.position.variation) {
|
||||
return `${i18nstring_localize(this.position.item.name)} – ${i18nstring_localize(this.position.variation.value)}`
|
||||
}
|
||||
return i18nstring_localize(this.position.item.name)
|
||||
},
|
||||
subevent() {
|
||||
if (!this.position.subevent) return ''
|
||||
const name = i18nstring_localize(this.position.subevent.name)
|
||||
const date = moment.utc(this.position.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
|
||||
return `${name} · ${date}`
|
||||
},
|
||||
},
|
||||
}
|
||||
// secret
|
||||
// status
|
||||
// order code
|
||||
// name
|
||||
// seat
|
||||
// require attention
|
||||
</script>
|
||||
|
||||
@@ -1,64 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { timeFormat, datetimeLocale } from '../i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
modelValue?: string
|
||||
id?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const input = ref<HTMLInputElement>()
|
||||
|
||||
const opts = {
|
||||
format: timeFormat,
|
||||
locale: datetimeLocale,
|
||||
useCurrent: false,
|
||||
showClear: props.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove',
|
||||
},
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
$(input.value!).data('DateTimePicker').date(moment(val))
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$(input.value!)
|
||||
.datetimepicker(opts)
|
||||
.trigger('change')
|
||||
.on('dp.change', function (this: HTMLElement) {
|
||||
emit('update:modelValue', $(this).data('DateTimePicker').date().format('HH:mm:ss'))
|
||||
})
|
||||
|
||||
if (!props.modelValue) {
|
||||
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||
} else {
|
||||
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(input.value!)
|
||||
.off()
|
||||
.datetimepicker('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
input.form-control(:id="id", ref="input", :required="required")
|
||||
<template>
|
||||
<input class="form-control" :required="required">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().format("HH:mm:ss"));
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(moment(vm.value));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-timeformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(moment(val));
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,106 +0,0 @@
|
||||
const body = document.body
|
||||
|
||||
export const timezone = body.dataset.timezone ?? 'UTC'
|
||||
export const datetimeFormat = body.dataset.datetimeformat ?? 'L LT'
|
||||
export const dateFormat = body.dataset.dateformat ?? 'L'
|
||||
export const timeFormat = body.dataset.timeformat ?? 'LT'
|
||||
export const datetimeLocale = body.dataset.datetimelocale ?? 'en'
|
||||
export const pretixLocale = body.dataset.pretixlocale ?? 'en'
|
||||
|
||||
moment.locale(datetimeLocale)
|
||||
|
||||
export function gettext (msgid: string): string {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid)
|
||||
}
|
||||
return msgid
|
||||
}
|
||||
|
||||
export function ngettext (singular: string, plural: string, count: number): string {
|
||||
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
||||
return django.ngettext(singular, plural, count)
|
||||
}
|
||||
return plural
|
||||
}
|
||||
|
||||
export type I18nString = string | Record<string, string> | null | undefined
|
||||
|
||||
export function i18nstringLocalize (obj: I18nString): string {
|
||||
// external
|
||||
return i18nstring_localize(obj)
|
||||
}
|
||||
|
||||
export const STRINGS: Record<string, string> = {
|
||||
'checkinlist.select': gettext('Select a check-in list'),
|
||||
'checkinlist.none': gettext('No active check-in lists found.'),
|
||||
'checkinlist.switch': gettext('Switch check-in list'),
|
||||
'results.headline': gettext('Search results'),
|
||||
'results.none': gettext('No tickets found'),
|
||||
'check.headline': gettext('Result'),
|
||||
'check.attention': gettext('This ticket requires special attention'),
|
||||
'scantype.switch': gettext('Switch direction'),
|
||||
'scantype.entry': gettext('Entry'),
|
||||
'scantype.exit': gettext('Exit'),
|
||||
'input.placeholder': gettext('Scan a ticket or search and press return…'),
|
||||
'pagination.next': gettext('Load more'),
|
||||
'status.p': gettext('Valid'),
|
||||
'status.n': gettext('Unpaid'),
|
||||
'status.c': gettext('Canceled'),
|
||||
'status.e': gettext('Canceled'),
|
||||
'status.pending_valid': gettext('Confirmed'),
|
||||
'status.require_approval': gettext('Approval pending'),
|
||||
'status.redeemed': gettext('Redeemed'),
|
||||
'modal.cancel': gettext('Cancel'),
|
||||
'modal.continue': gettext('Continue'),
|
||||
'modal.unpaid.head': gettext('Ticket not paid'),
|
||||
'modal.unpaid.text': gettext('This ticket is not yet paid. Do you want to continue anyways?'),
|
||||
'modal.questions': gettext('Additional information required'),
|
||||
'result.ok': gettext('Valid ticket'),
|
||||
'result.exit': gettext('Exit recorded'),
|
||||
'result.already_redeemed': gettext('Ticket already used'),
|
||||
'result.questions': gettext('Information required'),
|
||||
'result.invalid': gettext('Unknown ticket'),
|
||||
'result.product': gettext('Ticket type not allowed here'),
|
||||
'result.unpaid': gettext('Ticket not paid'),
|
||||
'result.rules': gettext('Entry not allowed'),
|
||||
'result.revoked': gettext('Ticket code revoked/changed'),
|
||||
'result.blocked': gettext('Ticket blocked'),
|
||||
'result.invalid_time': gettext('Ticket not valid at this time'),
|
||||
'result.canceled': gettext('Order canceled'),
|
||||
'result.ambiguous': gettext('Ticket code is ambiguous on list'),
|
||||
'result.unapproved': gettext('Order not approved'),
|
||||
'status.checkin': gettext('Checked-in Tickets'),
|
||||
'status.position': gettext('Valid Tickets'),
|
||||
'status.inside': gettext('Currently inside'),
|
||||
yes: gettext('Yes'),
|
||||
no: gettext('No'),
|
||||
}
|
||||
|
||||
export interface SubEvent {
|
||||
name: Record<string, string>
|
||||
date_from: string
|
||||
}
|
||||
|
||||
export function formatSubevent (subevent: SubEvent | null | undefined): string {
|
||||
if (!subevent) return ''
|
||||
const name = i18nstringLocalize(subevent.name)
|
||||
const date = moment.utc(subevent.date_from).tz(timezone).format(datetimeFormat)
|
||||
return `${name} · ${date}`
|
||||
}
|
||||
|
||||
export interface Question {
|
||||
type: string
|
||||
}
|
||||
|
||||
export function formatAnswer (value: string, question: Question): string {
|
||||
if (question.type === 'B' && value === 'True') {
|
||||
return STRINGS['yes']
|
||||
} else if (question.type === 'B' && value === 'False') {
|
||||
return STRINGS['no']
|
||||
} else if (question.type === 'W' && value) {
|
||||
return moment(value).tz(timezone).format('L LT')
|
||||
} else if (question.type === 'D' && value) {
|
||||
return moment(value).format('L')
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*global gettext, Vue, App*/
|
||||
function gettext(msgid) {
|
||||
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
|
||||
return django.gettext(msgid);
|
||||
}
|
||||
return msgid;
|
||||
}
|
||||
|
||||
function ngettext(singular, plural, count) {
|
||||
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
|
||||
return django.ngettext(singular, plural, count);
|
||||
}
|
||||
return plural;
|
||||
}
|
||||
|
||||
|
||||
moment.locale(document.body.attributes['data-datetimelocale'].value)
|
||||
window.vapp = new Vue({
|
||||
components: {
|
||||
App: App.default
|
||||
},
|
||||
render: function (h) {
|
||||
return h('App')
|
||||
},
|
||||
data: {
|
||||
api: {
|
||||
lists: document.querySelector('#app').attributes['data-api-lists'].value,
|
||||
},
|
||||
strings: {
|
||||
'checkinlist.select': gettext('Select a check-in list'),
|
||||
'checkinlist.none': gettext('No active check-in lists found.'),
|
||||
'checkinlist.switch': gettext('Switch check-in list'),
|
||||
'results.headline': gettext('Search results'),
|
||||
'results.none': gettext('No tickets found'),
|
||||
'check.headline': gettext('Result'),
|
||||
'check.attention': gettext('This ticket requires special attention'),
|
||||
'scantype.switch': gettext('Switch direction'),
|
||||
'scantype.entry': gettext('Entry'),
|
||||
'scantype.exit': gettext('Exit'),
|
||||
'input.placeholder': gettext('Scan a ticket or search and press return…'),
|
||||
'pagination.next': gettext('Load more'),
|
||||
'status.p': gettext('Valid'),
|
||||
'status.n': gettext('Unpaid'),
|
||||
'status.c': gettext('Canceled'),
|
||||
'status.e': gettext('Canceled'),
|
||||
'status.pending_valid': gettext('Confirmed'),
|
||||
'status.require_approval': gettext('Approval pending'),
|
||||
'status.redeemed': gettext('Redeemed'),
|
||||
'modal.cancel': gettext('Cancel'),
|
||||
'modal.continue': gettext('Continue'),
|
||||
'modal.unpaid.head': gettext('Ticket not paid'),
|
||||
'modal.unpaid.text': gettext('This ticket is not yet paid. Do you want to continue anyways?'),
|
||||
'modal.questions': gettext('Additional information required'),
|
||||
'result.ok': gettext('Valid ticket'),
|
||||
'result.exit': gettext('Exit recorded'),
|
||||
'result.already_redeemed': gettext('Ticket already used'),
|
||||
'result.questions': gettext('Information required'),
|
||||
'result.invalid': gettext('Unknown ticket'),
|
||||
'result.product': gettext('Ticket type not allowed here'),
|
||||
'result.unpaid': gettext('Ticket not paid'),
|
||||
'result.rules': gettext('Entry not allowed'),
|
||||
'result.revoked': gettext('Ticket code revoked/changed'),
|
||||
'result.blocked': gettext('Ticket blocked'),
|
||||
'result.invalid_time': gettext('Ticket not valid at this time'),
|
||||
'result.canceled': gettext('Order canceled'),
|
||||
'result.ambiguous': gettext('Ticket code is ambiguous on list'),
|
||||
'result.unapproved': gettext('Order not approved'),
|
||||
'status.checkin': gettext('Checked-in Tickets'),
|
||||
'status.position': gettext('Valid Tickets'),
|
||||
'status.inside': gettext('Currently inside'),
|
||||
'yes': gettext('Yes'),
|
||||
'no': gettext('No'),
|
||||
},
|
||||
event_name: document.querySelector('#app').attributes['data-event-name'].value,
|
||||
timezone: document.body.attributes['data-timezone'].value,
|
||||
datetime_format: document.body.attributes['data-datetimeformat'].value,
|
||||
},
|
||||
el: '#app'
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
// import './scss/main.scss'
|
||||
|
||||
import App from './components/app.vue'
|
||||
|
||||
const mountEl = document.querySelector<HTMLElement>('#app')!
|
||||
|
||||
const app = createApp(App, mountEl.dataset)
|
||||
app.mount('#app')
|
||||
|
||||
app.config.errorHandler = (error, _vm, info) => {
|
||||
// vue fatals on errors by default, which is a weird choice
|
||||
// https://github.com/vuejs/core/issues/3525
|
||||
// https://github.com/vuejs/router/discussions/2435
|
||||
console.error('[VUE]', info, error)
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
{% load statici18n %}
|
||||
{% load eventurl %}
|
||||
{% load escapejson %}
|
||||
{% load vite %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -24,7 +23,11 @@
|
||||
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}"
|
||||
data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}"
|
||||
data-pretixlocale="{{ request.LANGUAGE_CODE }}" data-timezone="{{ request.event.settings.timezone }}">
|
||||
<div id="app" data-event-name="{{ request.event.name }}"></div>
|
||||
<div
|
||||
data-api-lists="{% url "api-v1:checkinlist-list" event=request.event.slug organizer=request.organizer.slug %}"
|
||||
data-api-questions="{% url "api-v1:question-list" event=request.event.slug organizer=request.organizer.slug %}"
|
||||
data-event-name="{{ request.event.name }}"
|
||||
id="app"></div>
|
||||
{% compress js %}
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/i18nstring.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "moment/moment-with-locales.js" %}"></script>
|
||||
@@ -32,17 +35,22 @@
|
||||
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% if DEBUG %}
|
||||
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
|
||||
{% else %}
|
||||
<script type="text/javascript" src="{% static "vuejs/vue.min.js" %}"></script>
|
||||
{% endif %}
|
||||
{% compress js %}
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/checkinlist-item.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/checkinlist-select.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/searchresult-item.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/datetimefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/datefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/timefield.vue' %}"></script>
|
||||
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/app.vue' %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixplugins/webcheckin/main.js" %}"></script>
|
||||
{% endcompress %}
|
||||
<script type="application/json" id="countries">{{ countries|escapejson_dumps }}</script>
|
||||
<script type="application/json" id="api-settings">
|
||||
{
|
||||
"urls": {
|
||||
"lists": "{% url "api-v1:checkinlist-list" event=request.event.slug organizer=request.organizer.slug %}",
|
||||
"questions": "{% url "api-v1:question-list" event=request.event.slug organizer=request.organizer.slug %}"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% vite_hmr %}
|
||||
{% vite_asset "src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.ts" %}
|
||||
{% csrf_token %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -576,7 +576,7 @@ def filter_subevents_with_plugins(subevents, sales_channel=None):
|
||||
if not app_cache:
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in filter_subevents._live_receivers(None):
|
||||
for receiver in filter_subevents._live_receivers(None)[0]:
|
||||
app = get_defining_app(receiver)
|
||||
event_state = {}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.views.decorators.http import condition
|
||||
from django.views.i18n import (
|
||||
JavaScriptCatalog, get_formats, js_catalog_template,
|
||||
JavaScriptCatalog, builtin_template_path, get_formats,
|
||||
)
|
||||
from lxml import html
|
||||
|
||||
@@ -121,22 +121,9 @@ def widget_css_etag(request, version, **kwargs):
|
||||
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
|
||||
|
||||
|
||||
def _use_vite(request):
|
||||
if getattr(settings, 'PRETIX_WIDGET_VITE', False):
|
||||
return True
|
||||
origin = request.META.get('HTTP_ORIGIN', '')
|
||||
gs = GlobalSettingsObject()
|
||||
vite_origins = gs.settings.get('widget_vite_origins', as_type=str, default='')
|
||||
if origin and vite_origins:
|
||||
origins_list = [o.strip() for o in vite_origins.strip().splitlines() if o.strip()]
|
||||
return origin in origins_list
|
||||
return False
|
||||
|
||||
|
||||
def widget_js_etag(request, version, lang, **kwargs):
|
||||
gs = GlobalSettingsObject()
|
||||
variant = 'vite' if _use_vite(request) else 'legacy'
|
||||
return gs.settings.get('widget_checksum_{}_{}_{}'.format(version, lang, variant))
|
||||
return gs.settings.get('widget_checksum_{}_{}'.format(version, lang))
|
||||
|
||||
|
||||
@gzip_page
|
||||
@@ -165,16 +152,13 @@ def widget_css(request, version, **kwargs):
|
||||
return resp
|
||||
|
||||
|
||||
def generate_widget_js(version, lang, use_vite=False):
|
||||
def generate_widget_js(version, lang):
|
||||
code = []
|
||||
with language(lang):
|
||||
# Provide isolation
|
||||
code.append('(function (siteglobals) {\n')
|
||||
code.append('var module = {}, exports = {};\n')
|
||||
if use_vite:
|
||||
code.append('const LANG = "%s";\n' % lang)
|
||||
else:
|
||||
code.append('var lang = "%s";\n' % lang)
|
||||
code.append('var lang = "%s";\n' % lang)
|
||||
|
||||
c = JavaScriptCatalog()
|
||||
c.translation = DjangoTranslation(lang, domain='djangojs')
|
||||
@@ -186,7 +170,8 @@ def generate_widget_js(version, lang, use_vite=False):
|
||||
'September', 'October', 'November', 'December'
|
||||
)
|
||||
catalog = dict((k, v) for k, v in catalog.items() if k.startswith('widget\u0004') or k in str_wl)
|
||||
template = Engine().from_string(js_catalog_template)
|
||||
with builtin_template_path("i18n_catalog.js").open(encoding="utf-8") as fh:
|
||||
template = Engine().from_string(fh.read())
|
||||
context = Context({
|
||||
'catalog_str': indent(json.dumps(
|
||||
catalog, sort_keys=True, indent=2)) if catalog else None,
|
||||
@@ -195,25 +180,20 @@ def generate_widget_js(version, lang, use_vite=False):
|
||||
'plural': plural,
|
||||
})
|
||||
i18n_js = template.render(context)
|
||||
i18n_js = i18n_js.replace('for (const ', 'for (var ') # remove if we really want to break IE11 for good
|
||||
i18n_js = i18n_js.replace(r"value.includes(", r"-1 != value.indexOf(") # remove if we really want to break IE11 for good
|
||||
code.append(i18n_js)
|
||||
|
||||
if use_vite:
|
||||
vite_js = finders.find('vite/widget/widget.js')
|
||||
if not vite_js:
|
||||
raise FileNotFoundError('Vite widget build not found. Run: npm run build:widget')
|
||||
with open(vite_js, 'r', encoding='utf-8') as fp:
|
||||
files = [
|
||||
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
|
||||
'pretixpresale/js/widget/docready.js',
|
||||
'pretixpresale/js/widget/floatformat.js',
|
||||
'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
|
||||
]
|
||||
for fname in files:
|
||||
f = finders.find(fname)
|
||||
with open(f, 'r', encoding='utf-8') as fp:
|
||||
code.append(fp.read())
|
||||
else:
|
||||
files = [
|
||||
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
|
||||
'pretixpresale/js/widget/docready.js',
|
||||
'pretixpresale/js/widget/floatformat.js',
|
||||
'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
|
||||
]
|
||||
for fname in files:
|
||||
f = finders.find(fname)
|
||||
with open(f, 'r', encoding='utf-8') as fp:
|
||||
code.append(fp.read())
|
||||
|
||||
if settings.DEBUG:
|
||||
code.append('})(this);\n')
|
||||
@@ -234,22 +214,15 @@ def widget_js(request, version, lang, **kwargs):
|
||||
if version < version_min:
|
||||
version = version_min
|
||||
|
||||
use_vite = _use_vite(request)
|
||||
variant = 'vite' if use_vite else 'legacy'
|
||||
cache_prefix = 'widget_js_data_v{}_{}_{}'.format(version, lang, variant)
|
||||
|
||||
cached_js = cache.get(cache_prefix)
|
||||
cached_js = cache.get('widget_js_data_v{}_{}'.format(version, lang))
|
||||
if cached_js and not settings.DEBUG:
|
||||
resp = HttpResponse(cached_js, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
settings_key = 'widget_file_v{}_{}_{}'.format(version, lang, variant)
|
||||
checksum_key = 'widget_checksum_v{}_{}_{}'.format(version, lang, variant)
|
||||
|
||||
gs = GlobalSettingsObject()
|
||||
fname = gs.settings.get(settings_key)
|
||||
fname = gs.settings.get('widget_file_v{}_{}'.format(version, lang))
|
||||
resp = None
|
||||
if fname and not settings.DEBUG:
|
||||
if isinstance(fname, File):
|
||||
@@ -257,21 +230,21 @@ def widget_js(request, version, lang, **kwargs):
|
||||
try:
|
||||
data = default_storage.open(fname).read()
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
cache.set('widget_js_data_v{}_{}'.format(version, lang), data, 3600 * 4)
|
||||
except:
|
||||
logger.exception('Failed to open widget.js')
|
||||
|
||||
if not resp:
|
||||
data = generate_widget_js(version, lang, use_vite=use_vite).encode()
|
||||
data = generate_widget_js(version, lang).encode()
|
||||
checksum = hashlib.sha1(data).hexdigest()
|
||||
if not settings.DEBUG:
|
||||
newname = default_storage.save(
|
||||
'widget/widget.{}.{}.{}.{}.js'.format(version, lang, variant, checksum),
|
||||
'widget/widget.{}.{}.{}.js'.format(version, lang, checksum),
|
||||
ContentFile(data)
|
||||
)
|
||||
gs.settings.set(settings_key, 'file://' + newname)
|
||||
gs.settings.set(checksum_key, checksum)
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
gs.settings.set('widget_file_v{}_{}'.format(version, lang), 'file://' + newname)
|
||||
gs.settings.set('widget_checksum_v{}_{}'.format(version, lang), checksum)
|
||||
cache.set('widget_js_data_v{}_{}'.format(version, lang), data, 3600 * 4)
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
resp._csp_ignore = True
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
|
||||
@@ -534,6 +534,7 @@ X_FRAME_OPTIONS = 'DENY'
|
||||
|
||||
# URL settings
|
||||
ROOT_URLCONF = 'pretix.multidomain.maindomain_urlconf'
|
||||
FORMS_URLFIELD_ASSUME_HTTPS = True # transitional for django 6.0
|
||||
|
||||
WSGI_APPLICATION = 'pretix.wsgi.application'
|
||||
|
||||
@@ -869,10 +870,3 @@ FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix
|
||||
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
|
||||
VITE_DEV_SERVER_PORT = 5173
|
||||
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
||||
VITE_DEV_MODE = DEBUG
|
||||
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
||||
PRETIX_WIDGET_VITE = os.environ.get('PRETIX_WIDGET_VITE', '') not in ('', '0')
|
||||
|
||||
6405
src/pretix/static/npm_dir/package-lock.json
generated
Normal file
6405
src/pretix/static/npm_dir/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
src/pretix/static/npm_dir/package.json
Normal file
16
src/pretix/static/npm_dir/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "pretix",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.28.5",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"vue": "^2.7.16",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue-template-compiler": "^2.7.16"
|
||||
}
|
||||
}
|
||||
23
src/pretix/static/npm_dir/rollup.config.js
Normal file
23
src/pretix/static/npm_dir/rollup.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import vue from 'rollup-plugin-vue'
|
||||
import { getBabelOutputPlugin } from '@rollup/plugin-babel'
|
||||
|
||||
export default {
|
||||
output: {
|
||||
format: 'iife',
|
||||
exports: 'named',
|
||||
},
|
||||
plugins: [
|
||||
getBabelOutputPlugin({
|
||||
presets: ['@babel/preset-env'],
|
||||
// Running babel on iife output is apparently discouraged since it can lead to global
|
||||
// variable leaks. Since we didn't get it to work on inputs, let's take that risk.
|
||||
// (In our tests, it did not leak anything.)
|
||||
allowAllFormats: true
|
||||
}),
|
||||
vue({
|
||||
css: true,
|
||||
compileTemplate: true,
|
||||
needMap: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
318
src/pretix/static/pretixcontrol/js/ui/checkinrules.js
Normal file
318
src/pretix/static/pretixcontrol/js/ui/checkinrules.js
Normal file
@@ -0,0 +1,318 @@
|
||||
$(function () {
|
||||
var TYPEOPS = {
|
||||
// Every change to our supported JSON logic must be done
|
||||
// * in pretix.base.services.checkin
|
||||
// * in pretix.base.models.checkin
|
||||
// * in pretix.helpers.jsonlogic_boolalg
|
||||
// * in checkinrules.js
|
||||
// * in libpretixsync
|
||||
// * in pretixscan-ios
|
||||
'product': {
|
||||
'inList': {
|
||||
'label': gettext('is one of'),
|
||||
'cardinality': 2,
|
||||
}
|
||||
},
|
||||
'variation': {
|
||||
'inList': {
|
||||
'label': gettext('is one of'),
|
||||
'cardinality': 2,
|
||||
}
|
||||
},
|
||||
'gate': {
|
||||
'inList': {
|
||||
'label': gettext('is one of'),
|
||||
'cardinality': 2,
|
||||
}
|
||||
},
|
||||
'datetime': {
|
||||
'isBefore': {
|
||||
'label': gettext('is before'),
|
||||
'cardinality': 2,
|
||||
},
|
||||
'isAfter': {
|
||||
'label': gettext('is after'),
|
||||
'cardinality': 2,
|
||||
},
|
||||
},
|
||||
'enum_entry_status': {
|
||||
'==': {
|
||||
'label': gettext('='),
|
||||
'cardinality': 2,
|
||||
},
|
||||
},
|
||||
'int_by_datetime': {
|
||||
'<': {
|
||||
'label': '<',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'<=': {
|
||||
'label': '≤',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'>': {
|
||||
'label': '>',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'>=': {
|
||||
'label': '≥',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'==': {
|
||||
'label': '=',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'!=': {
|
||||
'label': '≠',
|
||||
'cardinality': 2,
|
||||
},
|
||||
},
|
||||
'int': {
|
||||
'<': {
|
||||
'label': '<',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'<=': {
|
||||
'label': '≤',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'>': {
|
||||
'label': '>',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'>=': {
|
||||
'label': '≥',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'==': {
|
||||
'label': '=',
|
||||
'cardinality': 2,
|
||||
},
|
||||
'!=': {
|
||||
'label': '≠',
|
||||
'cardinality': 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
var VARS = {
|
||||
'product': {
|
||||
'label': gettext('Product'),
|
||||
'type': 'product',
|
||||
},
|
||||
'variation': {
|
||||
'label': gettext('Product variation'),
|
||||
'type': 'variation',
|
||||
},
|
||||
'gate': {
|
||||
'label': gettext('Gate'),
|
||||
'type': 'gate',
|
||||
},
|
||||
'now': {
|
||||
'label': gettext('Current date and time'),
|
||||
'type': 'datetime',
|
||||
},
|
||||
'now_isoweekday': {
|
||||
'label': gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
|
||||
'type': 'int',
|
||||
},
|
||||
'entry_status': {
|
||||
'label': gettext('Current entry status'),
|
||||
'type': 'enum_entry_status',
|
||||
},
|
||||
'entries_number': {
|
||||
'label': gettext('Number of previous entries'),
|
||||
'type': 'int',
|
||||
},
|
||||
'entries_today': {
|
||||
'label': gettext('Number of previous entries since midnight'),
|
||||
'type': 'int',
|
||||
},
|
||||
'entries_since': {
|
||||
'label': gettext('Number of previous entries since'),
|
||||
'type': 'int_by_datetime',
|
||||
},
|
||||
'entries_before': {
|
||||
'label': gettext('Number of previous entries before'),
|
||||
'type': 'int_by_datetime',
|
||||
},
|
||||
'entries_days': {
|
||||
'label': gettext('Number of days with a previous entry'),
|
||||
'type': 'int',
|
||||
},
|
||||
'entries_days_since': {
|
||||
'label': gettext('Number of days with a previous entry since'),
|
||||
'type': 'int_by_datetime',
|
||||
},
|
||||
'entries_days_before': {
|
||||
'label': gettext('Number of days with a previous entry before'),
|
||||
'type': 'int_by_datetime',
|
||||
},
|
||||
'minutes_since_last_entry': {
|
||||
'label': gettext('Minutes since last entry (-1 on first entry)'),
|
||||
'type': 'int',
|
||||
},
|
||||
'minutes_since_first_entry': {
|
||||
'label': gettext('Minutes since first entry (-1 on first entry)'),
|
||||
'type': 'int',
|
||||
},
|
||||
};
|
||||
|
||||
var components = {
|
||||
CheckinRulesVisualization: CheckinRulesVisualization.default,
|
||||
}
|
||||
if (typeof CheckinRule !== "undefined") {
|
||||
Vue.component('checkin-rule', CheckinRule.default);
|
||||
components = {
|
||||
CheckinRulesEditor: CheckinRulesEditor.default,
|
||||
CheckinRulesVisualization: CheckinRulesVisualization.default,
|
||||
}
|
||||
}
|
||||
var app = new Vue({
|
||||
el: '#rules-editor',
|
||||
components: components,
|
||||
data: function () {
|
||||
return {
|
||||
rules: {},
|
||||
items: [],
|
||||
all_products: false,
|
||||
limit_products: [],
|
||||
TYPEOPS: TYPEOPS,
|
||||
VARS: VARS,
|
||||
texts: {
|
||||
and: gettext('All of the conditions below (AND)'),
|
||||
or: gettext('At least one of the conditions below (OR)'),
|
||||
date_from: gettext('Event start'),
|
||||
date_to: gettext('Event end'),
|
||||
date_admission: gettext('Event admission'),
|
||||
date_custom: gettext('custom date and time'),
|
||||
date_customtime: gettext('custom time'),
|
||||
date_tolerance: gettext('Tolerance (minutes)'),
|
||||
condition_add: gettext('Add condition'),
|
||||
minutes: gettext('minutes'),
|
||||
duplicate: gettext('Duplicate'),
|
||||
status_present: pgettext('entry_status', 'present'),
|
||||
status_absent: pgettext('entry_status', 'absent'),
|
||||
},
|
||||
hasRules: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
missingItems: function () {
|
||||
// This computed property contains list of item or variation names that
|
||||
// a) Are allowed on the checkin list according to all_products or include_products
|
||||
// b) Are not matched by ANY logical branch of the rule.
|
||||
// The list will be empty if there is a "catch-all" rule.
|
||||
var products_seen = {};
|
||||
var variations_seen = {};
|
||||
var rules = convert_to_dnf(this.rules);
|
||||
var branch_without_product_filter = false;
|
||||
|
||||
if (!rules["or"]) {
|
||||
rules = {"or": [rules]}
|
||||
}
|
||||
|
||||
for (var part of rules["or"]) {
|
||||
if (!part["and"]) {
|
||||
part = {"and": [part]}
|
||||
}
|
||||
var this_branch_without_product_filter = true;
|
||||
for (var subpart of part["and"]) {
|
||||
if (subpart["inList"]) {
|
||||
if (subpart["inList"][0]["var"] === "product" && subpart["inList"][1]) {
|
||||
this_branch_without_product_filter = false;
|
||||
for (var listentry of subpart["inList"][1]["objectList"]) {
|
||||
products_seen[parseInt(listentry["lookup"][1])] = true
|
||||
}
|
||||
} else if (subpart["inList"][0]["var"] === "variation" && subpart["inList"][1]) {
|
||||
this_branch_without_product_filter = false;
|
||||
for (var listentry_ of subpart["inList"][1]["objectList"]) {
|
||||
variations_seen[parseInt(listentry_["lookup"][1])] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this_branch_without_product_filter) {
|
||||
branch_without_product_filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (branch_without_product_filter || (!Object.keys(products_seen).length && !Object.keys(variations_seen).length)) {
|
||||
// At least one branch with no product filters at all – that's fine.
|
||||
return [];
|
||||
}
|
||||
|
||||
var missing = [];
|
||||
for (var item of this.items) {
|
||||
if (products_seen[item.id]) continue;
|
||||
if (!this.all_products && !this.limit_products.includes(item.id)) continue;
|
||||
if (item.variations.length > 0) {
|
||||
for (var variation of item.variations) {
|
||||
if (variations_seen[variation.id]) continue;
|
||||
missing.push(item.name + " – " + variation.name)
|
||||
}
|
||||
} else {
|
||||
missing.push(item.name)
|
||||
}
|
||||
}
|
||||
return missing;
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.rules = JSON.parse($("#id_rules").val());
|
||||
if ($("#items").length) {
|
||||
this.items = JSON.parse($("#items").html());
|
||||
|
||||
var root = this.$root
|
||||
|
||||
function _update() {
|
||||
root.all_products = $("#id_all_products").prop("checked")
|
||||
root.limit_products = $("input[name=limit_products]:checked").map(function () {
|
||||
return parseInt($(this).val())
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
$("#id_all_products, input[name=limit_products]").on("change", function () {
|
||||
_update();
|
||||
})
|
||||
_update()
|
||||
|
||||
function check_for_invalid_ids(valid_products, valid_variations, rule) {
|
||||
if (rule["and"]) {
|
||||
for(const child of rule["and"])
|
||||
check_for_invalid_ids(valid_products, valid_variations, child);
|
||||
} else if (rule["or"]) {
|
||||
for(const child of rule["or"])
|
||||
check_for_invalid_ids(valid_products, valid_variations, child);
|
||||
} else if (rule["inList"] && rule["inList"][0]["var"] === "product") {
|
||||
for(const item of rule["inList"][1]["objectList"]) {
|
||||
if (!valid_products[item["lookup"][1]])
|
||||
item["lookup"][2] = "[" + gettext('Error: Product not found!') + "]";
|
||||
else
|
||||
item["lookup"][2] = valid_products[item["lookup"][1]];
|
||||
}
|
||||
} else if (rule["inList"] && rule["inList"][0]["var"] === "variation") {
|
||||
for(const item of rule["inList"][1]["objectList"]) {
|
||||
if (!valid_variations[item["lookup"][1]])
|
||||
item["lookup"][2] = "[" + gettext('Error: Variation not found!') + "]";
|
||||
else
|
||||
item["lookup"][2] = valid_variations[item["lookup"][1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
check_for_invalid_ids(
|
||||
Object.fromEntries(this.items.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(this.items.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]))),
|
||||
this.rules
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
rules: {
|
||||
deep: true,
|
||||
handler: function (newval) {
|
||||
$("#id_rules").val(JSON.stringify(newval));
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { rules as rawRules, items, allProducts, limitProducts } from './django-interop'
|
||||
import { convertToDNF } from './jsonlogic-boolalg'
|
||||
|
||||
import RulesEditor from './checkin-rules-editor.vue'
|
||||
import RulesVisualization from './checkin-rules-visualization.vue'
|
||||
|
||||
const gettext = (window as any).gettext
|
||||
|
||||
const missingItems = computed(() => {
|
||||
// This computed variable contains list of item or variation names that
|
||||
// a) Are allowed on the checkin list according to all_products or include_products
|
||||
// b) Are not matched by ANY logical branch of the rule.
|
||||
// The list will be empty if there is a "catch-all" rule.
|
||||
let productsSeen = {}
|
||||
let variationsSeen = {}
|
||||
let rules = convertToDNF(rawRules.value)
|
||||
let branchWithoutProductFilter = false
|
||||
|
||||
if (!rules.or) {
|
||||
rules = { or: [rules] }
|
||||
}
|
||||
|
||||
for (let part of rules.or) {
|
||||
if (!part.and) {
|
||||
part = { and: [part] }
|
||||
}
|
||||
let thisBranchWithoutProductFilter = true
|
||||
for (let subpart of part.and) {
|
||||
if (subpart.inList) {
|
||||
if (subpart.inList[0].var === 'product' && subpart.inList[1]) {
|
||||
thisBranchWithoutProductFilter = false
|
||||
for (let listentry of subpart.inList[1].objectList) {
|
||||
productsSeen[parseInt(listentry.lookup[1])] = true
|
||||
}
|
||||
} else if (subpart.inList[0].var === 'variation' && subpart.inList[1]) {
|
||||
thisBranchWithoutProductFilter = false
|
||||
for (let listentry_ of subpart.inList[1].objectList) {
|
||||
variationsSeen[parseInt(listentry_.lookup[1])] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (thisBranchWithoutProductFilter) {
|
||||
branchWithoutProductFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (branchWithoutProductFilter || (!Object.keys(productsSeen).length && !Object.keys(variationsSeen).length)) {
|
||||
// At least one branch with no product filters at all – that's fine.
|
||||
return []
|
||||
}
|
||||
|
||||
let missing = []
|
||||
for (const item of items.value) {
|
||||
if (productsSeen[item.id]) continue
|
||||
if (!allProducts.value && !limitProducts.value.includes(item.id)) continue
|
||||
if (item.variations.length > 0) {
|
||||
for (let variation of item.variations) {
|
||||
if (variationsSeen[variation.id]) continue
|
||||
missing.push(item.name + ' – ' + variation.name)
|
||||
}
|
||||
} else {
|
||||
missing.push(item.name)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
#rules-editor.form-inline
|
||||
div
|
||||
ul.nav.nav-tabs(role="tablist")
|
||||
li.active(role="presentation")
|
||||
a(href="#rules-edit", role="tab", data-toggle="tab")
|
||||
span.fa.fa-edit
|
||||
| {{ gettext("Edit") }}
|
||||
li(role="presentation")
|
||||
a(href="#rules-viz", role="tab", data-toggle="tab")
|
||||
span.fa.fa-eye
|
||||
| {{ gettext("Visualize") }}
|
||||
|
||||
//- Tab panes
|
||||
.tab-content
|
||||
#rules-edit.tab-pane.active(v-if="items", role="tabpanel")
|
||||
RulesEditor
|
||||
#rules-viz.tab-pane(role="tabpanel")
|
||||
RulesVisualization
|
||||
|
||||
.alert.alert-info(v-if="missingItems.length")
|
||||
p {{ gettext("Your rule always filters by product or variation, but the following products or variations are not contained in any of your rule parts so people with these tickets will not get in:") }}
|
||||
ul
|
||||
li(v-for="h in missingItems", :key="h") {{ h }}
|
||||
p {{ gettext("Please double-check if this was intentional.") }}
|
||||
</template>
|
||||
<style lang="stylus">
|
||||
</style>
|
||||
@@ -1,365 +1,355 @@
|
||||
<script setup lang="ts">
|
||||
/* eslint-disable vue/no-mutating-props */
|
||||
import { computed } from 'vue'
|
||||
import { TEXTS, VARS, TYPEOPS } from './constants'
|
||||
import { productSelectURL, variationSelectURL, gateSelectURL } from './django-interop'
|
||||
import LookupSelect2 from './lookup-select2.vue'
|
||||
import Datetimefield from './datetimefield.vue'
|
||||
import Timefield from './timefield.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rule: any
|
||||
level: number
|
||||
index: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
remove: []
|
||||
duplicate: []
|
||||
}>()
|
||||
|
||||
const operator = computed(() => Object.keys(props.rule)[0])
|
||||
const operands = computed(() => props.rule[operator.value])
|
||||
|
||||
const variable = computed(() => {
|
||||
const op = operator.value
|
||||
if (op === 'and' || op === 'or') {
|
||||
return op
|
||||
} else if (props.rule[op]?.[0]) {
|
||||
if (props.rule[op][0]['entries_since']) return 'entries_since'
|
||||
if (props.rule[op][0]['entries_before']) return 'entries_before'
|
||||
if (props.rule[op][0]['entries_days_since']) return 'entries_days_since'
|
||||
if (props.rule[op][0]['entries_days_before']) return 'entries_days_before'
|
||||
return props.rule[op][0]['var']
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const rightoperand = computed(() => {
|
||||
const op = operator.value
|
||||
if (op === 'and' || op === 'or') return null
|
||||
return props.rule[op]?.[1] ?? null
|
||||
})
|
||||
|
||||
const classObject = computed(() => ({
|
||||
'checkin-rule': true,
|
||||
['checkin-rule-' + variable.value]: true
|
||||
}))
|
||||
|
||||
const vartype = computed(() => VARS[variable.value]?.type)
|
||||
|
||||
const timeType = computed(() => {
|
||||
if (vartype.value === 'int_by_datetime') {
|
||||
return props.rule[operator.value]?.[0]?.[variable.value]?.[0]?.buildTime?.[0]
|
||||
}
|
||||
return rightoperand.value?.buildTime?.[0]
|
||||
})
|
||||
|
||||
const timeTolerance = computed(() => {
|
||||
const op = operator.value
|
||||
if ((op === 'isBefore' || op === 'isAfter') && props.rule[op]?.[2] !== undefined) {
|
||||
return props.rule[op][2]
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const timeValue = computed(() => {
|
||||
if (vartype.value === 'int_by_datetime') {
|
||||
return props.rule[operator.value]?.[0]?.[variable.value]?.[0]?.buildTime?.[1]
|
||||
}
|
||||
return rightoperand.value?.buildTime?.[1]
|
||||
})
|
||||
|
||||
const cardinality = computed(() => TYPEOPS[vartype.value]?.[operator.value]?.cardinality)
|
||||
const operators = computed(() => TYPEOPS[vartype.value])
|
||||
|
||||
function setVariable (event: Event) {
|
||||
const target = event.target as HTMLSelectElement
|
||||
const currentOp = Object.keys(props.rule)[0]
|
||||
let currentVal = props.rule[currentOp]
|
||||
|
||||
if (target.value === 'and' || target.value === 'or') {
|
||||
if (currentVal[0]?.var) currentVal = []
|
||||
props.rule[target.value] = currentVal
|
||||
delete props.rule[currentOp]
|
||||
} else {
|
||||
if (currentVal !== 'and' && currentVal !== 'or' && currentVal[0] && VARS[target.value]?.type === vartype.value) {
|
||||
if (vartype.value === 'int_by_datetime') {
|
||||
const currentData = props.rule[currentOp][0][variable.value]
|
||||
props.rule[currentOp][0] = { [target.value]: JSON.parse(JSON.stringify(currentData)) }
|
||||
} else {
|
||||
props.rule[currentOp][0].var = target.value
|
||||
}
|
||||
} else if (VARS[target.value]?.type === 'int_by_datetime') {
|
||||
delete props.rule[currentOp]
|
||||
props.rule['!!'] = [{ [target.value]: [{ buildTime: [null, null] }] }]
|
||||
} else {
|
||||
delete props.rule[currentOp]
|
||||
props.rule['!!'] = [{ var: target.value }]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setOperator (event: Event) {
|
||||
const target = event.target as HTMLSelectElement
|
||||
const currentOp = Object.keys(props.rule)[0]
|
||||
const currentVal = props.rule[currentOp]
|
||||
delete props.rule[currentOp]
|
||||
props.rule[target.value] = currentVal
|
||||
}
|
||||
|
||||
function setRightOperandNumber (event: Event) {
|
||||
const val = parseInt((event.target as HTMLInputElement).value)
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(val)
|
||||
} else {
|
||||
props.rule[operator.value][1] = val
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeTolerance (event: Event) {
|
||||
const val = parseInt((event.target as HTMLInputElement).value)
|
||||
if (props.rule[operator.value].length === 2) {
|
||||
props.rule[operator.value].push(val)
|
||||
} else {
|
||||
props.rule[operator.value][2] = val
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeType (event: Event) {
|
||||
const val = (event.target as HTMLSelectElement).value
|
||||
const time = { buildTime: [val] }
|
||||
if (vartype.value === 'int_by_datetime') {
|
||||
props.rule[operator.value][0][variable.value][0] = time
|
||||
} else {
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(time)
|
||||
} else {
|
||||
props.rule[operator.value][1] = time
|
||||
}
|
||||
if (val === 'custom') {
|
||||
props.rule[operator.value][2] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeValue (val: string) {
|
||||
if (vartype.value === 'int_by_datetime') {
|
||||
props.rule[operator.value][0][variable.value][0]['buildTime'][1] = val
|
||||
} else {
|
||||
props.rule[operator.value][1]['buildTime'][1] = val
|
||||
}
|
||||
}
|
||||
|
||||
function setRightOperandProductList (val: { id: any; text: string }[]) {
|
||||
const products = { objectList: val.map(item => ({ lookup: ['product', item.id, item.text] })) }
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(products)
|
||||
} else {
|
||||
props.rule[operator.value][1] = products
|
||||
}
|
||||
}
|
||||
|
||||
function setRightOperandVariationList (val: { id: any; text: string }[]) {
|
||||
const products = { objectList: val.map(item => ({ lookup: ['variation', item.id, item.text] })) }
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(products)
|
||||
} else {
|
||||
props.rule[operator.value][1] = products
|
||||
}
|
||||
}
|
||||
|
||||
function setRightOperandGateList (val: { id: any; text: string }[]) {
|
||||
const products = { objectList: val.map(item => ({ lookup: ['gate', item.id, item.text] })) }
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(products)
|
||||
} else {
|
||||
props.rule[operator.value][1] = products
|
||||
}
|
||||
}
|
||||
|
||||
function setRightOperandEnum (event: Event) {
|
||||
const val = (event.target as HTMLSelectElement).value
|
||||
if (props.rule[operator.value].length === 1) {
|
||||
props.rule[operator.value].push(val)
|
||||
} else {
|
||||
props.rule[operator.value][1] = val
|
||||
}
|
||||
}
|
||||
|
||||
function addOperand () {
|
||||
props.rule[operator.value].push({ '': [] })
|
||||
}
|
||||
|
||||
function wrapWithOR () {
|
||||
const r = JSON.parse(JSON.stringify(props.rule))
|
||||
delete props.rule[operator.value]
|
||||
props.rule.or = [r]
|
||||
}
|
||||
|
||||
function wrapWithAND () {
|
||||
const r = JSON.parse(JSON.stringify(props.rule))
|
||||
delete props.rule[operator.value]
|
||||
props.rule.and = [r]
|
||||
}
|
||||
|
||||
function cutOut () {
|
||||
const cop = Object.keys(operands.value[0])[0]
|
||||
const r = operands.value[0][cop]
|
||||
delete props.rule[operator.value]
|
||||
props.rule[cop] = r
|
||||
}
|
||||
|
||||
function remove () {
|
||||
emit('remove')
|
||||
}
|
||||
|
||||
function duplicate () {
|
||||
emit('duplicate')
|
||||
}
|
||||
|
||||
function removeChild (index: number) {
|
||||
props.rule[operator.value].splice(index, 1)
|
||||
}
|
||||
|
||||
function duplicateChild (index: number) {
|
||||
const r = JSON.parse(JSON.stringify(props.rule[operator.value][index]))
|
||||
props.rule[operator.value].splice(index, 0, r)
|
||||
}
|
||||
</script>
|
||||
<template lang="pug">
|
||||
div(:class="classObject")
|
||||
.btn-group.pull-right
|
||||
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||
v-if="level > 0",
|
||||
type="button",
|
||||
data-toggle="tooltip",
|
||||
:title="TEXTS.duplicate",
|
||||
@click.prevent="duplicate"
|
||||
)
|
||||
span.fa.fa-copy
|
||||
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||
type="button",
|
||||
@click.prevent="wrapWithOR"
|
||||
) OR
|
||||
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||
type="button",
|
||||
@click.prevent="wrapWithAND"
|
||||
) AND
|
||||
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||
v-if="operands && operands.length === 1 && (operator === 'or' || operator === 'and')",
|
||||
type="button",
|
||||
@click.prevent="cutOut"
|
||||
)
|
||||
span.fa.fa-cut
|
||||
button.checkin-rule-remove.btn.btn-xs.btn-default(
|
||||
v-if="level > 0",
|
||||
type="button",
|
||||
@click.prevent="remove"
|
||||
)
|
||||
span.fa.fa-trash
|
||||
select.form-control(:value="variable", required, @input="setVariable")
|
||||
option(value="and") {{ TEXTS.and }}
|
||||
option(value="or") {{ TEXTS.or }}
|
||||
option(v-for="(v, name) in VARS", :key="name", :value="name") {{ v.label }}
|
||||
select.form-control(
|
||||
v-if="operator !== 'or' && operator !== 'and' && vartype !== 'int_by_datetime'",
|
||||
:value="operator",
|
||||
required,
|
||||
@input="setOperator"
|
||||
)
|
||||
option
|
||||
option(v-for="(v, name) in operators", :key="name", :value="name") {{ v.label }}
|
||||
select.form-control(
|
||||
v-if="vartype === 'datetime' || vartype === 'int_by_datetime'",
|
||||
:value="timeType",
|
||||
required,
|
||||
@input="setTimeType"
|
||||
)
|
||||
option(value="date_from") {{ TEXTS.date_from }}
|
||||
option(value="date_to") {{ TEXTS.date_to }}
|
||||
option(value="date_admission") {{ TEXTS.date_admission }}
|
||||
option(value="custom") {{ TEXTS.date_custom }}
|
||||
option(value="customtime") {{ TEXTS.date_customtime }}
|
||||
Datetimefield(
|
||||
v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'custom'",
|
||||
:value="timeValue",
|
||||
@input="setTimeValue"
|
||||
)
|
||||
Timefield(
|
||||
v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'customtime'",
|
||||
:value="timeValue",
|
||||
@input="setTimeValue"
|
||||
)
|
||||
input.form-control(
|
||||
v-if="vartype === 'datetime' && timeType && timeType !== 'customtime' && timeType !== 'custom'",
|
||||
required,
|
||||
type="number",
|
||||
:value="timeTolerance",
|
||||
:placeholder="TEXTS.date_tolerance",
|
||||
@input="setTimeTolerance"
|
||||
)
|
||||
select.form-control(
|
||||
v-if="vartype === 'int_by_datetime'",
|
||||
:value="operator",
|
||||
required,
|
||||
@input="setOperator"
|
||||
)
|
||||
option
|
||||
option(v-for="(v, name) in operators", :key="name", :value="name") {{ v.label }}
|
||||
input.form-control(
|
||||
v-if="(vartype === 'int' || vartype === 'int_by_datetime') && cardinality > 1",
|
||||
required,
|
||||
type="number",
|
||||
:value="rightoperand",
|
||||
@input="setRightOperandNumber"
|
||||
)
|
||||
LookupSelect2(
|
||||
v-if="vartype === 'product' && operator === 'inList'",
|
||||
required,
|
||||
:multiple="true",
|
||||
:value="rightoperand",
|
||||
:url="productSelectURL",
|
||||
@input="setRightOperandProductList"
|
||||
)
|
||||
LookupSelect2(
|
||||
v-if="vartype === 'variation' && operator === 'inList'",
|
||||
required,
|
||||
:multiple="true",
|
||||
:value="rightoperand",
|
||||
:url="variationSelectURL",
|
||||
@input="setRightOperandVariationList"
|
||||
)
|
||||
LookupSelect2(
|
||||
v-if="vartype === 'gate' && operator === 'inList'",
|
||||
required,
|
||||
:multiple="true",
|
||||
:value="rightoperand",
|
||||
:url="gateSelectURL",
|
||||
@input="setRightOperandGateList"
|
||||
)
|
||||
select.form-control(
|
||||
v-if="vartype === 'enum_entry_status' && operator === '=='",
|
||||
required,
|
||||
:value="rightoperand",
|
||||
@input="setRightOperandEnum"
|
||||
)
|
||||
option(value="absent") {{ TEXTS.status_absent }}
|
||||
option(value="present") {{ TEXTS.status_present }}
|
||||
.checkin-rule-childrules(v-if="operator === 'or' || operator === 'and'")
|
||||
div(v-for="(op, opi) in operands", :key="opi")
|
||||
CheckinRule(
|
||||
v-if="typeof op === 'object'",
|
||||
:rule="op",
|
||||
:index="opi",
|
||||
:level="level + 1",
|
||||
@remove="removeChild(opi)",
|
||||
@duplicate="duplicateChild(opi)"
|
||||
)
|
||||
button.checkin-rule-addchild.btn.btn-xs.btn-default(
|
||||
type="button",
|
||||
@click.prevent="addOperand"
|
||||
)
|
||||
span.fa.fa-plus-circle
|
||||
| {{ TEXTS.condition_add }}
|
||||
<template>
|
||||
<div v-bind:class="classObject">
|
||||
<div class="btn-group pull-right">
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="duplicate"
|
||||
v-if="level > 0" data-toggle="tooltip" :title="texts.duplicate">
|
||||
<span class="fa fa-copy"></span>
|
||||
</button>
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithOR">OR
|
||||
</button>
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithAND">AND
|
||||
</button>
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="cutOut"
|
||||
v-if="operands && operands.length === 1 && (operator === 'or' || operator === 'and')"><span
|
||||
class="fa fa-cut"></span></button>
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="remove"
|
||||
v-if="level > 0"><span class="fa fa-trash"></span></button>
|
||||
</div>
|
||||
<select v-bind:value="variable" v-on:input="setVariable" required class="form-control">
|
||||
<option value="and">{{texts.and}}</option>
|
||||
<option value="or">{{texts.or}}</option>
|
||||
<option v-for="(v, name) in vars" :value="name">{{ v.label }}</option>
|
||||
</select>
|
||||
<select v-bind:value="operator" v-on:input="setOperator" required class="form-control"
|
||||
v-if="operator !== 'or' && operator !== 'and' && vartype !== 'int_by_datetime'">
|
||||
<option></option>
|
||||
<option v-for="(v, name) in operators" :value="name">{{ v.label }}</option>
|
||||
</select>
|
||||
<select v-bind:value="timeType" v-on:input="setTimeType" required class="form-control"
|
||||
v-if="vartype === 'datetime' || vartype === 'int_by_datetime'">
|
||||
<option value="date_from">{{texts.date_from}}</option>
|
||||
<option value="date_to">{{texts.date_to}}</option>
|
||||
<option value="date_admission">{{texts.date_admission}}</option>
|
||||
<option value="custom">{{texts.date_custom}}</option>
|
||||
<option value="customtime">{{texts.date_customtime}}</option>
|
||||
</select>
|
||||
<datetimefield v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'custom'" :value="timeValue"
|
||||
v-on:input="setTimeValue"></datetimefield>
|
||||
<timefield v-if="(vartype === 'datetime' || vartype === 'int_by_datetime') && timeType === 'customtime'" :value="timeValue"
|
||||
v-on:input="setTimeValue"></timefield>
|
||||
<input class="form-control" required type="number"
|
||||
v-if="vartype === 'datetime' && timeType && timeType !== 'customtime' && timeType !== 'custom'" v-bind:value="timeTolerance"
|
||||
v-on:input="setTimeTolerance" :placeholder="texts.date_tolerance">
|
||||
<select v-bind:value="operator" v-on:input="setOperator" required class="form-control"
|
||||
v-if="vartype === 'int_by_datetime'">
|
||||
<option></option>
|
||||
<option v-for="(v, name) in operators" :value="name">{{ v.label }}</option>
|
||||
</select>
|
||||
<input class="form-control" required type="number" v-if="(vartype === 'int' || vartype === 'int_by_datetime') && cardinality > 1"
|
||||
v-bind:value="rightoperand" v-on:input="setRightOperandNumber">
|
||||
<lookup-select2 required v-if="vartype === 'product' && operator === 'inList'" :multiple="true"
|
||||
:value="rightoperand" v-on:input="setRightOperandProductList"
|
||||
:url="productSelectURL"></lookup-select2>
|
||||
<lookup-select2 required v-if="vartype === 'variation' && operator === 'inList'" :multiple="true"
|
||||
:value="rightoperand" v-on:input="setRightOperandVariationList"
|
||||
:url="variationSelectURL"></lookup-select2>
|
||||
<lookup-select2 required v-if="vartype === 'gate' && operator === 'inList'" :multiple="true"
|
||||
:value="rightoperand" v-on:input="setRightOperandGateList"
|
||||
:url="gateSelectURL"></lookup-select2>
|
||||
<select required v-if="vartype === 'enum_entry_status' && operator === '=='"
|
||||
:value="rightoperand" v-on:input="setRightOperandEnum" class="form-control">
|
||||
<option value="absent">{{ texts.status_absent }}</option>
|
||||
<option value="present">{{ texts.status_present }}</option>
|
||||
</select>
|
||||
<div class="checkin-rule-childrules" v-if="operator === 'or' || operator === 'and'">
|
||||
<div v-for="(op, opi) in operands">
|
||||
<checkin-rule :rule="op" :index="opi" :level="level + 1" v-if="typeof op === 'object'"></checkin-rule>
|
||||
</div>
|
||||
<button type="button" class="checkin-rule-addchild btn btn-xs btn-default" @click.prevent="addOperand"><span
|
||||
class="fa fa-plus-circle"></span> {{ texts.condition_add }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
LookupSelect2: LookupSelect2.default,
|
||||
Datetimefield: Datetimefield.default,
|
||||
Timefield: Timefield.default,
|
||||
},
|
||||
props: {
|
||||
rule: Object,
|
||||
level: Number,
|
||||
index: Number,
|
||||
},
|
||||
computed: {
|
||||
texts: function () {
|
||||
return this.$root.texts;
|
||||
},
|
||||
variable: function () {
|
||||
var op = this.operator;
|
||||
if (op === "and" || op === "or") {
|
||||
return op;
|
||||
} else if (this.rule[op] && this.rule[op][0]) {
|
||||
if (this.rule[op][0]["entries_since"]) {
|
||||
return "entries_since";
|
||||
}
|
||||
if (this.rule[op][0]["entries_before"]) {
|
||||
return "entries_before";
|
||||
}
|
||||
if (this.rule[op][0]["entries_days_since"]) {
|
||||
return "entries_days_since";
|
||||
}
|
||||
if (this.rule[op][0]["entries_days_before"]) {
|
||||
return "entries_days_before";
|
||||
}
|
||||
return this.rule[op][0]["var"];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
rightoperand: function () {
|
||||
var op = this.operator;
|
||||
if (op === "and" || op === "or") {
|
||||
return null;
|
||||
} else if (this.rule[op] && typeof this.rule[op][1] !== "undefined") {
|
||||
return this.rule[op][1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
operator: function () {
|
||||
return Object.keys(this.rule)[0];
|
||||
},
|
||||
operands: function () {
|
||||
return this.rule[this.operator];
|
||||
},
|
||||
classObject: function () {
|
||||
var c = {
|
||||
'checkin-rule': true
|
||||
};
|
||||
c['checkin-rule-' + this.variable] = true;
|
||||
return c;
|
||||
},
|
||||
vartype: function () {
|
||||
if (this.variable && this.$root.VARS[this.variable]) {
|
||||
return this.$root.VARS[this.variable]['type'];
|
||||
}
|
||||
},
|
||||
timeType: function () {
|
||||
if (this.vartype === 'int_by_datetime') {
|
||||
if (this.rule[this.operator][0][this.variable] && this.rule[this.operator][0][this.variable][0]['buildTime']) {
|
||||
return this.rule[this.operator][0][this.variable][0]['buildTime'][0];
|
||||
}
|
||||
} else if (this.rightoperand && this.rightoperand['buildTime']) {
|
||||
return this.rightoperand['buildTime'][0];
|
||||
}
|
||||
},
|
||||
timeTolerance: function () {
|
||||
var op = this.operator;
|
||||
if ((op === "isBefore" || op === "isAfter") && this.rule[op] && typeof this.rule[op][2] !== "undefined") {
|
||||
return this.rule[op][2];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
timeValue: function () {
|
||||
if (this.vartype === 'int_by_datetime') {
|
||||
if (this.rule[this.operator][0][this.variable][0]['buildTime']) {
|
||||
return this.rule[this.operator][0][this.variable][0]['buildTime'][1];
|
||||
}
|
||||
} else if (this.rightoperand && this.rightoperand['buildTime']) {
|
||||
return this.rightoperand['buildTime'][1];
|
||||
}
|
||||
},
|
||||
cardinality: function () {
|
||||
if (this.vartype && this.$root.TYPEOPS[this.vartype] && this.$root.TYPEOPS[this.vartype][this.operator]) {
|
||||
return this.$root.TYPEOPS[this.vartype][this.operator]['cardinality'];
|
||||
}
|
||||
},
|
||||
operators: function () {
|
||||
return this.$root.TYPEOPS[this.vartype];
|
||||
},
|
||||
productSelectURL: function () {
|
||||
return $("#product-select2").text();
|
||||
},
|
||||
variationSelectURL: function () {
|
||||
return $("#variations-select2").text();
|
||||
},
|
||||
gateSelectURL: function () {
|
||||
return $("#gates-select2").text();
|
||||
},
|
||||
vars: function () {
|
||||
return this.$root.VARS;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setVariable: function (event) {
|
||||
var current_op = Object.keys(this.rule)[0];
|
||||
var current_val = this.rule[current_op];
|
||||
|
||||
if (event.target.value === "and" || event.target.value === "or") {
|
||||
if (current_val[0] && current_val[0]["var"]) {
|
||||
current_val = [];
|
||||
}
|
||||
this.$set(this.rule, event.target.value, current_val);
|
||||
this.$delete(this.rule, current_op);
|
||||
} else {
|
||||
if (current_val !== "and" && current_val !== "or" && current_val[0] && this.$root.VARS[event.target.value]['type'] === this.vartype) {
|
||||
if (this.vartype === "int_by_datetime") {
|
||||
var current_data = this.rule[current_op][0][this.variable];
|
||||
var new_lhs = {};
|
||||
new_lhs[event.target.value] = JSON.parse(JSON.stringify(current_data));
|
||||
this.$set(this.rule[current_op], 0, new_lhs);
|
||||
} else {
|
||||
this.$set(this.rule[current_op][0], "var", event.target.value);
|
||||
}
|
||||
} else if (this.$root.VARS[event.target.value]['type'] === 'int_by_datetime') {
|
||||
this.$delete(this.rule, current_op);
|
||||
var o = {};
|
||||
o[event.target.value] = [{"buildTime": [null, null]}]
|
||||
this.$set(this.rule, "!!", [o]);
|
||||
} else {
|
||||
this.$delete(this.rule, current_op);
|
||||
this.$set(this.rule, "!!", [{"var": event.target.value}]);
|
||||
}
|
||||
}
|
||||
},
|
||||
setOperator: function (event) {
|
||||
var current_op = Object.keys(this.rule)[0];
|
||||
var current_val = this.rule[current_op];
|
||||
this.$delete(this.rule, current_op);
|
||||
this.$set(this.rule, event.target.value, current_val);
|
||||
},
|
||||
setRightOperandNumber: function (event) {
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(parseInt(event.target.value));
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, parseInt(event.target.value));
|
||||
}
|
||||
},
|
||||
setTimeTolerance: function (event) {
|
||||
if (this.rule[this.operator].length === 2) {
|
||||
this.rule[this.operator].push(parseInt(event.target.value));
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 2, parseInt(event.target.value));
|
||||
}
|
||||
},
|
||||
setTimeType: function (event) {
|
||||
var time = {
|
||||
"buildTime": [event.target.value]
|
||||
};
|
||||
if (this.vartype === "int_by_datetime") {
|
||||
this.$set(this.rule[this.operator][0][this.variable], 0, time);
|
||||
} else {
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(time);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, time);
|
||||
}
|
||||
if (event.target.value === "custom") {
|
||||
this.$set(this.rule[this.operator], 2, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
setTimeValue: function (val) {
|
||||
if (this.vartype === "int_by_datetime") {
|
||||
this.$set(this.rule[this.operator][0][this.variable][0]["buildTime"], 1, val);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator][1]["buildTime"], 1, val);
|
||||
}
|
||||
},
|
||||
setRightOperandProductList: function (val) {
|
||||
var products = {
|
||||
"objectList": []
|
||||
};
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
products["objectList"].push({
|
||||
"lookup": [
|
||||
"product",
|
||||
val[i].id,
|
||||
val[i].text
|
||||
]
|
||||
});
|
||||
}
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(products);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, products);
|
||||
}
|
||||
},
|
||||
setRightOperandVariationList: function (val) {
|
||||
var products = {
|
||||
"objectList": []
|
||||
};
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
products["objectList"].push({
|
||||
"lookup": [
|
||||
"variation",
|
||||
val[i].id,
|
||||
val[i].text
|
||||
]
|
||||
});
|
||||
}
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(products);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, products);
|
||||
}
|
||||
},
|
||||
setRightOperandGateList: function (val) {
|
||||
var products = {
|
||||
"objectList": []
|
||||
};
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
products["objectList"].push({
|
||||
"lookup": [
|
||||
"gate",
|
||||
val[i].id,
|
||||
val[i].text
|
||||
]
|
||||
});
|
||||
}
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(products);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, products);
|
||||
}
|
||||
},
|
||||
setRightOperandEnum: function (event) {
|
||||
if (this.rule[this.operator].length === 1) {
|
||||
this.rule[this.operator].push(event.target.value);
|
||||
} else {
|
||||
this.$set(this.rule[this.operator], 1, event.target.value);
|
||||
}
|
||||
},
|
||||
addOperand: function () {
|
||||
this.rule[this.operator].push({"": []});
|
||||
},
|
||||
wrapWithOR: function () {
|
||||
var r = JSON.parse(JSON.stringify(this.rule));
|
||||
this.$delete(this.rule, this.operator);
|
||||
this.$set(this.rule, "or", [r]);
|
||||
},
|
||||
wrapWithAND: function () {
|
||||
var r = JSON.parse(JSON.stringify(this.rule));
|
||||
this.$delete(this.rule, this.operator);
|
||||
this.$set(this.rule, "and", [r]);
|
||||
},
|
||||
cutOut: function () {
|
||||
var cop = Object.keys(this.operands[0])[0];
|
||||
var r = this.operands[0][cop];
|
||||
this.$delete(this.rule, this.operator);
|
||||
this.$set(this.rule, cop, r);
|
||||
},
|
||||
remove: function () {
|
||||
this.$parent.rule[this.$parent.operator].splice(this.index, 1);
|
||||
},
|
||||
duplicate: function () {
|
||||
var r = JSON.parse(JSON.stringify(this.rule));
|
||||
this.$parent.rule[this.$parent.operator].splice(this.index, 0, r);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { TEXTS } from './constants'
|
||||
import { rules } from './django-interop'
|
||||
import CheckinRule from './checkin-rule.vue'
|
||||
|
||||
const hasRules = computed(() => !!Object.keys(rules.value).length)
|
||||
|
||||
function addRule () {
|
||||
rules.value.and = []
|
||||
}
|
||||
</script>
|
||||
<template lang="pug">
|
||||
.checkin-rules-editor
|
||||
CheckinRule(v-if="hasRules", :rule="rules", :level="0", :index="0")
|
||||
button.checkin-rule-addchild.btn.btn-xs.btn-default(
|
||||
v-if="!hasRules",
|
||||
type="button",
|
||||
@click.prevent="addRule"
|
||||
)
|
||||
span.fa.fa-plus-circle
|
||||
| {{ TEXTS.condition_add }}
|
||||
<template>
|
||||
<div class="checkin-rules-editor">
|
||||
<checkin-rule :rule="this.$root.rules" :level="0" :index="0" v-if="hasRules"></checkin-rule>
|
||||
<button type="button" class="checkin-rule-addchild btn btn-xs btn-default" v-if="!hasRules"
|
||||
@click.prevent="addRule"><span class="fa fa-plus-circle"></span> {{ this.$root.texts.condition_add }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
CheckinRule: CheckinRule.default,
|
||||
},
|
||||
computed: {
|
||||
hasRules: function () {
|
||||
return !!Object.keys(this.$root.rules).length;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addRule: function () {
|
||||
this.$set(this.$root.rules, "and", []);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,276 +1,255 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { rules } from './django-interop'
|
||||
import VizNode from './viz-node.vue'
|
||||
|
||||
declare const d3: any
|
||||
|
||||
const svg = ref<SVGSVGElement | null>(null)
|
||||
const maximized = ref(false)
|
||||
const zoom = ref<any>(null)
|
||||
const defaultScale = ref(1)
|
||||
const zoomTransform = ref(d3.zoomTransform({ k: 1, x: 0, y: 0 }))
|
||||
|
||||
const boxWidth = 300
|
||||
const boxHeight = 62
|
||||
const paddingX = 50
|
||||
const marginX = 50
|
||||
const marginY = 20
|
||||
|
||||
interface GraphNode {
|
||||
rule: any
|
||||
column: number
|
||||
children: string[]
|
||||
y?: number
|
||||
parent?: GraphNode
|
||||
}
|
||||
|
||||
interface Graph {
|
||||
nodes_by_id: Record<string, GraphNode>
|
||||
children: string[]
|
||||
columns: number
|
||||
height: number
|
||||
y?: number
|
||||
}
|
||||
|
||||
const graph = computed<Graph>(() => {
|
||||
/**
|
||||
* Converts a JSON logic rule into a "flow chart".
|
||||
*
|
||||
* A JSON logic rule has a structure like an operator tree:
|
||||
*
|
||||
* OR
|
||||
* |-- AND
|
||||
* |-- A
|
||||
* |-- B
|
||||
* |-- AND
|
||||
* |-- OR
|
||||
* |-- C
|
||||
* |-- D
|
||||
* |-- E
|
||||
*
|
||||
* For our visualization, we want to visualize that tree as a graph one can follow along to reach a
|
||||
* decision, which has the structure of a directed graph:
|
||||
*
|
||||
* --- A --- B --- OK!
|
||||
* /
|
||||
* /
|
||||
* /
|
||||
* --
|
||||
* \
|
||||
* \ --- C ---
|
||||
* \ / \
|
||||
* --- --- E --- OK!
|
||||
* \ /
|
||||
* --- D ---
|
||||
*/
|
||||
const graphData: Graph = {
|
||||
nodes_by_id: {},
|
||||
children: [],
|
||||
columns: -1,
|
||||
height: 1,
|
||||
}
|
||||
|
||||
// Step 1: Start building the graph by finding all nodes and edges
|
||||
let counter = 0
|
||||
const _add_to_graph = (rule: any): [string[], string[]] => { // returns [heads, tails]
|
||||
if (typeof rule !== 'object' || rule === null) {
|
||||
const node_id = (counter++).toString()
|
||||
graphData.nodes_by_id[node_id] = {
|
||||
rule: rule,
|
||||
column: -1,
|
||||
children: [],
|
||||
}
|
||||
return [[node_id], [node_id]]
|
||||
}
|
||||
|
||||
const operator = Object.keys(rule)[0]
|
||||
const operands = rule[operator]
|
||||
|
||||
if (operator === 'and') {
|
||||
let children: string[] = []
|
||||
let tails: string[] | null = null
|
||||
operands.reverse()
|
||||
for (const operand of operands) {
|
||||
const [new_children, new_tails] = _add_to_graph(operand)
|
||||
for (const new_child of new_tails) {
|
||||
graphData.nodes_by_id[new_child].children.push(...children)
|
||||
for (const c of children) {
|
||||
graphData.nodes_by_id[c].parent = graphData.nodes_by_id[new_child]
|
||||
}
|
||||
}
|
||||
if (tails === null) {
|
||||
tails = new_tails
|
||||
}
|
||||
children = new_children
|
||||
}
|
||||
return [children, tails!]
|
||||
} else if (operator === 'or') {
|
||||
const children: string[] = []
|
||||
const tails: string[] = []
|
||||
for (const operand of operands) {
|
||||
const [new_children, new_tails] = _add_to_graph(operand)
|
||||
children.push(...new_children)
|
||||
tails.push(...new_tails)
|
||||
}
|
||||
return [children, tails]
|
||||
} else {
|
||||
const node_id = (counter++).toString()
|
||||
graphData.nodes_by_id[node_id] = {
|
||||
rule: rule,
|
||||
column: -1,
|
||||
children: [],
|
||||
}
|
||||
return [[node_id], [node_id]]
|
||||
}
|
||||
}
|
||||
graphData.children = _add_to_graph(JSON.parse(JSON.stringify(rules.value)))[0]
|
||||
|
||||
// Step 2: We compute the "column" of every node, which is the maximum number of hops required to reach the
|
||||
// node from the root node
|
||||
const _set_column_to_min = (nodes: GraphNode[], mincol: number) => {
|
||||
for (const node of nodes) {
|
||||
if (mincol > node.column) {
|
||||
node.column = mincol
|
||||
graphData.columns = Math.max(mincol + 1, graphData.columns)
|
||||
_set_column_to_min(node.children.map(nid => graphData.nodes_by_id[nid]), mincol + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
_set_column_to_min(graphData.children.map(nid => graphData.nodes_by_id[nid]), 0)
|
||||
|
||||
// Step 3: Align each node on a grid. The x position is already given by the column computed above, but we still
|
||||
// need the y position. This part of the algorithm is opinionated and probably not yet the nicest solution we
|
||||
// can use!
|
||||
const _set_y = (node: Graph | GraphNode, offset: number): number => {
|
||||
if (typeof node.y === 'undefined') {
|
||||
// We only take the first value we found for each node
|
||||
node.y = offset
|
||||
}
|
||||
|
||||
let used = 0
|
||||
for (const cid of node.children) {
|
||||
used += Math.max(0, _set_y(graphData.nodes_by_id[cid], offset + used) - 1)
|
||||
used++
|
||||
}
|
||||
return used
|
||||
}
|
||||
_set_y(graphData, 0)
|
||||
|
||||
// Step 4: Compute the "height" of the graph by looking at the node with the highest y value
|
||||
graphData.height = 1
|
||||
for (const node of [...Object.values(graphData.nodes_by_id)]) {
|
||||
graphData.height = Math.max(graphData.height, (node.y ?? 0) + 1)
|
||||
}
|
||||
|
||||
return graphData
|
||||
})
|
||||
|
||||
const contentWidth = computed(() => {
|
||||
return graph.value.columns * (boxWidth + marginX) + 2 * paddingX
|
||||
})
|
||||
|
||||
const contentHeight = computed(() => {
|
||||
return graph.value.height * (boxHeight + marginY)
|
||||
})
|
||||
|
||||
const viewBox = computed(() => {
|
||||
return `0 0 ${contentWidth.value} ${contentHeight.value}`
|
||||
})
|
||||
|
||||
function createZoom () {
|
||||
if (!svg.value) return
|
||||
|
||||
const viewportHeight = svg.value.clientHeight
|
||||
const viewportWidth = svg.value.clientWidth
|
||||
defaultScale.value = 1
|
||||
|
||||
zoom.value = d3
|
||||
.zoom()
|
||||
.scaleExtent([Math.min(defaultScale.value * 0.5, 1), Math.max(5, contentHeight.value / viewportHeight, contentWidth.value / viewportWidth)])
|
||||
.extent([[0, 0], [viewportWidth, viewportHeight]])
|
||||
.filter((event: any) => {
|
||||
const wheeled = event.type === 'wheel'
|
||||
const mouseDrag
|
||||
= event.type === 'mousedown'
|
||||
|| event.type === 'mouseup'
|
||||
|| event.type === 'mousemove'
|
||||
const touch
|
||||
= event.type === 'touchstart'
|
||||
|| event.type === 'touchmove'
|
||||
|| event.type === 'touchstop'
|
||||
return (wheeled || mouseDrag || touch) && maximized.value
|
||||
})
|
||||
.wheelDelta((event: any) => {
|
||||
// In contrast to default implementation, do not use a factor 10 if ctrl is pressed
|
||||
return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002)
|
||||
})
|
||||
.on('zoom', (event: any) => {
|
||||
zoomTransform.value = event.transform
|
||||
})
|
||||
|
||||
const initTransform = d3.zoomIdentity
|
||||
.scale(defaultScale.value)
|
||||
.translate(0, 0)
|
||||
zoomTransform.value = initTransform
|
||||
|
||||
// This sets correct d3 internal state for the initial centering
|
||||
d3.select(svg.value)
|
||||
.call(zoom.value.transform, initTransform)
|
||||
|
||||
const svgSelection = d3.select(svg.value).call(zoom.value)
|
||||
svgSelection.on('touchmove.zoom', null)
|
||||
// TODO touch support
|
||||
}
|
||||
|
||||
watch(maximized, () => {
|
||||
nextTick(() => {
|
||||
createZoom()
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
createZoom()
|
||||
window.addEventListener('resize', createZoom)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', createZoom)
|
||||
})
|
||||
|
||||
</script>
|
||||
<template lang="pug">
|
||||
div(:class="'checkin-rules-visualization ' + (maximized ? 'maximized' : '')")
|
||||
.tools
|
||||
button.btn.btn-default(
|
||||
v-if="maximized",
|
||||
type="button",
|
||||
@click.prevent="maximized = false"
|
||||
)
|
||||
span.fa.fa-window-close
|
||||
button.btn.btn-default(
|
||||
v-if="!maximized",
|
||||
type="button",
|
||||
@click.prevent="maximized = true"
|
||||
)
|
||||
span.fa.fa-window-maximize
|
||||
svg(
|
||||
ref="svg",
|
||||
:width="contentWidth",
|
||||
:height="contentHeight",
|
||||
:viewBox="viewBox"
|
||||
)
|
||||
g(:transform="zoomTransform.toString()")
|
||||
VizNode(
|
||||
v-for="(node, nodeid) in graph.nodes_by_id",
|
||||
:key="nodeid",
|
||||
:node="node",
|
||||
:children="node.children.map((n: string) => graph.nodes_by_id[n])",
|
||||
:nodeid="nodeid",
|
||||
:boxWidth="boxWidth",
|
||||
:boxHeight="boxHeight",
|
||||
:marginX="marginX",
|
||||
:marginY="marginY",
|
||||
:paddingX="paddingX"
|
||||
)
|
||||
<template>
|
||||
<div :class="'checkin-rules-visualization ' + (maximized ? 'maximized' : '')">
|
||||
<div class="tools">
|
||||
<button v-if="maximized" class="btn btn-default" type="button" @click.prevent="maximized = false"><span class="fa fa-window-close"></span></button>
|
||||
<button v-if="!maximized" class="btn btn-default" type="button" @click.prevent="maximized = true"><span class="fa fa-window-maximize"></span></button>
|
||||
</div>
|
||||
<svg :width="graph.columns * (boxWidth + marginX) + 2 * paddingX" :height="graph.height * (boxHeight + marginY)"
|
||||
:viewBox="viewBox" ref="svg">
|
||||
<g :transform="zoomTransform.toString()">
|
||||
<viz-node v-for="(node, nodeid) in graph.nodes_by_id" :key="nodeid" :node="node"
|
||||
:children="node.children.map(n => graph.nodes_by_id[n])" :nodeid="nodeid"
|
||||
:boxWidth="boxWidth" :boxHeight="boxHeight" :marginX="marginX" :marginY="marginY"
|
||||
:paddingX="paddingX"></viz-node>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
VizNode: VizNode.default,
|
||||
},
|
||||
computed: {
|
||||
boxWidth() {
|
||||
return 300
|
||||
},
|
||||
boxHeight() {
|
||||
return 62
|
||||
},
|
||||
paddingX() {
|
||||
return 50
|
||||
},
|
||||
marginX() {
|
||||
return 50
|
||||
},
|
||||
marginY() {
|
||||
return 20
|
||||
},
|
||||
contentWidth() {
|
||||
return this.graph.columns * (this.boxWidth + this.marginX) + 2 * this.paddingX
|
||||
},
|
||||
contentHeight() {
|
||||
return this.graph.height * (this.boxHeight + this.marginY)
|
||||
},
|
||||
viewBox() {
|
||||
return `0 0 ${this.contentWidth} ${this.contentHeight}`
|
||||
},
|
||||
graph() {
|
||||
/**
|
||||
* Converts a JSON logic rule into a "flow chart".
|
||||
*
|
||||
* A JSON logic rule has a structure like an operator tree:
|
||||
*
|
||||
* OR
|
||||
* |-- AND
|
||||
* |-- A
|
||||
* |-- B
|
||||
* |-- AND
|
||||
* |-- OR
|
||||
* |-- C
|
||||
* |-- D
|
||||
* |-- E
|
||||
*
|
||||
* For our visualization, we want to visualize that tree as a graph one can follow along to reach a
|
||||
* decision, which has the structure of a directed graph:
|
||||
*
|
||||
* --- A --- B --- OK!
|
||||
* /
|
||||
* /
|
||||
* /
|
||||
* --
|
||||
* \
|
||||
* \ --- C ---
|
||||
* \ / \
|
||||
* --- --- E --- OK!
|
||||
* \ /
|
||||
* --- D ---
|
||||
*/
|
||||
const graph = {
|
||||
nodes_by_id: {},
|
||||
children: [],
|
||||
columns: -1,
|
||||
}
|
||||
|
||||
// Step 1: Start building the graph by finding all nodes and edges
|
||||
let counter = 0;
|
||||
const _add_to_graph = (rule) => { // returns [heads, tails]
|
||||
if (typeof rule !== 'object' || rule === null) {
|
||||
const node_id = (counter++).toString()
|
||||
graph.nodes_by_id[node_id] = {
|
||||
rule: rule,
|
||||
column: -1,
|
||||
children: [],
|
||||
}
|
||||
return [[node_id], [node_id]]
|
||||
}
|
||||
|
||||
const operator = Object.keys(rule)[0]
|
||||
const operands = rule[operator]
|
||||
|
||||
if (operator === "and") {
|
||||
let children = []
|
||||
let tails = null
|
||||
operands.reverse()
|
||||
for (let operand of operands) {
|
||||
let [new_children, new_tails] = _add_to_graph(operand)
|
||||
for (let new_child of new_tails) {
|
||||
graph.nodes_by_id[new_child].children.push(...children)
|
||||
for (let c of children) {
|
||||
graph.nodes_by_id[c].parent = graph.nodes_by_id[new_child]
|
||||
}
|
||||
}
|
||||
if (tails === null) {
|
||||
tails = new_tails
|
||||
}
|
||||
children = new_children
|
||||
}
|
||||
return [children, tails]
|
||||
} else if (operator === "or") {
|
||||
const children = []
|
||||
const tails = []
|
||||
for (let operand of operands) {
|
||||
let [new_children, new_tails] = _add_to_graph(operand)
|
||||
children.push(...new_children)
|
||||
tails.push(...new_tails)
|
||||
}
|
||||
return [children, tails]
|
||||
} else {
|
||||
const node_id = (counter++).toString()
|
||||
graph.nodes_by_id[node_id] = {
|
||||
rule: rule,
|
||||
column: -1,
|
||||
children: [],
|
||||
}
|
||||
return [[node_id], [node_id]]
|
||||
}
|
||||
|
||||
}
|
||||
graph.children = _add_to_graph(JSON.parse(JSON.stringify(this.$root.rules)))[0]
|
||||
|
||||
// Step 2: We compute the "column" of every node, which is the maximum number of hops required to reach the
|
||||
// node from the root node
|
||||
const _set_column_to_min = (nodes, mincol) => {
|
||||
for (let node of nodes) {
|
||||
if (mincol > node.column) {
|
||||
node.column = mincol
|
||||
graph.columns = Math.max(mincol + 1, graph.columns)
|
||||
_set_column_to_min(node.children.map(nid => graph.nodes_by_id[nid]), mincol + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
_set_column_to_min(graph.children.map(nid => graph.nodes_by_id[nid]), 0)
|
||||
|
||||
// Step 3: Align each node on a grid. The x position is already given by the column computed above, but we still
|
||||
// need the y position. This part of the algorithm is opinionated and probably not yet the nicest solution we
|
||||
// can use!
|
||||
const _set_y = (node, offset) => {
|
||||
if (typeof node.y === "undefined") {
|
||||
// We only take the first value we found for each node
|
||||
node.y = offset
|
||||
}
|
||||
|
||||
let used = 0
|
||||
for (let cid of node.children) {
|
||||
used += Math.max(0, _set_y(graph.nodes_by_id[cid], offset + used) - 1)
|
||||
used++
|
||||
}
|
||||
return used
|
||||
}
|
||||
_set_y(graph, 0)
|
||||
|
||||
// Step 4: Compute the "height" of the graph by looking at the node with the highest y value
|
||||
graph.height = 1
|
||||
for (let node of [...Object.values(graph.nodes_by_id)]) {
|
||||
graph.height = Math.max(graph.height, node.y + 1)
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.createZoom()
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.createZoom)
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('resize', this.createZoom)
|
||||
},
|
||||
watch: {
|
||||
maximized() {
|
||||
this.$nextTick(() => {
|
||||
this.createZoom()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createZoom() {
|
||||
if (!this.$refs.svg) return
|
||||
|
||||
const viewportHeight = this.$refs.svg.clientHeight
|
||||
const viewportWidth = this.$refs.svg.clientWidth
|
||||
this.defaultScale = 1
|
||||
|
||||
this.zoom = d3
|
||||
.zoom()
|
||||
.scaleExtent([Math.min(this.defaultScale * 0.5, 1), Math.max(5, this.contentHeight / viewportHeight, this.contentWidth / viewportWidth)])
|
||||
.extent([[0, 0], [viewportWidth, viewportHeight]])
|
||||
.filter(event => {
|
||||
const wheeled = event.type === 'wheel'
|
||||
const mouseDrag =
|
||||
event.type === 'mousedown' ||
|
||||
event.type === 'mouseup' ||
|
||||
event.type === 'mousemove'
|
||||
const touch =
|
||||
event.type === 'touchstart' ||
|
||||
event.type === 'touchmove' ||
|
||||
event.type === 'touchstop'
|
||||
return (wheeled || mouseDrag || touch) && this.maximized
|
||||
})
|
||||
.wheelDelta(event => {
|
||||
// In contrast to default implementation, do not use a factor 10 if ctrl is pressed
|
||||
return -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002)
|
||||
})
|
||||
.on('zoom', (event) => {
|
||||
this.zoomTransform = event.transform
|
||||
})
|
||||
|
||||
const initTransform = d3.zoomIdentity
|
||||
.scale(this.defaultScale)
|
||||
.translate(
|
||||
0,
|
||||
0
|
||||
)
|
||||
this.zoomTransform = initTransform
|
||||
|
||||
// This sets correct d3 internal state for the initial centering
|
||||
d3.select(this.$refs.svg)
|
||||
.call(this.zoom.transform, initTransform)
|
||||
|
||||
const svg = d3.select(this.$refs.svg).call(this.zoom)
|
||||
svg.on('touchmove.zoom', null)
|
||||
// TODO touch support
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maximized: false,
|
||||
zoom: null,
|
||||
defaultScale: 1,
|
||||
zoomTransform: d3.zoomTransform({k: 1, x: 0, y: 0}),
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
/* global gettext, pgettext */
|
||||
|
||||
export const TEXTS = {
|
||||
and: gettext('All of the conditions below (AND)'),
|
||||
or: gettext('At least one of the conditions below (OR)'),
|
||||
date_from: gettext('Event start'),
|
||||
date_to: gettext('Event end'),
|
||||
date_admission: gettext('Event admission'),
|
||||
date_custom: gettext('custom date and time'),
|
||||
date_customtime: gettext('custom time'),
|
||||
date_tolerance: gettext('Tolerance (minutes)'),
|
||||
condition_add: gettext('Add condition'),
|
||||
minutes: gettext('minutes'),
|
||||
duplicate: gettext('Duplicate'),
|
||||
status_present: pgettext('entry_status', 'present'),
|
||||
status_absent: pgettext('entry_status', 'absent'),
|
||||
}
|
||||
|
||||
export const TYPEOPS = {
|
||||
// Every change to our supported JSON logic must be done
|
||||
// * in pretix.base.services.checkin
|
||||
// * in pretix.base.models.checkin
|
||||
// * in pretix.helpers.jsonlogic_boolalg
|
||||
// * in checkinrules.js
|
||||
// * in libpretixsync
|
||||
// * in pretixscan-ios
|
||||
product: {
|
||||
inList: {
|
||||
label: gettext('is one of'),
|
||||
cardinality: 2,
|
||||
}
|
||||
},
|
||||
variation: {
|
||||
inList: {
|
||||
label: gettext('is one of'),
|
||||
cardinality: 2,
|
||||
}
|
||||
},
|
||||
gate: {
|
||||
inList: {
|
||||
label: gettext('is one of'),
|
||||
cardinality: 2,
|
||||
}
|
||||
},
|
||||
datetime: {
|
||||
isBefore: {
|
||||
label: gettext('is before'),
|
||||
cardinality: 2,
|
||||
},
|
||||
isAfter: {
|
||||
label: gettext('is after'),
|
||||
cardinality: 2,
|
||||
},
|
||||
},
|
||||
enum_entry_status: {
|
||||
'==': {
|
||||
label: gettext('='),
|
||||
cardinality: 2,
|
||||
},
|
||||
},
|
||||
int_by_datetime: {
|
||||
'<': {
|
||||
label: '<',
|
||||
cardinality: 2,
|
||||
},
|
||||
'<=': {
|
||||
label: '≤',
|
||||
cardinality: 2,
|
||||
},
|
||||
'>': {
|
||||
label: '>',
|
||||
cardinality: 2,
|
||||
},
|
||||
'>=': {
|
||||
label: '≥',
|
||||
cardinality: 2,
|
||||
},
|
||||
'==': {
|
||||
label: '=',
|
||||
cardinality: 2,
|
||||
},
|
||||
'!=': {
|
||||
label: '≠',
|
||||
cardinality: 2,
|
||||
},
|
||||
},
|
||||
int: {
|
||||
'<': {
|
||||
label: '<',
|
||||
cardinality: 2,
|
||||
},
|
||||
'<=': {
|
||||
label: '≤',
|
||||
cardinality: 2,
|
||||
},
|
||||
'>': {
|
||||
label: '>',
|
||||
cardinality: 2,
|
||||
},
|
||||
'>=': {
|
||||
label: '≥',
|
||||
cardinality: 2,
|
||||
},
|
||||
'==': {
|
||||
label: '=',
|
||||
cardinality: 2,
|
||||
},
|
||||
'!=': {
|
||||
label: '≠',
|
||||
cardinality: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const VARS = {
|
||||
product: {
|
||||
label: gettext('Product'),
|
||||
type: 'product',
|
||||
},
|
||||
variation: {
|
||||
label: gettext('Product variation'),
|
||||
type: 'variation',
|
||||
},
|
||||
gate: {
|
||||
label: gettext('Gate'),
|
||||
type: 'gate',
|
||||
},
|
||||
now: {
|
||||
label: gettext('Current date and time'),
|
||||
type: 'datetime',
|
||||
},
|
||||
now_isoweekday: {
|
||||
label: gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
|
||||
type: 'int',
|
||||
},
|
||||
entry_status: {
|
||||
label: gettext('Current entry status'),
|
||||
type: 'enum_entry_status',
|
||||
},
|
||||
entries_number: {
|
||||
label: gettext('Number of previous entries'),
|
||||
type: 'int',
|
||||
},
|
||||
entries_today: {
|
||||
label: gettext('Number of previous entries since midnight'),
|
||||
type: 'int',
|
||||
},
|
||||
entries_since: {
|
||||
label: gettext('Number of previous entries since'),
|
||||
type: 'int_by_datetime',
|
||||
},
|
||||
entries_before: {
|
||||
label: gettext('Number of previous entries before'),
|
||||
type: 'int_by_datetime',
|
||||
},
|
||||
entries_days: {
|
||||
label: gettext('Number of days with a previous entry'),
|
||||
type: 'int',
|
||||
},
|
||||
entries_days_since: {
|
||||
label: gettext('Number of days with a previous entry since'),
|
||||
type: 'int_by_datetime',
|
||||
},
|
||||
entries_days_before: {
|
||||
label: gettext('Number of days with a previous entry before'),
|
||||
type: 'int_by_datetime',
|
||||
},
|
||||
minutes_since_last_entry: {
|
||||
label: gettext('Minutes since last entry (-1 on first entry)'),
|
||||
type: 'int',
|
||||
},
|
||||
minutes_since_first_entry: {
|
||||
label: gettext('Minutes since first entry (-1 on first entry)'),
|
||||
type: 'int',
|
||||
},
|
||||
}
|
||||
|
||||
export const DATETIME_OPTIONS = {
|
||||
format: document.body.dataset.datetimeformat,
|
||||
locale: document.body.dataset.datetimelocale,
|
||||
useCurrent: false,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { DATETIME_OPTIONS } from './constants'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
value?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
input: [value: string]
|
||||
}>()
|
||||
|
||||
const input = ref<HTMLInputElement | null>(null)
|
||||
|
||||
watch(() => props.value, (val) => {
|
||||
$(input.value).data('DateTimePicker').date(moment(val))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$(input.value)
|
||||
.datetimepicker({
|
||||
...DATETIME_OPTIONS,
|
||||
showClear: props.required,
|
||||
})
|
||||
.trigger('change')
|
||||
.on('dp.change', function (this: HTMLElement) {
|
||||
emit('input', $(this).data('DateTimePicker').date().toISOString())
|
||||
})
|
||||
if (!props.value) {
|
||||
$(input.value).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||
} else {
|
||||
$(input.value).data('DateTimePicker').date(moment(props.value))
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(input.value)
|
||||
.off()
|
||||
.datetimepicker('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
input.form-control(ref="input")
|
||||
<template>
|
||||
<input class="form-control">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
template: (''),
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().toISOString());
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(moment(vm.value));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-datetimeformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(moment(val));
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
export const allProducts = ref(false)
|
||||
export const limitProducts = ref<number[]>([])
|
||||
|
||||
function updateProducts () {
|
||||
allProducts.value = document.querySelector<HTMLInputElement>('#id_all_products')?.checked ?? false
|
||||
limitProducts.value = Array.from(document.querySelectorAll<HTMLInputElement>('input[name=limit_products]:checked')).map(el => parseInt(el.value))
|
||||
}
|
||||
|
||||
// listen to change events for products
|
||||
document.querySelectorAll('#id_all_products, input[name=limit_products]').forEach(el => el.addEventListener('change', updateProducts))
|
||||
updateProducts()
|
||||
|
||||
export const rules = ref<any>({})
|
||||
|
||||
// grab rules from hidden input
|
||||
const rulesInput = document.querySelector<HTMLInputElement>('#id_rules')
|
||||
if (rulesInput?.value) {
|
||||
rules.value = JSON.parse(rulesInput.value)
|
||||
}
|
||||
|
||||
// sync back to hidden input
|
||||
watch(rules, (newVal) => {
|
||||
if (!rulesInput) return
|
||||
rulesInput.value = JSON.stringify(newVal)
|
||||
}, { deep: true })
|
||||
|
||||
export const items = ref<any[]>([])
|
||||
|
||||
const itemsEl = document.querySelector('#items')
|
||||
if (itemsEl?.textContent) {
|
||||
items.value = JSON.parse(itemsEl.textContent || '[]')
|
||||
|
||||
function checkForInvalidIds (validProducts: Record<string, string>, validVariations: Record<string, string>, rule: any) {
|
||||
if (rule['and']) {
|
||||
for (const child of rule['and'])
|
||||
checkForInvalidIds(validProducts, validVariations, child)
|
||||
} else if (rule['or']) {
|
||||
for (const child of rule['or'])
|
||||
checkForInvalidIds(validProducts, validVariations, child)
|
||||
} else if (rule['inList'] && rule['inList'][0]['var'] === 'product') {
|
||||
for (const item of rule['inList'][1]['objectList']) {
|
||||
if (!validProducts[item['lookup'][1]])
|
||||
item['lookup'][2] = '[' + gettext('Error: Product not found!') + ']'
|
||||
else
|
||||
item['lookup'][2] = validProducts[item['lookup'][1]]
|
||||
}
|
||||
} else if (rule['inList'] && rule['inList'][0]['var'] === 'variation') {
|
||||
for (const item of rule['inList'][1]['objectList']) {
|
||||
if (!validVariations[item['lookup'][1]])
|
||||
item['lookup'][2] = '[' + gettext('Error: Variation not found!') + ']'
|
||||
else
|
||||
item['lookup'][2] = validVariations[item['lookup'][1]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkForInvalidIds(
|
||||
Object.fromEntries(items.value.map(p => [p.id, p.name])),
|
||||
Object.fromEntries(items.value.flatMap(p => p.variations?.map(v => [v.id, p.name + ' – ' + v.name]) ?? [])),
|
||||
rules.value
|
||||
)
|
||||
}
|
||||
|
||||
export const productSelectURL = ref(document.querySelector('#product-select2')?.textContent)
|
||||
export const variationSelectURL = ref(document.querySelector('#variations-select2')?.textContent)
|
||||
export const gateSelectURL = ref(document.querySelector('#gate-select2')?.textContent)
|
||||
@@ -1,12 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
app.mount('#rules-editor')
|
||||
|
||||
app.config.errorHandler = (error, _vm, info) => {
|
||||
// vue fatals on errors by default, which is a weird choice
|
||||
// https://github.com/vuejs/core/issues/3525
|
||||
// https://github.com/vuejs/router/discussions/2435
|
||||
console.error('[VUE]', info, error)
|
||||
}
|
||||
@@ -1,93 +1,93 @@
|
||||
export function convertToDNF (rules) {
|
||||
// Converts a set of rules to disjunctive normal form, i.e. returns something of the form
|
||||
// `(a AND b AND c) OR (a AND d AND f)`
|
||||
// without further nesting.
|
||||
if (typeof rules !== 'object' || Array.isArray(rules) || rules === null) {
|
||||
return rules
|
||||
}
|
||||
function convert_to_dnf(rules) {
|
||||
// Converts a set of rules to disjunctive normal form, i.e. returns something of the form
|
||||
// `(a AND b AND c) OR (a AND d AND f)`
|
||||
// without further nesting.
|
||||
if (typeof rules !== "object" || Array.isArray(rules) || rules === null) {
|
||||
return rules
|
||||
}
|
||||
|
||||
function _distribute_or_over_and (r) {
|
||||
let operator = Object.keys(r)[0]
|
||||
let values = r[operator]
|
||||
if (operator === 'and') {
|
||||
let arg_to_distribute = null
|
||||
let other_args = []
|
||||
for (let arg of values) {
|
||||
if (typeof arg === 'object' && !Array.isArray(arg) && typeof arg['or'] !== 'undefined' && arg_to_distribute === null) {
|
||||
arg_to_distribute = arg
|
||||
} else {
|
||||
other_args.push(arg)
|
||||
}
|
||||
}
|
||||
if (arg_to_distribute === null) {
|
||||
return r
|
||||
}
|
||||
let or_operands = []
|
||||
for (let dval of arg_to_distribute['or']) {
|
||||
or_operands.push({ and: other_args.concat([dval]) })
|
||||
}
|
||||
return {
|
||||
or: or_operands
|
||||
}
|
||||
} else if (!operator) {
|
||||
return r
|
||||
} else if (operator === '!' || operator === '!!' || operator === '?:' || operator === 'if') {
|
||||
console.warn('Operator ' + operator + ' currently unsupported by convert_to_dnf')
|
||||
return r
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
}
|
||||
function _distribute_or_over_and(r) {
|
||||
var operator = Object.keys(r)[0]
|
||||
var values = r[operator]
|
||||
if (operator === "and") {
|
||||
var arg_to_distribute = null
|
||||
var other_args = []
|
||||
for (var arg of values) {
|
||||
if (typeof arg === "object" && !Array.isArray(arg) && typeof arg["or"] !== "undefined" && arg_to_distribute === null) {
|
||||
arg_to_distribute = arg
|
||||
} else {
|
||||
other_args.push(arg)
|
||||
}
|
||||
}
|
||||
if (arg_to_distribute === null) {
|
||||
return r
|
||||
}
|
||||
var or_operands = []
|
||||
for (var dval of arg_to_distribute["or"]) {
|
||||
or_operands.push({"and": other_args.concat([dval])})
|
||||
}
|
||||
return {
|
||||
"or": or_operands
|
||||
}
|
||||
} else if (!operator) {
|
||||
return r
|
||||
} else if (operator === "!" || operator === "!!" || operator === "?:" || operator === "if") {
|
||||
console.warn("Operator " + operator + " currently unsupported by convert_to_dnf")
|
||||
return r
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
function _simplify_chained_operators (r) {
|
||||
// Simplify `(a OR b) OR (c or d)` to `a OR b OR c OR d` and the same with `AND`
|
||||
if (typeof r !== 'object' || Array.isArray(r)) {
|
||||
return r
|
||||
}
|
||||
let operator = Object.keys(r)[0]
|
||||
let values = r[operator]
|
||||
if (operator !== 'or' && operator !== 'and') {
|
||||
return r
|
||||
}
|
||||
let new_values = []
|
||||
for (let v of values) {
|
||||
if (typeof v !== 'object' || Array.isArray(v) || typeof v[operator] === 'undefined') {
|
||||
new_values.push(v)
|
||||
} else {
|
||||
new_values.push(...v[operator])
|
||||
}
|
||||
}
|
||||
let result = {}
|
||||
result[operator] = new_values
|
||||
return result
|
||||
}
|
||||
function _simplify_chained_operators(r) {
|
||||
// Simplify `(a OR b) OR (c or d)` to `a OR b OR c OR d` and the same with `AND`
|
||||
if (typeof r !== "object" || Array.isArray(r)) {
|
||||
return r
|
||||
}
|
||||
var operator = Object.keys(r)[0]
|
||||
var values = r[operator]
|
||||
if (operator !== "or" && operator !== "and") {
|
||||
return r
|
||||
}
|
||||
var new_values = []
|
||||
for (var v of values) {
|
||||
if (typeof v !== "object" || Array.isArray(v) || typeof v[operator] === "undefined") {
|
||||
new_values.push(v)
|
||||
} else {
|
||||
new_values.push(...v[operator])
|
||||
}
|
||||
}
|
||||
var result = {}
|
||||
result[operator] = new_values
|
||||
return result
|
||||
}
|
||||
|
||||
// Run _distribute_or_over_and on until it no longer changes anything. Do so recursively
|
||||
// for the full expression tree.
|
||||
let old_rules = rules
|
||||
while (true) {
|
||||
rules = _distribute_or_over_and(rules)
|
||||
let operator = Object.keys(rules)[0]
|
||||
let values = rules[operator]
|
||||
let no_list = false
|
||||
if (!Array.isArray(values)) {
|
||||
values = [values]
|
||||
no_list = true
|
||||
}
|
||||
rules = {}
|
||||
if (!no_list) {
|
||||
rules[operator] = []
|
||||
for (let v of values) {
|
||||
rules[operator].push(convertToDNF(v))
|
||||
}
|
||||
} else {
|
||||
rules[operator] = convertToDNF(values[0])
|
||||
}
|
||||
if (JSON.stringify(old_rules) === JSON.stringify(rules)) { // Let's hope this is good enough...
|
||||
break
|
||||
}
|
||||
old_rules = rules
|
||||
}
|
||||
rules = _simplify_chained_operators(rules)
|
||||
return rules
|
||||
}
|
||||
// Run _distribute_or_over_and on until it no longer changes anything. Do so recursively
|
||||
// for the full expression tree.
|
||||
var old_rules = rules
|
||||
while (true) {
|
||||
rules = _distribute_or_over_and(rules)
|
||||
var operator = Object.keys(rules)[0]
|
||||
var values = rules[operator]
|
||||
var no_list = false
|
||||
if (!Array.isArray(values)) {
|
||||
values = [values]
|
||||
no_list = true
|
||||
}
|
||||
rules = {}
|
||||
if (!no_list) {
|
||||
rules[operator] = []
|
||||
for (var v of values) {
|
||||
rules[operator].push(convert_to_dnf(v))
|
||||
}
|
||||
} else {
|
||||
rules[operator] = convert_to_dnf(values[0])
|
||||
}
|
||||
if (JSON.stringify(old_rules) === JSON.stringify(rules)) { // Let's hope this is good enough...
|
||||
break
|
||||
}
|
||||
old_rules = rules
|
||||
}
|
||||
rules = _simplify_chained_operators(rules)
|
||||
return rules
|
||||
}
|
||||
@@ -1,116 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
declare const $: any
|
||||
|
||||
export interface ObjectListItem {
|
||||
lookup: [string, number | string, string]
|
||||
}
|
||||
|
||||
export interface ObjectList {
|
||||
objectList: ObjectListItem[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
value?: ObjectList
|
||||
placeholder?: string
|
||||
url?: string
|
||||
multiple?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
input: [value: any[]]
|
||||
}>()
|
||||
|
||||
const select = ref<HTMLSelectElement | null>(null)
|
||||
|
||||
function opts () {
|
||||
return {
|
||||
theme: 'bootstrap',
|
||||
delay: 100,
|
||||
width: '100%',
|
||||
multiple: true,
|
||||
allowClear: props.required,
|
||||
language: $('body').attr('data-select2-locale'),
|
||||
ajax: {
|
||||
url: props.url,
|
||||
data: function (params: { term: string; page?: number }) {
|
||||
return {
|
||||
query: params.term,
|
||||
page: params.page || 1
|
||||
}
|
||||
}
|
||||
},
|
||||
templateResult: function (res: { id?: string; text: string }) {
|
||||
if (!res.id) {
|
||||
return res.text
|
||||
}
|
||||
const $ret = $('<span>').append(
|
||||
$('<span>').addClass('primary').append($('<div>').text(res.text).html())
|
||||
)
|
||||
return $ret
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function build () {
|
||||
$(select.value)
|
||||
.empty()
|
||||
.select2(opts())
|
||||
.val(props.value || '')
|
||||
.trigger('change')
|
||||
.on('change', function (this: HTMLElement) {
|
||||
emit('input', $(this).select2('data'))
|
||||
})
|
||||
if (props.value) {
|
||||
for (let i = 0; i < props.value.objectList.length; i++) {
|
||||
const option = new Option(props.value.objectList[i].lookup[2], String(props.value.objectList[i].lookup[1]), true, true)
|
||||
$(select.value).append(option)
|
||||
}
|
||||
}
|
||||
$(select.value).trigger('change')
|
||||
}
|
||||
|
||||
watch(() => props.placeholder, () => {
|
||||
$(select.value).select2('destroy')
|
||||
build()
|
||||
})
|
||||
|
||||
watch(() => props.required, () => {
|
||||
$(select.value).select2('destroy')
|
||||
build()
|
||||
})
|
||||
|
||||
watch(() => props.url, () => {
|
||||
$(select.value).select2('destroy')
|
||||
build()
|
||||
})
|
||||
|
||||
watch(() => props.value, (newval, oldval) => {
|
||||
if (JSON.stringify(newval) !== JSON.stringify(oldval)) {
|
||||
$(select.value).empty()
|
||||
if (newval) {
|
||||
for (let i = 0; i < newval.objectList.length; i++) {
|
||||
const option = new Option(newval.objectList[i].lookup[2], String(newval.objectList[i].lookup[1]), true, true)
|
||||
$(select.value).append(option)
|
||||
}
|
||||
}
|
||||
$(select.value).trigger('change')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
build()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(select.value)
|
||||
.off()
|
||||
.select2('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
select(ref="select")
|
||||
slot
|
||||
<template>
|
||||
<select>
|
||||
<slot></slot>
|
||||
</select>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value", "placeholder", "url", "multiple"],
|
||||
template: ('<select>\n' +
|
||||
' <slot></slot>\n' +
|
||||
' </select>'),
|
||||
mounted: function () {
|
||||
this.build();
|
||||
},
|
||||
methods: {
|
||||
build: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.empty()
|
||||
.select2(this.opts())
|
||||
.val(this.value || "")
|
||||
.trigger("change")
|
||||
// emit event on change.
|
||||
.on("change", function (e) {
|
||||
vm.$emit("input", $(this).select2('data'));
|
||||
});
|
||||
if (vm.value) {
|
||||
for (var i = 0; i < vm.value["objectList"].length; i++) {
|
||||
var option = new Option(vm.value["objectList"][i]["lookup"][2], vm.value["objectList"][i]["lookup"][1], true, true);
|
||||
$(vm.$el).append(option);
|
||||
}
|
||||
}
|
||||
$(vm.$el).trigger("change");
|
||||
},
|
||||
opts: function () {
|
||||
return {
|
||||
theme: "bootstrap",
|
||||
delay: 100,
|
||||
width: '100%',
|
||||
multiple: true,
|
||||
allowClear: this.required,
|
||||
language: $("body").attr("data-select2-locale"),
|
||||
ajax: {
|
||||
url: this.url,
|
||||
data: function (params) {
|
||||
return {
|
||||
query: params.term,
|
||||
page: params.page || 1
|
||||
}
|
||||
}
|
||||
},
|
||||
templateResult: function (res) {
|
||||
if (!res.id) {
|
||||
return res.text;
|
||||
}
|
||||
var $ret = $("<span>").append(
|
||||
$("<span>").addClass("primary").append($("<div>").text(res.text).html())
|
||||
);
|
||||
return $ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
placeholder: function (val) {
|
||||
$(this.$el).select2("destroy");
|
||||
this.build();
|
||||
},
|
||||
required: function (val) {
|
||||
$(this.$el).select2("destroy");
|
||||
this.build();
|
||||
},
|
||||
url: function (val) {
|
||||
$(this.$el).select2("destroy");
|
||||
this.build();
|
||||
},
|
||||
value: function (newval, oldval) {
|
||||
if (JSON.stringify(newval) !== JSON.stringify(oldval)) {
|
||||
$(this.$el).empty();
|
||||
if (newval) {
|
||||
for (var i = 0; i < newval["objectList"].length; i++) {
|
||||
var option = new Option(newval["objectList"][i]["lookup"][2], newval["objectList"][i]["lookup"][1], true, true);
|
||||
$(this.$el).append(option);
|
||||
}
|
||||
}
|
||||
$(this.$el).trigger("change");
|
||||
}
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.select2("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,45 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { DATETIME_OPTIONS } from './constants'
|
||||
|
||||
const props = defineProps<{
|
||||
required?: boolean
|
||||
value?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
input: [value: string]
|
||||
}>()
|
||||
|
||||
const input = ref<HTMLInputElement | null>(null)
|
||||
|
||||
watch(() => props.value, (val) => {
|
||||
$(input.value).data('DateTimePicker').date(val)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$(input.value)
|
||||
.datetimepicker({
|
||||
...DATETIME_OPTIONS,
|
||||
showClear: props.required,
|
||||
})
|
||||
.trigger('change')
|
||||
.on('dp.change', function (this: HTMLElement) {
|
||||
emit('input', $(this).data('DateTimePicker').date().format('HH:mm:ss'))
|
||||
})
|
||||
if (!props.value) {
|
||||
$(input.value).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
|
||||
} else {
|
||||
$(input.value).data('DateTimePicker').date(props.value)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
$(input.value)
|
||||
.off()
|
||||
.datetimepicker('destroy')
|
||||
})
|
||||
</script>
|
||||
<template lang="pug">
|
||||
input.form-control(ref="input")
|
||||
<template>
|
||||
<input class="form-control">
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ["required", "value"],
|
||||
template: (''),
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
var multiple = this.multiple;
|
||||
$(this.$el)
|
||||
.datetimepicker(this.opts())
|
||||
.trigger("change")
|
||||
.on("dp.change", function (e) {
|
||||
vm.$emit("input", $(this).data('DateTimePicker').date().format("HH:mm:ss"));
|
||||
});
|
||||
if (!vm.value) {
|
||||
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
|
||||
} else {
|
||||
$(this.$el).data("DateTimePicker").date(vm.value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
opts: function () {
|
||||
return {
|
||||
format: $("body").attr("data-timeformat"),
|
||||
locale: $("body").attr("data-datetimelocale"),
|
||||
useCurrent: false,
|
||||
showClear: this.required,
|
||||
icons: {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'fa fa-screenshot',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-remove'
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: function (val) {
|
||||
$(this.$el).data('DateTimePicker').date(val);
|
||||
},
|
||||
},
|
||||
destroyed: function () {
|
||||
$(this.$el)
|
||||
.off()
|
||||
.datetimepicker("destroy");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,242 +1,255 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { TEXTS, VARS, TYPEOPS } from './constants'
|
||||
<template>
|
||||
<g>
|
||||
<path v-for="e in edges" :d="e" class="edge"></path>
|
||||
<path v-if="rootEdge" :d="rootEdge" class="edge"></path>
|
||||
<path v-if="!node.children.length" :d="checkEdge" class="edge"></path>
|
||||
<rect :width="boxWidth" :height="boxHeight" :x="x" :y="y" :class="nodeClass" rx="5">
|
||||
</rect>
|
||||
|
||||
declare const $: any
|
||||
declare const moment: any
|
||||
<foreignObject :width="boxWidth - 10" :height="boxHeight - 10" :x="x + 5" :y="y + 5">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" class="text">
|
||||
<span v-if="vardata && vardata.type === 'int'">
|
||||
<span v-if="variable.startsWith('entries_')" class="fa fa-sign-in"></span>
|
||||
{{ vardata.label }}
|
||||
<br>
|
||||
<span v-if="varresult !== null">
|
||||
{{varresult}}
|
||||
</span>
|
||||
<strong>
|
||||
{{ op.label }} {{ rightoperand }}
|
||||
</strong>
|
||||
</span>
|
||||
<span v-else-if="vardata && vardata.type === 'int_by_datetime'">
|
||||
<span v-if="variable.startsWith('entries_')" class="fa fa-sign-in"></span>
|
||||
{{ vardata.label }}
|
||||
<span v-if="node.rule[operator][0][variable][0].buildTime[0] === 'custom'">
|
||||
{{ df(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else-if="node.rule[operator][0][variable][0].buildTime[0] === 'customtime'">
|
||||
{{ tf(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ this.$root.texts[node.rule[operator][0][variable][0].buildTime[0]] }}
|
||||
</span>
|
||||
<br>
|
||||
<span v-if="varresult !== null">
|
||||
{{varresult}}
|
||||
</span>
|
||||
<strong>
|
||||
{{ op.label }} {{ rightoperand }}
|
||||
</strong>
|
||||
</span>
|
||||
<span v-else-if="vardata && variable === 'now'">
|
||||
<span class="fa fa-clock-o"></span> {{ vardata.label }}<br>
|
||||
<span v-if="varresult !== null">
|
||||
{{varresult}}
|
||||
</span>
|
||||
<strong>
|
||||
{{ op.label }}<br>
|
||||
<span v-if="rightoperand.buildTime[0] === 'custom'">
|
||||
{{ df(rightoperand.buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else-if="rightoperand.buildTime[0] === 'customtime'">
|
||||
{{ tf(rightoperand.buildTime[1]) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ this.$root.texts[rightoperand.buildTime[0]] }}
|
||||
</span>
|
||||
<span v-if="operands[2]">
|
||||
<span v-if="operator === 'isBefore'">+</span>
|
||||
<span v-else>-</span>
|
||||
{{ operands[2] }}
|
||||
{{ this.$root.texts.minutes }}
|
||||
</span>
|
||||
</strong>
|
||||
</span>
|
||||
<span v-else-if="vardata && operator === 'inList'">
|
||||
<span class="fa fa-sign-in" v-if="variable === 'gate'"></span>
|
||||
<span class="fa fa-ticket" v-else></span>
|
||||
{{ vardata.label }}
|
||||
<span v-if="varresult !== null">
|
||||
({{varresult}})
|
||||
</span>
|
||||
<br>
|
||||
<strong>
|
||||
{{ rightoperand.objectList.map((o) => o.lookup[2]).join(", ") }}
|
||||
</strong>
|
||||
</span>
|
||||
<span v-else-if="vardata && vardata.type === 'enum_entry_status'">
|
||||
<span class="fa fa-check-circle-o"></span>
|
||||
{{ vardata.label }}
|
||||
<span v-if="varresult !== null">
|
||||
({{varresult}})
|
||||
</span>
|
||||
<br>
|
||||
<strong>
|
||||
{{ op.label }} {{ rightoperand }}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
</foreignObject>
|
||||
|
||||
interface GraphNode {
|
||||
rule: any
|
||||
column: number
|
||||
children: string[]
|
||||
y: number
|
||||
parent?: GraphNode
|
||||
}
|
||||
<g v-if="result === false" :transform="`translate(${x + boxWidth - 15}, ${y - 10})`">
|
||||
<ellipse fill="#fff" cx="14.685823" cy="14.318233" rx="12.140151" ry="11.55523" />
|
||||
<path d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z"
|
||||
class="error" />
|
||||
</g>
|
||||
<g v-if="result === true" :transform="`translate(${x + boxWidth - 15}, ${y - 10})`">
|
||||
<ellipse fill="#fff" cx="14.685823" cy="14.318233" rx="12.140151" ry="11.55523" />
|
||||
<path d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z"
|
||||
class="check"/>
|
||||
</g>
|
||||
<g v-if="!node.children.length && (resultInclParents === null || resultInclParents === true)" :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`">
|
||||
<path d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z"
|
||||
class="check"/>
|
||||
</g>
|
||||
<g v-if="!node.children.length && (resultInclParents === false)" :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`">
|
||||
<path d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z"
|
||||
class="error" />
|
||||
</g>
|
||||
</g>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
const props = defineProps<{
|
||||
node: GraphNode
|
||||
nodeid: string
|
||||
children: GraphNode[]
|
||||
boxWidth: number
|
||||
boxHeight: number
|
||||
marginX: number
|
||||
marginY: number
|
||||
paddingX: number
|
||||
}>()
|
||||
props: {
|
||||
node: Object,
|
||||
nodeid: String,
|
||||
children: Array,
|
||||
boxWidth: Number,
|
||||
boxHeight: Number,
|
||||
marginX: Number,
|
||||
marginY: Number,
|
||||
paddingX: Number,
|
||||
},
|
||||
computed: {
|
||||
x() {
|
||||
return this.node.column * (this.boxWidth + this.marginX) + this.marginX / 2 + this.paddingX
|
||||
},
|
||||
y() {
|
||||
return this.node.y * (this.boxHeight + this.marginY) + this.marginY / 2
|
||||
},
|
||||
edges() {
|
||||
const startX = this.x + this.boxWidth + 1
|
||||
const startY = this.y + this.boxHeight / 2
|
||||
return this.children.map((c) => {
|
||||
const endX = (c.column * (this.boxWidth + this.marginX) + this.marginX / 2 + this.paddingX) - 1
|
||||
const endY = (c.y * (this.boxHeight + this.marginY) + this.marginY / 2) + this.boxHeight / 2
|
||||
|
||||
const x = computed(() => {
|
||||
return props.node.column * (props.boxWidth + props.marginX) + props.marginX / 2 + props.paddingX
|
||||
})
|
||||
|
||||
const y = computed(() => {
|
||||
return props.node.y * (props.boxHeight + props.marginY) + props.marginY / 2
|
||||
})
|
||||
|
||||
const edges = computed(() => {
|
||||
const startX = x.value + props.boxWidth + 1
|
||||
const startY = y.value + props.boxHeight / 2
|
||||
return props.children.map((c) => {
|
||||
const endX = (c.column * (props.boxWidth + props.marginX) + props.marginX / 2 + props.paddingX) - 1
|
||||
const endY = (c.y * (props.boxHeight + props.marginY) + props.marginY / 2) + props.boxHeight / 2
|
||||
|
||||
return `
|
||||
return `
|
||||
M ${startX} ${startY}
|
||||
L ${endX - 50} ${startY}
|
||||
C ${endX - 25} ${startY} ${endX - 25} ${startY} ${endX - 25} ${startY + 25 * Math.sign(endY - startY)}
|
||||
L ${endX - 25} ${endY - 25 * Math.sign(endY - startY)}
|
||||
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
||||
`
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
checkEdge() {
|
||||
const startX = this.x + this.boxWidth + 1
|
||||
const startY = this.y + this.boxHeight / 2
|
||||
|
||||
const checkEdge = computed(() => {
|
||||
const startX = x.value + props.boxWidth + 1
|
||||
const startY = y.value + props.boxHeight / 2
|
||||
return `M ${startX} ${startY} L ${startX + 25} ${startY}`
|
||||
},
|
||||
rootEdge() {
|
||||
if (this.node.column > 0) {
|
||||
return
|
||||
}
|
||||
const startX = 0
|
||||
const startY = this.boxHeight / 2 + this.marginY / 2
|
||||
const endX = this.x - 1
|
||||
const endY = this.y + this.boxHeight / 2
|
||||
|
||||
return `M ${startX} ${startY} L ${startX + 25} ${startY}`
|
||||
})
|
||||
|
||||
const rootEdge = computed(() => {
|
||||
if (props.node.column > 0) {
|
||||
return
|
||||
}
|
||||
const startX = 0
|
||||
const startY = props.boxHeight / 2 + props.marginY / 2
|
||||
const endX = x.value - 1
|
||||
const endY = y.value + props.boxHeight / 2
|
||||
|
||||
return `
|
||||
return `
|
||||
M ${startX} ${startY}
|
||||
L ${endX - 50} ${startY}
|
||||
C ${endX - 25} ${startY} ${endX - 25} ${startY} ${endX - 25} ${startY + 25 * Math.sign(endY - startY)}
|
||||
L ${endX - 25} ${endY - 25 * Math.sign(endY - startY)}
|
||||
C ${endX - 25} ${endY} ${endX - 25} ${endY} ${endX} ${endY}
|
||||
`
|
||||
})
|
||||
},
|
||||
variable () {
|
||||
const op = this.operator;
|
||||
if (this.node.rule[op] && this.node.rule[op][0]) {
|
||||
if (this.node.rule[op][0]["entries_since"]) {
|
||||
return "entries_since";
|
||||
}
|
||||
if (this.node.rule[op][0]["entries_before"]) {
|
||||
return "entries_before";
|
||||
}
|
||||
if (this.node.rule[op][0]["entries_days_since"]) {
|
||||
return "entries_days_since";
|
||||
}
|
||||
if (this.node.rule[op][0]["entries_days_before"]) {
|
||||
return "entries_days_before";
|
||||
}
|
||||
return this.node.rule[op][0]["var"];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
vardata () {
|
||||
return this.$root.VARS[this.variable];
|
||||
},
|
||||
varresult () {
|
||||
const op = this.operator;
|
||||
if (this.node.rule[op] && this.node.rule[op][0]) {
|
||||
if (typeof this.node.rule[op][0]["__result"] === "undefined")
|
||||
return null;
|
||||
return this.node.rule[op][0]["__result"];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
rightoperand () {
|
||||
const op = this.operator;
|
||||
if (this.node.rule[op] && typeof this.node.rule[op][1] !== "undefined") {
|
||||
return this.node.rule[op][1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
op: function () {
|
||||
return this.$root.TYPEOPS[this.vardata.type][this.operator]
|
||||
},
|
||||
operands: function () {
|
||||
return this.node.rule[this.operator]
|
||||
},
|
||||
operator: function () {
|
||||
return Object.keys(this.node.rule).filter(function (k) { return !k.startsWith("__") })[0];
|
||||
},
|
||||
result: function () {
|
||||
return typeof this.node.rule.__result == "undefined" ? null : !!this.node.rule.__result
|
||||
},
|
||||
resultInclParents: function () {
|
||||
if (typeof this.node.rule.__result == "undefined")
|
||||
return null
|
||||
|
||||
const operator = computed(() => {
|
||||
return Object.keys(props.node.rule).filter((k) => !k.startsWith('__'))[0]
|
||||
})
|
||||
|
||||
const variable = computed(() => {
|
||||
const op = operator.value
|
||||
if (props.node.rule[op] && props.node.rule[op][0]) {
|
||||
if (props.node.rule[op][0]['entries_since']) {
|
||||
return 'entries_since'
|
||||
}
|
||||
if (props.node.rule[op][0]['entries_before']) {
|
||||
return 'entries_before'
|
||||
}
|
||||
if (props.node.rule[op][0]['entries_days_since']) {
|
||||
return 'entries_days_since'
|
||||
}
|
||||
if (props.node.rule[op][0]['entries_days_before']) {
|
||||
return 'entries_days_before'
|
||||
}
|
||||
return props.node.rule[op][0]['var']
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const vardata = computed(() => {
|
||||
return VARS[variable.value as keyof typeof VARS]
|
||||
})
|
||||
|
||||
const varresult = computed(() => {
|
||||
const op = operator.value
|
||||
if (props.node.rule[op] && props.node.rule[op][0]) {
|
||||
if (typeof props.node.rule[op][0]['__result'] === 'undefined')
|
||||
return null
|
||||
return props.node.rule[op][0]['__result']
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const rightoperand = computed(() => {
|
||||
const op = operator.value
|
||||
if (props.node.rule[op] && typeof props.node.rule[op][1] !== 'undefined') {
|
||||
return props.node.rule[op][1]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const op = computed(() => {
|
||||
return TYPEOPS[vardata.value.type as keyof typeof TYPEOPS]?.[operator.value as any]
|
||||
})
|
||||
|
||||
const operands = computed(() => {
|
||||
return props.node.rule[operator.value]
|
||||
})
|
||||
|
||||
const result = computed(() => {
|
||||
return typeof props.node.rule.__result === 'undefined' ? null : !!props.node.rule.__result
|
||||
})
|
||||
|
||||
const resultInclParents = computed(() => {
|
||||
if (typeof props.node.rule.__result === 'undefined') return null
|
||||
|
||||
function _p (node: GraphNode): boolean {
|
||||
if (node.parent) {
|
||||
return node.rule.__result && _p(node.parent)
|
||||
}
|
||||
return node.rule.__result
|
||||
}
|
||||
return _p(props.node)
|
||||
})
|
||||
|
||||
const nodeClass = computed(() => {
|
||||
return {
|
||||
node: true,
|
||||
'node-true': result.value === true,
|
||||
'node-false': result.value === false,
|
||||
}
|
||||
})
|
||||
|
||||
function df (val: string) {
|
||||
const format = $('body').attr('data-datetimeformat')
|
||||
return moment(val).format(format)
|
||||
}
|
||||
|
||||
function tf (val: string) {
|
||||
const format = $('body').attr('data-timeformat')
|
||||
return moment(val, 'HH:mm:ss').format(format)
|
||||
}
|
||||
function _p(node) {
|
||||
if (node.parent) {
|
||||
return node.rule.__result && _p(node.parent)
|
||||
}
|
||||
return node.rule.__result
|
||||
}
|
||||
return _p(this.node)
|
||||
},
|
||||
nodeClass: function () {
|
||||
return {
|
||||
"node": true,
|
||||
"node-true": this.result === true,
|
||||
"node-false": this.result === false,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
df (val) {
|
||||
const format = $("body").attr("data-datetimeformat")
|
||||
return moment(val).format(format)
|
||||
},
|
||||
tf (val) {
|
||||
const format = $("body").attr("data-timeformat")
|
||||
return moment(val, "HH:mm:ss").format(format)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<template lang="pug">
|
||||
g
|
||||
path.edge(v-for="e in edges", :key="e", :d="e")
|
||||
path.edge(v-if="rootEdge", :d="rootEdge")
|
||||
path.edge(v-if="!node.children.length", :d="checkEdge")
|
||||
rect(:width="boxWidth", :height="boxHeight", :x="x", :y="y", :class="nodeClass", rx="5")
|
||||
|
||||
foreignObject(:width="boxWidth - 10", :height="boxHeight - 10", :x="x + 5", :y="y + 5")
|
||||
div.text(xmlns="http://www.w3.org/1999/xhtml")
|
||||
span(v-if="vardata && vardata.type === 'int'")
|
||||
span.fa.fa-sign-in(v-if="variable.startsWith('entries_')")
|
||||
| {{ vardata.label }}
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
span(v-else-if="vardata && vardata.type === 'int_by_datetime'")
|
||||
span.fa.fa-sign-in(v-if="variable.startsWith('entries_')")
|
||||
| {{ vardata.label }}
|
||||
span(v-if="node.rule[operator][0][variable][0].buildTime[0] === 'custom'")
|
||||
| {{ df(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||
span(v-else-if="node.rule[operator][0][variable][0].buildTime[0] === 'customtime'")
|
||||
| {{ tf(node.rule[operator][0][variable][0].buildTime[1]) }}
|
||||
span(v-else)
|
||||
| {{ TEXTS[node.rule[operator][0][variable][0].buildTime[0]] }}
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
span(v-else-if="vardata && variable === 'now'")
|
||||
span.fa.fa-clock-o
|
||||
| {{ vardata.label }}
|
||||
br
|
||||
span(v-if="varresult !== null") {{ varresult }}
|
||||
strong
|
||||
| {{ op.label }}
|
||||
br
|
||||
span(v-if="rightoperand.buildTime[0] === 'custom'")
|
||||
| {{ df(rightoperand.buildTime[1]) }}
|
||||
span(v-else-if="rightoperand.buildTime[0] === 'customtime'")
|
||||
| {{ tf(rightoperand.buildTime[1]) }}
|
||||
span(v-else)
|
||||
| {{ TEXTS[rightoperand.buildTime[0]] }}
|
||||
span(v-if="operands[2]")
|
||||
span(v-if="operator === 'isBefore'") +
|
||||
span(v-else) -
|
||||
| {{ operands[2] }}
|
||||
| {{ TEXTS.minutes }}
|
||||
span(v-else-if="vardata && operator === 'inList'")
|
||||
span.fa.fa-sign-in(v-if="variable === 'gate'")
|
||||
span.fa.fa-ticket(v-else)
|
||||
| {{ vardata.label }}
|
||||
span(v-if="varresult !== null") ({{ varresult }})
|
||||
br
|
||||
strong
|
||||
| {{ rightoperand.objectList.map((o: any) => o.lookup[2]).join(", ") }}
|
||||
span(v-else-if="vardata && vardata.type === 'enum_entry_status'")
|
||||
span.fa.fa-check-circle-o
|
||||
| {{ vardata.label }}
|
||||
span(v-if="varresult !== null") ({{ varresult }})
|
||||
br
|
||||
strong
|
||||
| {{ op.label }} {{ rightoperand }}
|
||||
|
||||
g(v-if="result === false", :transform="`translate(${x + boxWidth - 15}, ${y - 10})`")
|
||||
ellipse(fill="#fff", cx="14.685823", cy="14.318233", rx="12.140151", ry="11.55523")
|
||||
path.error(d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z")
|
||||
g(v-if="result === true", :transform="`translate(${x + boxWidth - 15}, ${y - 10})`")
|
||||
ellipse(fill="#fff", cx="14.685823", cy="14.318233", rx="12.140151", ry="11.55523")
|
||||
path.check(d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z")
|
||||
g(v-if="!node.children.length && (resultInclParents === null || resultInclParents === true)", :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`")
|
||||
path.check(d="m 25.078125,11.835938 c 0,-0.332032 -0.117188,-0.664063 -0.351563,-0.898438 L 22.949219,9.1796875 c -0.234375,-0.234375 -0.546875,-0.3710937 -0.878907,-0.3710937 -0.332031,0 -0.644531,0.1367187 -0.878906,0.3710937 L 13.222656,17.128906 8.8085938,12.714844 C 8.5742188,12.480469 8.2617188,12.34375 7.9296875,12.34375 c -0.3320313,0 -0.6445313,0.136719 -0.8789063,0.371094 l -1.7773437,1.757812 c -0.234375,0.234375 -0.3515625,0.566407 -0.3515625,0.898438 0,0.332031 0.1171875,0.644531 0.3515625,0.878906 l 7.0703125,7.070312 c 0.234375,0.234375 0.566406,0.371094 0.878906,0.371094 0.332032,0 0.664063,-0.136719 0.898438,-0.371094 L 24.726562,12.714844 c 0.234375,-0.234375 0.351563,-0.546875 0.351563,-0.878906 z M 30,15 C 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 23.28125,0 30,6.71875 30,15 Z")
|
||||
g(v-if="!node.children.length && (resultInclParents === false)", :transform="`translate(${x + boxWidth + 25}, ${y + boxHeight/2 - 15})`")
|
||||
path.error(d="M 15,0 C 23.28125,0 30,6.71875 30,15 30,23.28125 23.28125,30 15,30 6.71875,30 0,23.28125 0,15 0,6.71875 6.71875,0 15,0 Z m 2.5,24.35547 V 20.64453 C 17.5,20.29297 17.22656,20 16.89453,20 h -3.75 C 12.79297,20 12.5,20.29297 12.5,20.64453 v 3.71094 C 12.5,24.70703 12.79297,25 13.14453,25 h 3.75 C 17.22656,25 17.5,24.70703 17.5,24.35547 Z M 17.4609,17.63672 17.81246,5.50781 c 0,-0.13672 -0.0586,-0.27343 -0.19531,-0.35156 C 17.49996,5.05855 17.32418,5 17.1484,5 h -4.29688 c -0.17578,0 -0.35156,0.0586 -0.46875,0.15625 -0.13672,0.0781 -0.19531,0.21484 -0.19531,0.35156 l 0.33203,12.12891 c 0,0.27344 0.29297,0.48828 0.66406,0.48828 h 3.61329 c 0.35156,0 0.64453,-0.21484 0.66406,-0.48828 z")
|
||||
</template>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<script>
|
||||
import Questionnaire from './Questionnaire.vue';
|
||||
import {get_datafields, get_items, get_questionnaires} from './api';
|
||||
import { i18n_any, QUESTION_TYPE } from './helper';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const datafields_response = await get_datafields();
|
||||
const questionnaires_response = await get_questionnaires();
|
||||
const items_response = await get_items();
|
||||
|
||||
const questionnaires = ref(questionnaires_response.results);
|
||||
const datafields = ref(datafields_response.results);
|
||||
export default {
|
||||
components: {
|
||||
Questionnaire
|
||||
},
|
||||
methods: {
|
||||
i18n_any,
|
||||
addQuestionnaire: function() {
|
||||
questionnaires.value.push({
|
||||
items: [], internal_name: "Unnamed questionnaire",
|
||||
type: 'PS',
|
||||
});
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
questionnaires,
|
||||
datafields,
|
||||
items: items_response.results,
|
||||
selected_product: ref(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.hidden-question { opacity: 0.3; }
|
||||
.hidden-question label { text-decoration: line-through; }
|
||||
|
||||
.question-editor { margin-right: 140px; }
|
||||
.question-edit-buttons { float:right }
|
||||
.question-edit-buttons div { position: absolute; margin-left: 10px; }
|
||||
.question-edit-buttons button { }
|
||||
.form-group { margin-bottom: 30px }
|
||||
</style>
|
||||
<template>
|
||||
|
||||
<p>
|
||||
Questionnaires for product:
|
||||
<select v-model="selected_product">
|
||||
<option value="">(all)</option>
|
||||
<option v-for="item in items" :value="item.id">
|
||||
{{ i18n_any(item.name) }}
|
||||
</option>
|
||||
</select>
|
||||
</p>
|
||||
<div class="question-editor">
|
||||
<Questionnaire
|
||||
v-for="questionnaire in questionnaires"
|
||||
:questionnaire="questionnaire"
|
||||
:datafields="datafields"
|
||||
:items="items"
|
||||
:selected_product="selected_product" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<button class="btn btn-default" @click="addQuestionnaire()"><i class="fa fa-plus"></i> Neuen Fragebogen erstellen</button>
|
||||
</p>
|
||||
</template>
|
||||
@@ -1,14 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useId, defineProps } from 'vue';
|
||||
|
||||
const props = defineProps(['value', 'id']);
|
||||
|
||||
if (!props.value) props.value = {};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="i18n-form-group" :id="id">
|
||||
<textarea cols="40" rows="2" lang="en" dir="ltr" class="form-control" title="Englisch" :id="`${id}_0`" placeholder="Englisch" v-model="value.en"></textarea>
|
||||
<textarea cols="40" rows="2" lang="de" dir="ltr" class="form-control" title="Deutsch" :id="`${id}_1`" placeholder="Deutsch" v-model="value.de"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,49 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, useId } from 'vue';
|
||||
|
||||
const dialog = ref<HTMLDialogElement>();
|
||||
|
||||
const props = defineProps({
|
||||
classes: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: '',
|
||||
});
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
const showModal = () => {
|
||||
dialog.value?.showModal();
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
show: showModal,
|
||||
close: (returnVal?: string): void => dialog.value?.close(returnVal),
|
||||
visible,
|
||||
});
|
||||
const id = useId();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dialog
|
||||
ref="dialog" class="modal-card"
|
||||
@close="visible = false"
|
||||
closedby="any"
|
||||
:aria-labelledby="`${id}-title`"
|
||||
>
|
||||
<form
|
||||
v-if="visible"
|
||||
method="dialog" class="modal-card-inner form-horizontal"
|
||||
:class="{
|
||||
[props.classes]: props.classes,
|
||||
}"
|
||||
>
|
||||
<div class="modal-card-content">
|
||||
<h2 :id="`${id}-title`" class="modal-card-title h3">{{ title }}</h2>
|
||||
<slot />
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user