feat(16-automated-tasks): moduł zadań automatycznych — CRUD + watcher/executor

Reguły automatyzacji oparte na zdarzeniach (receipt.created) z warunkami
(integracja/kanał sprzedaży, AND logic) i akcjami (wyślij e-mail z 3 trybami
odbiorcy: klient / firma / klient+firma). Trigger w ReceiptController po
utworzeniu paragonu — błąd automatyzacji nie blokuje sukcesu paragonu.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 00:39:47 +01:00
parent a6512cbfa4
commit b9f639e037
24 changed files with 4997 additions and 32 deletions

View File

@@ -0,0 +1,132 @@
(function() {
'use strict';
var conditionsContainer = document.getElementById('js-conditions-container');
var actionsContainer = document.getElementById('js-actions-container');
var addConditionBtn = document.getElementById('js-add-condition');
var addActionBtn = document.getElementById('js-add-action');
var data = window.AutomationFormData || { integrations: [], emailTemplates: [], recipientLabels: {} };
function getNextIndex(container) {
var rows = container.querySelectorAll('.automation-row');
var maxIdx = -1;
rows.forEach(function(row) {
var idx = parseInt(row.getAttribute('data-index') || '0', 10);
if (idx > maxIdx) maxIdx = idx;
});
return maxIdx + 1;
}
function escapeHtml(str) {
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function buildIntegrationCheckboxes(namePrefix) {
var html = '<div class="checkbox-group">';
data.integrations.forEach(function(integ) {
html += '<label class="checkbox-label">'
+ '<input type="checkbox" name="' + namePrefix + '[integration_ids][]" value="' + integ.id + '"> '
+ escapeHtml(integ.name) + ' <span class="muted">(' + escapeHtml(integ.type) + ')</span>'
+ '</label>';
});
html += '</div>';
return html;
}
function buildEmailActionConfig(namePrefix) {
var html = '<select class="form-control" name="' + namePrefix + '[template_id]">'
+ '<option value="">-- Wybierz szablon --</option>';
data.emailTemplates.forEach(function(tpl) {
html += '<option value="' + tpl.id + '">' + escapeHtml(tpl.name) + '</option>';
});
html += '</select>';
html += '<select class="form-control" name="' + namePrefix + '[recipient]">';
Object.keys(data.recipientLabels).forEach(function(key) {
html += '<option value="' + escapeHtml(key) + '">' + escapeHtml(data.recipientLabels[key]) + '</option>';
});
html += '</select>';
return html;
}
function addCondition() {
var idx = getNextIndex(conditionsContainer);
var namePrefix = 'conditions[' + idx + ']';
var row = document.createElement('div');
row.className = 'automation-row mt-8';
row.setAttribute('data-index', idx);
row.innerHTML = '<div class="automation-row__fields">'
+ '<select class="form-control automation-row__type" name="' + namePrefix + '[type]" onchange="window.AutomationForm.onConditionTypeChange(this)">'
+ '<option value="integration" selected>Integracja (kanal sprzedazy)</option>'
+ '</select>'
+ '<div class="automation-row__config">'
+ buildIntegrationCheckboxes(namePrefix)
+ '</div>'
+ '</div>'
+ '<button type="button" class="btn btn--sm btn--danger automation-row__remove" onclick="window.AutomationForm.removeRow(this)">&times;</button>';
conditionsContainer.appendChild(row);
}
function addAction() {
var idx = getNextIndex(actionsContainer);
var namePrefix = 'actions[' + idx + ']';
var row = document.createElement('div');
row.className = 'automation-row mt-8';
row.setAttribute('data-index', idx);
row.innerHTML = '<div class="automation-row__fields">'
+ '<select class="form-control automation-row__type" name="' + namePrefix + '[type]" onchange="window.AutomationForm.onActionTypeChange(this)">'
+ '<option value="send_email" selected>Wyslij e-mail</option>'
+ '</select>'
+ '<div class="automation-row__config">'
+ buildEmailActionConfig(namePrefix)
+ '</div>'
+ '</div>'
+ '<button type="button" class="btn btn--sm btn--danger automation-row__remove" onclick="window.AutomationForm.removeRow(this)">&times;</button>';
actionsContainer.appendChild(row);
}
function removeRow(btn) {
var row = btn.closest('.automation-row');
if (row) row.remove();
}
function onConditionTypeChange(select) {
var row = select.closest('.automation-row');
var configDiv = row.querySelector('.automation-row__config');
var idx = row.getAttribute('data-index');
var namePrefix = 'conditions[' + idx + ']';
if (select.value === 'integration') {
configDiv.innerHTML = buildIntegrationCheckboxes(namePrefix);
}
}
function onActionTypeChange(select) {
var row = select.closest('.automation-row');
var configDiv = row.querySelector('.automation-row__config');
var idx = row.getAttribute('data-index');
var namePrefix = 'actions[' + idx + ']';
if (select.value === 'send_email') {
configDiv.innerHTML = buildEmailActionConfig(namePrefix);
}
}
if (addConditionBtn) addConditionBtn.addEventListener('click', addCondition);
if (addActionBtn) addActionBtn.addEventListener('click', addAction);
window.AutomationForm = {
removeRow: removeRow,
onConditionTypeChange: onConditionTypeChange,
onActionTypeChange: onActionTypeChange
};
})();