@@ -409,16 +450,18 @@ async function issueBatch(){
var name = document.getElementById('iss-name').value.trim();
var vf = document.getElementById('iss-from').value;
var vu = document.getElementById('iss-until').value;
+ var bonus = document.getElementById('iss-bonus').checked;
var msg = document.getElementById('iss-msg');
if(count<1||count>5000){ msg.textContent='Anzahl 1–5000.'; return; }
+ var delivered = calcDelivered(count, bonus);
var who = name||email||custno||'(kein Kunde)';
- if(count>=100 && !confirm(count+' Codes für „'+who+'"'+(order?' (Best.-Nr. '+order+')':'')+', gültig bis '+fmtDate(vu)+', Typ '+type+'.\n\nWirklich erzeugen?')) return;
+ if(count>=100 && !confirm(count+' bestellt → '+delivered+' Codes im Paket für „'+who+'"'+(order?' (Best.-Nr. '+order+')':'')+', gültig bis '+fmtDate(vu)+', Typ '+type+'.\n\nWirklich erzeugen?')) return;
msg.textContent = 'Erzeuge …';
- var r = await api('licenses', {action:'issue_batch', count:count, sales_type:type, order_no:order,
+ var r = await api('licenses', {action:'issue_batch', count:count, bonus:bonus, sales_type:type, order_no:order,
customer:{email:email, customer_no:custno, name:name}, valid_from:vf, valid_until:vu});
if(r.error){ msg.innerHTML='
'+esc(r.error)+''; return; }
- msg.innerHTML='
✓ '+r.count+' Codes erzeugt (Charge #'+r.batch_id+').';
- lastIssue = {batch_id:r.batch_id, codes:r.codes, count:r.count, valid_until:r.valid_until, token:r.redeem_token, order_no:order, email:email};
+ msg.innerHTML='
✓ '+r.count+' Codes erzeugt (Bestellung #'+r.internal_no+', bestellt '+r.ordered+').';
+ lastIssue = {batch_id:r.batch_id, internal_no:r.internal_no, ordered:r.ordered, codes:r.codes, count:r.count, valid_until:r.valid_until, token:r.redeem_token, order_no:order, email:email};
renderIssueResult();
loadBatches();
loadLicenses();
@@ -429,17 +472,15 @@ function renderIssueResult(){
var codesText = m.codes.join('\n');
var mailText = buildMailText(m);
var h = '
'
+ + '
Bestellung #'+m.internal_no+' · bestellt '+m.ordered+' · geliefert '+m.count+''+(m.count>m.ordered?' (+'+(m.count-m.ordered)+' gratis)':'')+' · gültig bis '+fmtDate(m.valid_until)+'
'
+ '
1 · Codes (' + m.count + ') — kopieren
'
+ '
'
+ '
'
+ ''
+ ''
- + ''
+ + ''
+ '
'
- + '
2 · Sammel-Einlöse-Link (alle auf einmal)
'
- + '
'
- + '
Pro Code funktioniert auch: '+esc(absUrl('/einloesen?code=CODE'))+'
'
- + '
3 · Per Mail senden
'
+ + '
2 · Per Mail senden
'
+ '
'
+ '
'
+ '
'
@@ -457,20 +498,65 @@ function buildMailText(m){
var order = m.order_no ? ('\nBestellnummer: '+m.order_no) : '';
return 'Guten Tag,\n\nvielen Dank für Ihre Bestellung. Anbei Ihre '+m.count+' GeoGraSim-Lizenzcodes, gültig bis '+fmtDate(m.valid_until)+'.'+order+'\n\n'
+ codesPart + '\n\n'
- + 'Einlösen: Als Lehrperson unter '+absUrl('/')+' anmelden und die Codes unter „Lizenzen" eingeben.\n'
- + 'Oder alle auf einmal über diesen Link: '+absUrl('/einloesen?charge='+m.token)+'\n\n'
+ + 'Einlösen: Als Lehrperson unter '+PUBLIC_URL+' anmelden und die Codes unter „Lizenzen" eingeben.\n\n'
+ 'Freundliche Grüße\nGeoGraSim';
}
function copyEl(id){ var el=document.getElementById(id); el.select(); navigator.clipboard.writeText(el.value).catch(function(){document.execCommand('copy');}); }
+// CSV: Metadaten (inkl. Gültig-bis) in die KOPFZEILE, danach reine Code-Zeilen
+// -> der Code-Block bleibt per Copy-Paste einlösbar (kein Datum je Zeile).
function downloadCodes(fmt){
var m = lastIssue; if(!m) return;
var content, mime;
- if(fmt==='csv'){ content='Code;Gueltig_bis\n'+m.codes.map(function(c){return c+';'+m.valid_until;}).join('\n')+'\n'; mime='text/csv'; }
- else { content=m.codes.join('\r\n')+'\r\n'; mime='text/plain'; }
- var base = m.order_no ? m.order_no.replace(/[^a-zA-Z0-9_\-]/g,'_') : (new Date().toISOString().slice(0,16).replace(/[:T]/g,'-'));
- var a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([content],{type:mime})); a.download=base+'.'+fmt; a.click();
+ if(fmt==='csv'){
+ var head='Bestellung;#'+(m.internal_no||'')+'\nExterne Bestellnummer;'+(m.order_no||'')+'\nGültig bis;'+fmtDate(m.valid_until)+'\nAnzahl;'+m.count+'\n\n';
+ content=head+m.codes.join('\r\n')+'\r\n'; mime='text/csv';
+ } else { content=m.codes.join('\r\n')+'\r\n'; mime='text/plain'; }
+ var ext = m.order_no ? m.order_no.replace(/[^a-zA-Z0-9_\-]/g,'_') : (new Date().toISOString().slice(0,16).replace(/[:T]/g,'-'));
+ var fname = ext+'_geograsim_'+(m.internal_no||'')+'.'+fmt;
+ var a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([content],{type:mime})); a.download=fname; a.click();
+}
+
+function calcDelivered(ordered, bonus){ return bonus ? Math.ceil(ordered*1.1) : ordered; }
+function updatePreview(){
+ var o=parseInt(document.getElementById('iss-count').value)||0;
+ var b=document.getElementById('iss-bonus').checked;
+ var el=document.getElementById('iss-preview'); if(!el) return;
+ if(o<1){ el.textContent=''; return; }
+ var d=calcDelivered(o,b);
+ el.innerHTML = b ? ('→ im Paket
'+d+' Codes (+'+(d-o)+' gratis)') : ('→
'+d+' Codes (keine Zugabe)');
+}
+
+// ── Mail-Einstellungen ──
+var PUBLIC_URL='https://geograsim.at';
+async function loadMailConfig(){
+ var r=await api('licenses?admin=1&mail_config=1');
+ if(!r || r.error) return;
+ PUBLIC_URL=r.public_url||PUBLIC_URL;
+ document.getElementById('mc-provider').value=r.provider||'server';
+ document.getElementById('mc-fromname').value=r.from_name||'';
+ document.getElementById('mc-fromemail').value=r.from_email||'';
+ document.getElementById('mc-publicurl').value=r.public_url||'';
+}
+async function saveMailConfig(){
+ var mm=document.getElementById('mc-msg'); mm.textContent='Speichere …';
+ var r=await api('licenses',{action:'set_mail_config',
+ provider:document.getElementById('mc-provider').value,
+ from_name:document.getElementById('mc-fromname').value.trim(),
+ from_email:document.getElementById('mc-fromemail').value.trim(),
+ public_url:document.getElementById('mc-publicurl').value.trim()});
+ if(r.error){ mm.innerHTML='
'+esc(r.error)+''; return; }
+ mm.innerHTML='
✓ Gespeichert.'; loadMailConfig();
+}
+async function testMail(){
+ var to=document.getElementById('mc-testto').value.trim();
+ var mm=document.getElementById('mc-msg');
+ if(!to){ mm.innerHTML='
Test-Adresse fehlt.'; return; }
+ mm.textContent='Sende Test …';
+ var r=await api('licenses',{action:'test_mail', to_email:to});
+ if(r.error){ mm.innerHTML='
'+esc(r.error)+''; return; }
+ mm.innerHTML='
✓ Test gesendet.';
}
async function sendMail(batchId){
@@ -542,8 +628,6 @@ async function openBatch(id){
+ ' · gültig '+fmtDate(b.default_valid_from)+'–'+fmtDate(b.default_valid_until)
+ (b.canceled_at?' ·
Charge storniert':'')+'
';
h+='
'
- + ''
- + ''
+ ''
+ '
';
h+='
| Code | Status | Gültig bis | Lehrperson | |
';
@@ -608,6 +692,8 @@ fetch(BASE + '/api/admin', {method:'POST', headers:{'Content-Type':'application/
loadLicenses();
prefillDefaults();
loadBatches();
+ loadMailConfig();
+ updatePreview();
});