Merged the AssetAssignment collection with the Asset collection; Updated views for assigning assets; Added view to find asset assignments by assetId.

This commit is contained in:
2022-08-14 17:07:14 -07:00
parent f3b1b38e82
commit cf51200393
13 changed files with 3897 additions and 53 deletions

View File

@@ -36,3 +36,5 @@ alanning:roles # Adds roles to the user
msavin:mongol # Free version of MeteorToys - Provides access to the client side MongoDB for debugging. (Ctrl-M to activate :: https://atmospherejs.com/msavin/mongol)
#zodern:melte # Alternative to meteor-svelte (https://github.com/meteor-svelte/meteor-svelte). Was more actively developed.
svelte:compiler # Switching back to this because of TS errors.
tunguska:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it.

View File

@@ -98,6 +98,7 @@ templating-runtime@1.6.0
templating-tools@1.2.2
tmeasday:check-npm-versions@1.0.2
tracker@1.2.0
tunguska:reactive-aggregate@1.3.8
typescript@4.5.4
underscore@1.0.10
url@1.3.2

View File

@@ -73,7 +73,6 @@ Meteor.methods({
check(assigneeType, String);
check(assetId, String);
if(assigneeType !== 'Student' && assigneeType !== 'Staff') {
// Should never happen.
console.error("Error: Received incorrect assignee type in adding an assignment.");

View File

@@ -4,6 +4,8 @@ import { check } from 'meteor/check';
import { Roles } from 'meteor/alanning:roles';
//import SimpleSchema from "simpl-schema";
import {AssetTypes} from "./asset-types";
import { ReactiveAggregate } from 'meteor/tunguska:reactive-aggregate';
import {AssetAssignments} from "/imports/api/asset-assignments";
export const Assets = new Mongo.Collection('assets');
@@ -31,6 +33,25 @@ const AssetsSchema = new SimpleSchema({
index: 1,
unique: false
},
assigneeId: { //Should be undefined or non-existent if not assigned.
type: String,
label: "Assignee ID",
optional: true,
},
assigneeType: { // 0: Student, 1: Staff, Should be undefined or non-existent if not assigned.
type: SimpleSchema.Integer,
label: "Assignee Type",
optional: true,
min: 0,
max: 1,
exclusiveMin: false,
exclusiveMax: false,
},
assignmentDate: {
type: Date,
label: "Assignment Date",
optional: true,
}
});
Assets.attachSchema(AssetsSchema);
*/
@@ -84,9 +105,91 @@ Meteor.methods({
check(_id, String);
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
//TODO: Need to first verify there are no checked out assets to the staff member.
//TODO: Ensure we have not assigned this asset??? Not sure if we should require unassigning first.
Assets.remove({_id});
}
},
/**
* Assigns the asset to the assignee. The assignee should either be a Student or Staff member.
* @param assetId The Asset ID (eg: 'Z1Q') of the asset (asset.assetId).
* @param assigneeType One of: 'Student', 'Staff'
* @param assigneeId The Mongo ID of the Student or Staff (person._id).
* @param date The date/time of the action. Will be set to the current date/time if not provided.
*/
'assets.assign'(assetId, assigneeType, assigneeId, date) {
check(assigneeId, String);
check(assigneeType, String);
check(assetId, String);
if(date) check(date, Date);
if(!date) date = new Date();
if(assigneeType !== 'Student' && assigneeType !== 'Staff') {
// Should never happen.
console.error("Error: Received incorrect assignee type in adding an assignment.");
console.error(assigneeType);
}
else if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
let asset = Assets.findOne({assetId});
if(asset) {
if(asset.assigneeId) {
//TODO: Should we unassign and re-assign????
console.error("Asset is already assigned! " + assetId);
throw new Meteor.Error("Asset is already assigned.", "Cannot assign an asset that has already been assigned.");
}
else {
Assets.update({assetId}, {$set: {assigneeType, assigneeId, assignmentDate: date}});
}
}
else {
console.error("Could not find the asset: " + assetId)
}
}
},
/**
* Removes an assignment for the asset.
* TODO: Should create a historical record.
* @param assetId The Asset ID (eg: 'Z1Q') of the asset (asset.assetId).
* @param date The date/time of the action. Will be set to the current date/time if not provided.
*/
'assets.unassign'(assetId, date) {
check(assetId, String);
if(date) check(date, Date);
if(!date) date = new Date();
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
Assets.update({assetId}, {$unset: {assigneeType, assigneeId, assignmentDate}});
}
},
/**
* A fix to remove the AssetAssignment collection and merge it with the Asset collection.
*/
'assets.fixAssetAssignments'() {
let assignmentDate = new Date();
//This function just removes the need for the asset-assignments collection and merges it with assets.
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
let assets = Assets.find({}).fetch();
let assetAssignments = AssetAssignments.find({}).fetch();
let assetMap = assets.reduce((map, obj) => {
map[obj.assetId] = obj;
return map;
}, {});
console.log(assetMap);
console.log("");
for(let next of assetAssignments) {
console.log(next);
let asset = assetMap[next.assetId];
console.log("Updating " + asset.assetId + " to be assigned to " + next.assigneeType + ": " + next.assigneeId);
let c = Assets.update({assetId: asset.assetId}, {$set: {assigneeType: next.assigneeType, assigneeId: next.assigneeId, assignmentDate}});
console.log("Updated " + c + " Assets");
console.log(Assets.findOne({assetId: asset.assetId}));
}
}
}
});

