mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
feat: Attachment files system.
This commit is contained in:
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/node_modules/
|
||||
/.env
|
||||
/.env.test
|
||||
/.env.test
|
||||
/storage
|
||||
@@ -10,6 +10,8 @@ exports.up = function (knex) {
|
||||
table.integer('role_id').unique();
|
||||
table.string('language');
|
||||
table.date('last_login_at');
|
||||
|
||||
table.date('invite_accepted_at');
|
||||
table.timestamps();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('media_links', table => {
|
||||
table.increments();
|
||||
table.string('model_name');
|
||||
table.integer('media_id').unsigned();
|
||||
table.integer('model_id').unsigned();
|
||||
})
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTableIfExists('media_links');
|
||||
};
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
const filter = {
|
||||
filter_roles: [],
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
page_size: 999,
|
||||
...req.query,
|
||||
};
|
||||
if (filter.stringified_filter_roles) {
|
||||
@@ -90,8 +90,6 @@ export default {
|
||||
}
|
||||
const { Resource, View, ManualJournal } = req.models;
|
||||
|
||||
console.log(req.models);
|
||||
|
||||
const errorReasons = [];
|
||||
const manualJournalsResource = await Resource.query()
|
||||
.where('name', 'manual_journals')
|
||||
@@ -185,6 +183,8 @@ export default {
|
||||
check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(),
|
||||
check('entries.*.account_id').isNumeric().toInt(),
|
||||
check('entries.*.note').optional(),
|
||||
check('media_ids').optional().isArray(),
|
||||
check('media_ids.*').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
@@ -198,9 +198,10 @@ export default {
|
||||
date: new Date(),
|
||||
transaction_type: 'journal',
|
||||
reference: '',
|
||||
media_ids: [],
|
||||
...req.body,
|
||||
};
|
||||
const { ManualJournal, Account } = req.models;
|
||||
const { ManualJournal, Account, Media, MediaLink } = req.models;
|
||||
|
||||
let totalCredit = 0;
|
||||
let totalDebit = 0;
|
||||
@@ -233,6 +234,14 @@ export default {
|
||||
|
||||
const storedAccountsIds = accounts.map((account) => account.id);
|
||||
|
||||
if (form.media_ids.length > 0) {
|
||||
const storedMedia = await Media.query().whereIn('id', form.media_ids);
|
||||
const notFoundMedia = difference(form.media_ids, storedMedia.map((m) => m.id));
|
||||
|
||||
if (notFoundMedia.length > 0) {
|
||||
errorReasons.push({ type: 'MEDIA.IDS.NOT.FOUND', code: 400, ids: notFoundMedia });
|
||||
}
|
||||
}
|
||||
if (difference(accountsIds, storedAccountsIds).length > 0) {
|
||||
errorReasons.push({ type: 'ACCOUNTS.IDS.NOT.FOUND', code: 200 });
|
||||
}
|
||||
@@ -279,8 +288,22 @@ export default {
|
||||
journalPoster.credit(jouranlEntry);
|
||||
}
|
||||
});
|
||||
|
||||
// Save linked media to the journal model.
|
||||
const bulkSaveMediaLink = [];
|
||||
|
||||
form.media_ids.forEach((mediaId) => {
|
||||
const oper = MediaLink.query().insert({
|
||||
model_name: 'Journal',
|
||||
model_id: manualJournal.id,
|
||||
media_id: mediaId,
|
||||
});
|
||||
bulkSaveMediaLink.push(oper);
|
||||
});
|
||||
|
||||
// Saves the journal entries and accounts balance changes.
|
||||
await Promise.all([
|
||||
...bulkSaveMediaLink,
|
||||
journalPoster.saveEntries(),
|
||||
(form.status) && journalPoster.saveBalance(),
|
||||
]);
|
||||
@@ -313,6 +336,9 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the given manual journal.
|
||||
*/
|
||||
editManualJournal: {
|
||||
validation: [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
@@ -326,6 +352,8 @@ export default {
|
||||
check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(),
|
||||
check('entries.*.account_id').isNumeric().toInt(),
|
||||
check('entries.*.note').optional(),
|
||||
check('media_ids').optional().isArray(),
|
||||
check('media_ids.*').isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
@@ -339,14 +367,17 @@ export default {
|
||||
date: new Date(),
|
||||
transaction_type: 'journal',
|
||||
reference: '',
|
||||
media_ids: [],
|
||||
...req.body,
|
||||
};
|
||||
const { id } = req.params;
|
||||
const {
|
||||
ManualJournal, AccountTransaction, Account,
|
||||
ManualJournal, AccountTransaction, Account, Media, MediaLink,
|
||||
} = req.models;
|
||||
|
||||
const manualJournal = await ManualJournal.query().where('id', id).first();
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id)
|
||||
.withGraphFetched('media').first();
|
||||
|
||||
if (!manualJournal) {
|
||||
return res.status(4040).send({
|
||||
@@ -395,6 +426,16 @@ export default {
|
||||
if (difference(accountsIds, storedAccountsIds).length > 0) {
|
||||
errorReasons.push({ type: 'ACCOUNTS.IDS.NOT.FOUND', code: 200 });
|
||||
}
|
||||
|
||||
// Validate if media ids was not already exists on the storage.
|
||||
if (form.media_ids.length > 0) {
|
||||
const storedMedia = await Media.query().whereIn('id', form.media_ids);
|
||||
const notFoundMedia = difference(form.media_ids, storedMedia.map((m) => m.id));
|
||||
|
||||
if (notFoundMedia.length > 0) {
|
||||
errorReasons.push({ type: 'MEDIA.IDS.NOT.FOUND', code: 400, ids: notFoundMedia });
|
||||
}
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
@@ -439,7 +480,23 @@ export default {
|
||||
journal.credit(jouranlEntry);
|
||||
}
|
||||
});
|
||||
|
||||
// Save links of new inserted media that associated to the journal model.
|
||||
const journalMediaIds = manualJournal.media.map((m) => m.id);
|
||||
const newInsertedMedia = difference(form.media_ids, journalMediaIds);
|
||||
const bulkSaveMediaLink = [];
|
||||
|
||||
newInsertedMedia.forEach((mediaId) => {
|
||||
const oper = MediaLink.query().insert({
|
||||
model_name: 'Journal',
|
||||
model_id: manualJournal.id,
|
||||
media_id: mediaId,
|
||||
});
|
||||
bulkSaveMediaLink.push(oper);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
...bulkSaveMediaLink,
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
@@ -524,7 +581,9 @@ export default {
|
||||
|
||||
const { id } = req.params;
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id).first();
|
||||
.where('id', id)
|
||||
.withGraphFetched('media')
|
||||
.first();
|
||||
|
||||
if (!manualJournal) {
|
||||
return res.status(404).send({
|
||||
@@ -564,7 +623,9 @@ export default {
|
||||
}
|
||||
const { id } = req.params;
|
||||
const {
|
||||
ManualJournal, AccountTransaction,
|
||||
ManualJournal,
|
||||
AccountTransaction,
|
||||
MediaLink,
|
||||
} = req.models;
|
||||
const manualJournal = await ManualJournal.query()
|
||||
.where('id', id).first();
|
||||
@@ -583,6 +644,11 @@ export default {
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await MediaLink.query()
|
||||
.where('model_name', 'Journal')
|
||||
.where('model_id', manualJournal.id)
|
||||
.delete();
|
||||
|
||||
await ManualJournal.query()
|
||||
.where('id', manualJournal.id)
|
||||
.delete();
|
||||
@@ -678,7 +744,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const filter = { ...req.query };
|
||||
const { ManualJournal, AccountTransaction } = req.models;
|
||||
const { ManualJournal, AccountTransaction, MediaLink } = req.models;
|
||||
|
||||
const manualJournals = await ManualJournal.query()
|
||||
.whereIn('id', filter.ids);
|
||||
@@ -699,6 +765,11 @@ export default {
|
||||
journal.loadEntries(transactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await MediaLink.query()
|
||||
.where('model_name', 'Journal')
|
||||
.whereIn('model_id', filter.ids)
|
||||
.delete();
|
||||
|
||||
await ManualJournal.query()
|
||||
.whereIn('id', filter.ids).delete();
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ export default {
|
||||
check('custom_fields.*.value').exists(),
|
||||
|
||||
check('note').optional(),
|
||||
|
||||
check('media_ids').optional().isArray(),
|
||||
check('media_ids.*').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Media } = req.models;
|
||||
const { Media, MediaLink } = req.models;
|
||||
const { id } = req.params;
|
||||
const media = await Media.query().where('id', id).first();
|
||||
|
||||
@@ -137,6 +137,8 @@ export default {
|
||||
} catch (error) {
|
||||
Logger.log('error', 'Delete item attachment file delete failed.', { error });
|
||||
}
|
||||
|
||||
await MediaLink.query().where('media_id', media.id).delete();
|
||||
await Media.query().where('id', media.id).delete();
|
||||
|
||||
return res.status(200).send();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from '@/models/TenantModel';
|
||||
|
||||
export default class ManualJournal extends TenantModel {
|
||||
@@ -7,4 +8,26 @@ export default class ManualJournal extends TenantModel {
|
||||
static get tableName() {
|
||||
return 'manual_journals';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Media = require('@/models/Media');
|
||||
|
||||
return {
|
||||
media: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: this.relationBindKnex(Media.default),
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
through: {
|
||||
from: 'media_links.model_id',
|
||||
to: 'media_links.media_id',
|
||||
},
|
||||
to: 'media.id',
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
10
server/src/models/MediaLink.js
Normal file
10
server/src/models/MediaLink.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import TenantModel from '@/models/TenantModel';
|
||||
|
||||
export default class MediaLink extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'media_links';
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export default class JournalPoster {
|
||||
|
||||
async deleteEntries() {
|
||||
if (this.deletedEntriesIds.length > 0) {
|
||||
await AccountTransaction.query()
|
||||
await AccountTransaction.tenant().query()
|
||||
.whereIn('id', this.deletedEntriesIds)
|
||||
.delete();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ exports.up = function (knex) {
|
||||
table.boolean('active');
|
||||
table.integer('role_id').unique();
|
||||
table.string('language');
|
||||
table.date('last_login_at');
|
||||
|
||||
table.integer('tenant_id').unsigned();
|
||||
|
||||
table.date('last_login_at');
|
||||
table.timestamps();
|
||||
}).then(() => {
|
||||
// knex.seed.run({
|
||||
|
||||
Reference in New Issue
Block a user