259 lines
17 KiB
HTML
259 lines
17 KiB
HTML
{% set resources = resources or {} %}
|
|
<section class="rounded-2xl border border-slate-200 bg-white shadow-soft">
|
|
<div class="border-b border-slate-200 px-5 py-4">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div>
|
|
<h3 class="font-semibold text-slate-900">Engagement Data & Documents</h3>
|
|
<p class="mt-1 text-sm text-slate-500">Tally/accounting storage and engagement files are available here before the task checklist.</p>
|
|
</div>
|
|
<a href="/documents/engagements/{{ engagement_id }}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Open Full Document Register</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid gap-4 p-5 lg:grid-cols-[minmax(0,1fr)_minmax(360px,0.8fr)]">
|
|
<div class="space-y-4">
|
|
<div class="rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
|
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
<div>
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-indigo-600">Accounting Intelligence</div>
|
|
<div class="mt-1 font-semibold text-indigo-950">Client accounting .act path</div>
|
|
{% if resources.registered_mirror %}
|
|
<div class="mt-1 text-xs text-indigo-700">Registered mirror · FY {{ resources.registered_mirror.financial_year }} · v{{ resources.registered_mirror.version_no }}{% if resources.registered_mirror.company_name %} · {{ resources.registered_mirror.company_name }}{% endif %}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
{% if resources.registered_mirror %}<span class="rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-semibold text-emerald-700">Mirror Available</span>{% endif %}
|
|
{% if resources.storage_node_name %}<span class="rounded-full bg-white px-2.5 py-1 text-xs font-semibold text-indigo-700">{{ resources.storage_node_name }}</span>{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if resources.accounting_local_path %}
|
|
<div class="mt-3 flex gap-2">
|
|
<input id="engagement-accounting-path" readonly value="{{ resources.accounting_local_path }}" class="min-w-0 flex-1 rounded-xl border border-indigo-200 bg-white px-3 py-2 font-mono text-xs text-slate-700">
|
|
<button type="button" data-copy-target="engagement-accounting-path" class="rounded-xl bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-700">Copy Path</button>
|
|
</div>
|
|
<p class="mt-2 text-xs text-indigo-700">This is the existing .act accounting/intelligence database path used by the Local Agent. Uploaded Tally data paths are shown separately on the right.</p>
|
|
{% elif resources.accounting_relative_path %}
|
|
<div class="mt-3 rounded-xl border border-indigo-200 bg-white px-3 py-2 font-mono text-xs text-slate-700">{{ resources.accounting_relative_path }}</div>
|
|
<p class="mt-2 text-xs text-amber-700">The relative accounting path is known, but this branch does not currently expose a configured local storage root.</p>
|
|
{% else %}
|
|
<p class="mt-3 text-sm text-slate-600">Accounting path is unavailable for this engagement.</p>
|
|
{% endif %}
|
|
|
|
{% if resources.registered_mirror and resources.registered_mirror.tally_data_local_path %}
|
|
<div class="mt-4 border-t border-indigo-200 pt-3">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-indigo-600">Tally Working Data Path</div>
|
|
<div class="mt-2 flex gap-2">
|
|
<input id="engagement-tally-working-path" readonly value="{{ resources.registered_mirror.tally_data_local_path }}" class="min-w-0 flex-1 rounded-xl border border-indigo-200 bg-white px-3 py-2 font-mono text-xs text-slate-700">
|
|
<button type="button" data-copy-target="engagement-tally-working-path" class="rounded-xl bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-700">Copy Path</button>
|
|
</div>
|
|
<p class="mt-2 text-xs text-indigo-700">This path is stored with the Client + FY mirror metadata and is reused by the engagement workspace.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div>
|
|
<div class="mb-2 flex items-center justify-between gap-3">
|
|
<div class="text-sm font-semibold text-slate-900">Current Engagement Files</div>
|
|
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-600">{{ resources.document_options|length }}</span>
|
|
</div>
|
|
<div class="max-h-72 space-y-2 overflow-y-auto pr-1">
|
|
{% for doc in resources.document_options %}
|
|
<div class="rounded-xl border border-slate-200 p-3">
|
|
<div class="flex flex-wrap items-start justify-between gap-2">
|
|
<div class="min-w-0">
|
|
<div class="truncate text-sm font-semibold text-slate-900">{{ doc.title }}</div>
|
|
<div class="mt-1 text-xs text-slate-500">{{ doc.document_type|replace('_',' ')|title }} · v{{ doc.version_no }}{% if doc.latest_filename %} · {{ doc.latest_filename }}{% endif %}</div>
|
|
</div>
|
|
{% if doc.storage_status %}<span class="rounded-full bg-slate-100 px-2 py-1 text-[11px] font-semibold text-slate-600">{{ doc.storage_status|replace('_',' ')|title }}</span>{% endif %}
|
|
</div>
|
|
{% if doc.latest_relative_path %}<div class="mt-2 break-all font-mono text-[11px] text-slate-400">{{ doc.latest_relative_path }}</div>{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="rounded-xl border border-dashed border-slate-300 p-5 text-center text-sm text-slate-500">No engagement documents uploaded yet.</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
|
{% if resources.tally_data_files %}
|
|
<div class="mb-4 rounded-2xl border border-emerald-200 bg-emerald-50 p-3">
|
|
<div class="text-sm font-semibold text-emerald-950">Uploaded Tally Data Paths</div>
|
|
<p class="mt-1 text-xs text-emerald-700">Copy the required year path for opening-balance comparison or local Tally work.</p>
|
|
<div class="mt-3 space-y-2">
|
|
{% for tally in resources.tally_data_files %}
|
|
<div class="rounded-xl border border-emerald-200 bg-white p-2.5">
|
|
<div class="mb-1 flex items-center justify-between gap-2">
|
|
<span class="text-xs font-semibold text-emerald-900">FY {{ tally.financial_year }}</span>
|
|
<span class="text-[11px] text-slate-500">v{{ tally.version_no }}{% if tally.latest_filename %} · {{ tally.latest_filename }}{% endif %}</span>
|
|
</div>
|
|
{% if tally.local_path %}
|
|
<div class="flex gap-2">
|
|
<input id="tally-data-path-{{ loop.index }}" readonly value="{{ tally.local_path }}" class="min-w-0 flex-1 rounded-lg border border-emerald-200 bg-white px-2.5 py-2 font-mono text-[11px] text-slate-700">
|
|
<button type="button" data-copy-target="tally-data-path-{{ loop.index }}" class="rounded-lg bg-emerald-600 px-2.5 py-2 text-xs font-semibold text-white hover:bg-emerald-700">Copy Path</button>
|
|
</div>
|
|
{% elif tally.latest_relative_path %}
|
|
<div class="break-all rounded-lg border border-emerald-200 bg-white px-2.5 py-2 font-mono text-[11px] text-slate-700">{{ tally.latest_relative_path }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="text-sm font-semibold text-slate-900">Upload from Engagement Page</div>
|
|
<p class="mt-1 text-xs text-slate-500">Select Tally Data or a service task category. Category uploads stay linked to the existing task/evidence workflow.</p>
|
|
<form method="post" enctype="multipart/form-data" action="/documents/engagements/{{ engagement_id }}/upload" class="mt-4 space-y-3" data-engagement-upload-form data-engagement-id="{{ engagement_id }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="return_to" value="{{ return_to }}">
|
|
<input type="hidden" name="upload_target" value="tally" data-upload-target-field>
|
|
<div>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Upload To</label>
|
|
<select class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" data-upload-target-select>
|
|
<option value="tally" data-kind="tally">Tally Data</option>
|
|
{% for category in resources.task_categories %}
|
|
<option value="category-{{ loop.index }}" data-kind="category" data-task-id="{{ category.task_id or '' }}" {% if not category.available %}disabled{% endif %}>
|
|
{{ category.name }}{% if not category.available %} — no task in this engagement{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% if resources.task_categories %}
|
|
<p class="mt-1 text-[11px] text-slate-500">Categories come from this service's Task Category master; older engagements are mapped through their task templates where needed.</p>
|
|
{% endif %}
|
|
</div>
|
|
<div data-tally-year-wrap>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Tally Financial Year</label>
|
|
<select name="tally_financial_year" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" data-tally-year-select>
|
|
{% for year in resources.tally_year_options %}<option value="{{ year }}">FY {{ year }}{% if loop.first %} — engagement year{% else %} — previous year{% endif %}</option>{% endfor %}
|
|
</select>
|
|
<p class="mt-1 text-[11px] text-slate-500">Previous-year Tally data is kept separately so opening balances can be compared without replacing the current-year dataset.</p>
|
|
</div>
|
|
<div>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Upload Action</label>
|
|
<select class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" data-upload-mode>
|
|
<option value="new">New Document</option>
|
|
<option value="replace">Replace Current Document</option>
|
|
<option value="version">Add Another Version</option>
|
|
</select>
|
|
</div>
|
|
<div data-existing-document-wrap class="hidden">
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Existing Document</label>
|
|
<select name="existing_document_id" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" data-existing-document-select>
|
|
<option value="">Select document</option>
|
|
{% for doc in resources.document_options %}<option value="{{ doc.id }}" data-task-id="{{ doc.task_instance_id or '' }}" data-tally-fy="{{ doc.tally_financial_year or '' }}">{{ doc.title }} · v{{ doc.version_no }}</option>{% endfor %}
|
|
</select>
|
|
<p class="mt-1 text-[11px] text-slate-500">Replacement/version uploads use the existing document container and retain the ERP audit/version history.</p>
|
|
</div>
|
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
<div data-document-type-wrap>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Document Type</label>
|
|
<select name="document_type" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" data-document-type-select>
|
|
{% for item in ['GENERAL','CLIENT_DOCUMENT','WORKING_PAPER','BANK_STATEMENT','TRIAL_BALANCE','LEDGER','GST_RETURN','INCOME_TAX','ROC','AUDIT_REPORT','SIGNED_OUTPUT','ACKNOWLEDGEMENT'] %}<option value="{{ item }}">{{ item|replace('_',' ')|title }}</option>{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Title</label>
|
|
<input name="title" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" placeholder="Defaults to file name">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">File</label>
|
|
<input type="file" name="file" required class="block w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Description / Remarks</label>
|
|
<input name="remarks" class="w-full rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm" placeholder="Optional">
|
|
</div>
|
|
<div data-upload-target-note class="rounded-xl border border-indigo-100 bg-indigo-50 px-3 py-2 text-xs text-indigo-700">Tally Data is stored at engagement level and remains separate from task-category evidence.</div>
|
|
<button class="w-full rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-700" data-upload-submit>Upload Tally Data</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
(function () {
|
|
const form = document.querySelector('[data-engagement-upload-form]');
|
|
if (form) {
|
|
const targetSelect = form.querySelector('[data-upload-target-select]');
|
|
const mode = form.querySelector('[data-upload-mode]');
|
|
const wrap = form.querySelector('[data-existing-document-wrap]');
|
|
const existing = form.querySelector('[data-existing-document-select]');
|
|
const note = form.querySelector('[data-upload-target-note]');
|
|
const uploadTargetField = form.querySelector('[data-upload-target-field]');
|
|
const tallyYearWrap = form.querySelector('[data-tally-year-wrap]');
|
|
const tallyYear = form.querySelector('[data-tally-year-select]');
|
|
const documentTypeWrap = form.querySelector('[data-document-type-wrap]');
|
|
const documentTypeSelect = form.querySelector('[data-document-type-select]');
|
|
const uploadSubmit = form.querySelector('[data-upload-submit]');
|
|
const engagementId = form.getAttribute('data-engagement-id');
|
|
|
|
const filterExistingDocuments = function () {
|
|
const selected = targetSelect.options[targetSelect.selectedIndex];
|
|
const kind = selected ? selected.getAttribute('data-kind') : 'tally';
|
|
const taskId = selected ? (selected.getAttribute('data-task-id') || '') : '';
|
|
const fy = tallyYear ? tallyYear.value : '';
|
|
Array.from(existing.options).forEach(function (opt, idx) {
|
|
if (idx === 0) return;
|
|
const optionTask = opt.getAttribute('data-task-id') || '';
|
|
const optionTallyFy = opt.getAttribute('data-tally-fy') || '';
|
|
const visible = kind === 'tally' ? optionTallyFy === fy : optionTask === taskId && !optionTallyFy;
|
|
opt.hidden = !visible;
|
|
opt.disabled = !visible;
|
|
});
|
|
if (existing.selectedOptions.length && existing.selectedOptions[0].disabled) existing.value = '';
|
|
};
|
|
|
|
const updateAction = function () {
|
|
const option = targetSelect.options[targetSelect.selectedIndex];
|
|
const kind = option ? option.getAttribute('data-kind') : 'tally';
|
|
const taskId = option ? option.getAttribute('data-task-id') : '';
|
|
if (kind === 'category' && taskId) {
|
|
form.action = '/documents/tasks/' + taskId + '/upload';
|
|
uploadTargetField.value = 'category';
|
|
tallyYearWrap.classList.add('hidden');
|
|
if (documentTypeWrap) documentTypeWrap.classList.remove('hidden');
|
|
if (documentTypeSelect) documentTypeSelect.disabled = false;
|
|
if (uploadSubmit) uploadSubmit.textContent = 'Upload Document';
|
|
note.textContent = 'This file will be linked to the selected category through its existing engagement task, preserving evidence and AQMM behaviour.';
|
|
} else {
|
|
form.action = '/documents/engagements/' + engagementId + '/upload';
|
|
uploadTargetField.value = 'tally';
|
|
tallyYearWrap.classList.remove('hidden');
|
|
if (documentTypeWrap) documentTypeWrap.classList.add('hidden');
|
|
if (documentTypeSelect) documentTypeSelect.disabled = true;
|
|
if (uploadSubmit) uploadSubmit.textContent = 'Upload Tally Data';
|
|
note.textContent = 'Tally Data is stored under the selected financial year. Current-year and previous-year datasets remain separate for opening-balance comparison.';
|
|
}
|
|
filterExistingDocuments();
|
|
};
|
|
const updateMode = function () {
|
|
const useExisting = mode.value !== 'new';
|
|
wrap.classList.toggle('hidden', !useExisting);
|
|
existing.required = useExisting;
|
|
if (!useExisting) existing.value = '';
|
|
};
|
|
targetSelect.addEventListener('change', updateAction);
|
|
if (tallyYear) tallyYear.addEventListener('change', filterExistingDocuments);
|
|
mode.addEventListener('change', updateMode);
|
|
updateAction();
|
|
updateMode();
|
|
}
|
|
document.querySelectorAll('[data-copy-target]').forEach(function (button) {
|
|
button.addEventListener('click', async function () {
|
|
const field = document.getElementById(button.getAttribute('data-copy-target'));
|
|
if (!field) return;
|
|
try {
|
|
await navigator.clipboard.writeText(field.value);
|
|
const oldText = button.textContent;
|
|
button.textContent = 'Copied';
|
|
setTimeout(function () { button.textContent = oldText; }, 1200);
|
|
} catch (err) {
|
|
field.select();
|
|
document.execCommand('copy');
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script>
|