Updated Asset Entry and Views

This commit is contained in:
2026-06-03 14:12:32 -05:00
parent 2f13b8c590
commit 8335f1af1b
9 changed files with 620 additions and 73 deletions

View File

@@ -130,6 +130,10 @@
<AssetDrawer
v-model="assetDrawer"
:asset-form="assetForm"
:asset-files="activeFiles"
:asset-notes="activeNotes"
:asset-tasks="activeTasks"
:asset-warranties="activeWarranties"
:category-items="categoryItems"
:drawer-error="drawerError"
:entity-options="entityOptions"
@@ -139,8 +143,18 @@
:selected-template-id="selectedTemplateId"
:status-items="statusItems"
:template-options="templateOptions"
@add-note="addNote"
@add-task="addTask"
@add-warranty="addWarranty"
@delete-file="deleteAssetFile"
@delete-note="deleteNote"
@delete-task="deleteTask"
@delete-warranty="deleteWarranty"
@download-file="downloadAssetFile"
@save-asset="saveAsset"
@select-template="applyTemplate"
@toggle-task="toggleTask"
@upload-file="uploadAssetFile"
/>
<MassEditDialog
@@ -166,7 +180,7 @@ import AssetDrawer from './components/AssetDrawer.vue';
import MassEditDialog from './components/MassEditDialog.vue';
import TopNav from './components/TopNav.vue';
import { apiRequest, loginRequest, saveBlob } from './services/api';
import { blankAsset, defaultValueForField, normalizeCustomFields } from './utils/assets';
import { blankAsset, defaultValueForField, makeDefaultBooks, normalizeCustomFields } from './utils/assets';
import { currency, shortDate } from './utils/format';
import { bookItems, customFieldTypes, navItems, statusItems, themeItems } from './constants';
@@ -193,6 +207,10 @@ export default {
data() {
return {
activeView: 'dashboard',
activeFiles: [],
activeNotes: [],
activeTasks: [],
activeWarranties: [],
assets: [],
assetDrawer: false,
assetFilters: { search: '', status: null },
@@ -373,7 +391,10 @@ export default {
for (const field of normalizeCustomFields(template.custom_fields)) {
if (customFields[field.key] === undefined) customFields[field.key] = defaultValueForField(field);
}
this.assetForm = { ...this.assetForm, ...template.defaults, custom_fields: customFields, template_id: template.id };
const merged = { ...this.assetForm, ...template.defaults, custom_fields: customFields, template_id: template.id };
// For a brand-new asset, realign the book defaults to the template's method/life.
if (!merged.id) merged.books = makeDefaultBooks(merged);
this.assetForm = merged;
},
async assignAsset(form) {
this.assignmentSaving = true;
@@ -406,11 +427,79 @@ export default {
const blob = await (await this.api(`/api/reports/depreciation.pdf?year=${this.reportFilters.year}&book=${this.reportFilters.book}`)).blob();
saveBlob(blob, `mixedassets-${this.reportFilters.book}-${this.reportFilters.year}.pdf`);
},
editAsset(asset) {
this.assetForm = { ...blankAsset(), ...asset, custom_fields: asset.custom_fields || {} };
this.selectedTemplateId = asset.template_id || null;
async editAsset(asset) {
this.drawerError = '';
const full = await this.refreshActiveAsset(asset.id);
this.assetForm = {
...blankAsset(),
...full,
custom_fields: full.custom_fields || {},
books: full.books && full.books.length ? full.books : makeDefaultBooks(full)
};
this.selectedTemplateId = full.template_id || null;
this.assetDrawer = true;
},
async refreshActiveAsset(id) {
const data = await (await this.api(`/api/assets/${id}`)).json();
const asset = data.asset;
this.activeFiles = asset.files || [];
this.activeWarranties = asset.warranties || [];
this.activeTasks = asset.tasks || [];
this.activeNotes = asset.notes_log || [];
return asset;
},
clearActiveCollections() {
this.activeFiles = [];
this.activeWarranties = [];
this.activeTasks = [];
this.activeNotes = [];
},
async uploadAssetFile(file) {
const formData = new FormData();
formData.append('file', file);
await this.api(`/api/assets/${this.assetForm.id}/files`, { method: 'POST', body: formData });
await this.refreshActiveAsset(this.assetForm.id);
},
async downloadAssetFile(file) {
const blob = await (await this.api(`/api/assets/${this.assetForm.id}/files/${file.id}`)).blob();
saveBlob(blob, file.file_name);
},
async deleteAssetFile(fileId) {
await this.api(`/api/assets/${this.assetForm.id}/files/${fileId}`, { method: 'DELETE' });
await this.refreshActiveAsset(this.assetForm.id);
},
async addWarranty(warranty) {
await this.api(`/api/assets/${this.assetForm.id}/warranties`, { method: 'POST', body: JSON.stringify(warranty) });
await this.refreshActiveAsset(this.assetForm.id);
},
async deleteWarranty(id) {
await this.api(`/api/assets/${this.assetForm.id}/warranties/${id}`, { method: 'DELETE' });
await this.refreshActiveAsset(this.assetForm.id);
},
async addTask(task) {
await this.api(`/api/assets/${this.assetForm.id}/tasks`, { method: 'POST', body: JSON.stringify(task) });
await this.refreshActiveAsset(this.assetForm.id);
await this.loadDashboard();
},
async toggleTask(task) {
const status = task.status === 'complete' ? 'open' : 'complete';
await this.api(`/api/assets/${this.assetForm.id}/tasks/${task.id}`, { method: 'PUT', body: JSON.stringify({ status }) });
await this.refreshActiveAsset(this.assetForm.id);
await this.loadDashboard();
},
async deleteTask(id) {
await this.api(`/api/assets/${this.assetForm.id}/tasks/${id}`, { method: 'DELETE' });
await this.refreshActiveAsset(this.assetForm.id);
await this.loadDashboard();
},
async addNote(body) {
await this.api(`/api/assets/${this.assetForm.id}/notes`, { method: 'POST', body: JSON.stringify({ body }) });
await this.refreshActiveAsset(this.assetForm.id);
},
async deleteNote(id) {
await this.api(`/api/assets/${this.assetForm.id}/notes/${id}`, { method: 'DELETE' });
await this.refreshActiveAsset(this.assetForm.id);
},
async exportAssets(format) {
const blob = await (await this.api(`/api/export/assets?format=${format}`)).blob();
saveBlob(blob, `mixedassets-assets.${format}`);
@@ -541,27 +630,38 @@ export default {
this.assetForm.entity_id = this.entities[0]?.id || null;
this.selectedTemplateId = null;
this.drawerError = '';
this.clearActiveCollections();
this.assetDrawer = true;
},
async saveAsset(assetInput) {
this.saving = true;
this.drawerError = '';
try {
const payload = {
...assetInput,
books: this.bookItems.map((book) => ({
book_type: book,
depreciation_method: assetInput.depreciation_method,
useful_life_months: assetInput.useful_life_months,
cost: assetInput.acquisition_cost
}))
};
const books = (assetInput.books && assetInput.books.length ? assetInput.books : makeDefaultBooks(assetInput))
.map((book) => ({
...book,
cost: book.cost === '' || book.cost === null || book.cost === undefined ? null : Number(book.cost)
}));
const payload = { ...assetInput, books };
const method = assetInput.id ? 'PUT' : 'POST';
const url = assetInput.id ? `/api/assets/${assetInput.id}` : '/api/assets';
await this.api(url, { method, body: JSON.stringify(payload) });
this.assetDrawer = false;
const saved = await (await this.api(url, { method, body: JSON.stringify(payload) })).json();
await this.loadAssets();
await this.loadDashboard();
if (!assetInput.id && saved.asset) {
// Keep the drawer open on the freshly created asset so files, warranties,
// tasks, and notes can be attached without reopening.
this.assetForm = {
...blankAsset(),
...saved.asset,
custom_fields: saved.asset.custom_fields || {},
books: saved.asset.books && saved.asset.books.length ? saved.asset.books : makeDefaultBooks(saved.asset)
};
this.selectedTemplateId = saved.asset.template_id || null;
await this.refreshActiveAsset(saved.asset.id);
} else {
this.assetDrawer = false;
}
} catch (error) {
this.drawerError = error.message;
} finally {