View File

@@ -4,7 +4,7 @@ import { Roles } from 'meteor/alanning:roles';
import {check} from "meteor/check";
import {Sites} from "/imports/api/sites";
import {parse} from "csv-parse";
import {Students} from "/imports/api/students";
import { ReactiveAggregate } from 'meteor/tunguska:reactive-aggregate';
export const Staff = new Mongo.Collection('staff');
@@ -13,6 +13,15 @@ if (Meteor.isServer) {
Meteor.publish('staff', function(siteId) {
return Staff.find({siteId});
});
Meteor.publish('staffWithAssetAssignments', function(query) {
ReactiveAggregate(this, Staff, {$lookup: {
from: 'assetAssignments',
localField: '_id',
foreignField: 'assigneeId',
as: 'assignments'
}}, {});
//Note: The options can use {clientCollection: 'your_name_here'} as the options to change the collection name on the client.
});
}
Meteor.methods({
'staff.add'(firstName, lastName, email, siteId) {

View File

@@ -4,6 +4,7 @@ import { check } from 'meteor/check';
import {Sites} from "./sites";
import { Roles } from 'meteor/alanning:roles';
import {parse} from 'csv-parse';
import { ReactiveAggregate } from 'meteor/tunguska:reactive-aggregate';
export const Students = new Mongo.Collection('students');
@@ -14,6 +15,15 @@ if (Meteor.isServer) {
Meteor.publish('students', function(siteId) {
return Students.find({siteId});
});
Meteor.publish('studentWithAssetAssignments', function(query) {
ReactiveAggregate(this, Students, {$lookup: {
from: 'assetAssignments',
localField: '_id',
foreignField: 'assigneeId',
as: 'assignments'
}}, {});
//Note: The options can use {clientCollection: 'your_name_here'} as the options to change the collection name on the client.
});
Meteor.methods({
'students.getPossibleGrades'() {

View File

@@ -5,12 +5,13 @@
import Sites from "/imports/ui/Admin/Sites.svelte";
import Students from "/imports/ui/Admin/Students.svelte";
import Staff from "/imports/ui/Admin/Staff.svelte";
import Functions from "/imports/ui/Admin/Functions.svelte";
let activeTab = "Sites";
</script>
<div class="container">
<TabBar tabs={['Sites','Students','Staff','Asset Types']} minWidth let:tab bind:active={activeTab}>
<TabBar tabs={['Sites','Students','Staff','Asset Types', 'Functions']} minWidth let:tab bind:active={activeTab}>
<Tab {tab}>
<Label>{tab}</Label>
</Tab>
@@ -23,6 +24,8 @@
<Staff></Staff>
{:else if activeTab === 'Asset Types'}
<AssetTypes></AssetTypes>
{:else if activeTab === 'Functions'}
<Functions></Functions>
{/if}
</div>

View File

@@ -0,0 +1,16 @@
<script>
import Button, { Label } from '@smui/button';
const fixAssetAssignments = () => {
Meteor.call('assets.fixAssetAssignments');
}
</script>
<div class="container">
<Button variant="raised" touch on:click={fixAssetAssignments}>
<Label style="color: white">Fix Assignments</Label>
</Button>
</div>
<style>
</style>

View File

@@ -7,7 +7,8 @@
import AddAssets from "/imports/ui/Assets/AddAssets.svelte";
import {useTracker} from "meteor/rdb:svelte-meteor-data";
import AssignAssets from "/imports/ui/Assets/AssignAssets.svelte";
import Assignments from "/imports/ui/Assets/Assignments.svelte";
import AssignmentByAssignee from "/imports/ui/Assets/AssignmentByAssignee.svelte";
import AssignmentByAsset from "/imports/ui/Assets/AssignmentByAsset.svelte";
let canManageLaptops = false;
let isAdmin = false;
@@ -23,7 +24,8 @@
let tabs = [];
if(canManageLaptops) {
tabs.push({id: 'listAssignments', label: 'Assignment List'});
tabs.push({id: 'listAssignmentsByAssignee', label: 'Assignments By Assignee'});
tabs.push({id: 'listAssignmentsByAsset', label: 'Assignments By Asset'});
tabs.push({id: 'assignAssets', label: 'Assign Assets'});
}
if(isAdmin) {
@@ -45,8 +47,10 @@
<AddAssets></AddAssets>
{:else if activeTab && activeTab.id === 'assignAssets'}
<AssignAssets></AssignAssets>
{:else if activeTab && activeTab.id === 'listAssignments'}
<Assignments></Assignments>
{:else if activeTab && activeTab.id === 'listAssignmentsByAssignee'}
<AssignmentByAssignee></AssignmentByAssignee>
{:else if activeTab && activeTab.id === 'listAssignmentsByAsset'}
<AssignmentByAsset></AssignmentByAsset>
{/if}
</div>

View File

@@ -57,10 +57,17 @@
const createAssignment = () => {
if(assetId && assetId.length && selectedAssignee) {
Meteor.call("AssetAssignments.add", assetId, selectedCategory === 'Student' ? "Student" : "Staff", selectedAssignee._id)
Meteor.call("assets.assign", assetId, selectedCategory === 'Student' ? "Student" : "Staff", selectedAssignee._id, (err, result) => {
if(err) {
console.error(err);
//TODO: Display an error!
}
else {
// TODO: Set focus to the asset ID field.
assetId = "";
}
});
}
}
</script>

View File

@@ -0,0 +1,84 @@
<script>
import {Meteor} from "meteor/meteor";
import {onMount} from "svelte";
import TextField from '@smui/textfield';
import {Staff} from "/imports/api/staff";
import List, {Item, Graphic, Meta, Text, PrimaryText, SecondaryText} from '@smui/list';
import Paper from '@smui/paper';
import LayoutGrid, {Cell} from '@smui/layout-grid';
import {Assets} from "/imports/api/assets";
import {Students} from "/imports/api/students";
import {AssetTypes} from "/imports/api/asset-types";
onMount(async () => {
Meteor.subscribe('assets');
Meteor.subscribe('students');
Meteor.subscribe('staff');
Meteor.subscribe('assetTypes');
});
let searchText = "";
let foundAsset;
let selectedResult;
let foundAssignee;
let foundAssetType;
$: {
selectedResult = null;
if(searchText) {
foundAsset = Assets.findOne({assetId: searchText});
}
else {
foundAsset = undefined;
}
}
$: {
if(foundAsset) {
foundAssetType = AssetTypes.findOne({_id: foundAsset.assetTypeId});
if(foundAsset.assigneeType === 'Student') {
foundAssignee = Students.findOne({_id: foundAsset.assigneeId});
}
else {
foundAssignee = Staff.findOne({_id: foundAsset.assigneeId});
}
}
else {
foundAssetType = undefined;
foundAssignee = undefined;
}
}
const formatDate = (date) => {
return date.toLocaleDateString('en-us', {weekday: 'long', year: 'numeric', month: 'short', day: 'numeric'});
}
</script>
<div class="container">
<h1 style="display: block">Asset Assignments</h1>
<Paper>
<LayoutGrid>
<Cell span="{6}">
<TextField type="text" bind:value={searchText} label="Asset ID"></TextField>
</Cell>
</LayoutGrid>
</Paper>
{#if foundAsset}
<div>Asset ID: {foundAsset.assetId}</div>
{#if foundAssetType}
<div>{foundAssetType.name}</div>
{/if}
{#if foundAssignee}
<div>Assigned on: {formatDate(foundAsset.assignmentDate)}</div>
<div>Assigned to: {foundAssignee.firstName} {foundAssignee.lastName}
{#if foundAssignee.grade} ~ {foundAssignee.grade} {/if}({foundAssignee.email})</div>
{/if}
{/if}
</div>
<style>
</style>

3690
package-lock.json generated

File diff suppressed because it is too large Load Diff