Autosave: 20260329-052922
This commit is contained in:
parent
e79ff80194
commit
65dc763357
0
.perm_test_apache
Normal file
0
.perm_test_apache
Normal file
0
.perm_test_exec
Normal file
0
.perm_test_exec
Normal file
BIN
assets/pasted-20260329-051144-5bfe6900.png
Normal file
BIN
assets/pasted-20260329-051144-5bfe6900.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
211
backend/src/db/api/quartiers.js
Normal file
211
backend/src/db/api/quartiers.js
Normal file
@ -0,0 +1,211 @@
|
||||
const db = require('../models');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class QuartiersDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const record = await db.quartiers.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
nom: data.nom || null,
|
||||
notes: data.notes || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await record.setVillage(data.village || data.villageId || null, { transaction });
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const rows = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
nom: item.nom || null,
|
||||
notes: item.notes || null,
|
||||
villageId: item.villageId || item.village || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
return db.quartiers.bulkCreate(rows, { transaction });
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.quartiers.findByPk(id, { transaction });
|
||||
|
||||
const payload = {
|
||||
updatedById: currentUser.id,
|
||||
};
|
||||
|
||||
if (data.nom !== undefined) payload.nom = data.nom;
|
||||
if (data.notes !== undefined) payload.notes = data.notes;
|
||||
|
||||
await record.update(payload, { transaction });
|
||||
|
||||
if (data.village !== undefined || data.villageId !== undefined) {
|
||||
await record.setVillage(data.village || data.villageId || null, { transaction });
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const records = await db.quartiers.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.quartiers.findByPk(id, { transaction });
|
||||
|
||||
await record.destroy({ transaction });
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.quartiers.findOne({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: db.villages,
|
||||
as: 'village',
|
||||
attributes: ['id', 'nom'],
|
||||
},
|
||||
],
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return record;
|
||||
}
|
||||
|
||||
return record.get({ plain: true });
|
||||
}
|
||||
|
||||
static async findAll(filter, options) {
|
||||
const limit = filter.limit || 0;
|
||||
const currentPage = +filter.page || 0;
|
||||
const offset = currentPage * limit;
|
||||
let where = {};
|
||||
|
||||
if (filter.id) {
|
||||
where.id = Utils.uuid(filter.id);
|
||||
}
|
||||
|
||||
if (filter.nom) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('quartiers', 'nom', filter.nom),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.notes) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('quartiers', 'notes', filter.notes),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.villageId || filter.village) {
|
||||
where.villageId = filter.villageId || filter.village;
|
||||
}
|
||||
|
||||
const include = [
|
||||
{
|
||||
model: db.villages,
|
||||
as: 'village',
|
||||
attributes: ['id', 'nom'],
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
const queryOptions = {
|
||||
where,
|
||||
include,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['nom', 'asc']],
|
||||
transaction: options?.transaction,
|
||||
logging: console.log,
|
||||
};
|
||||
|
||||
if (!options?.countOnly) {
|
||||
queryOptions.limit = limit ? Number(limit) : undefined;
|
||||
queryOptions.offset = offset ? Number(offset) : undefined;
|
||||
}
|
||||
|
||||
const { rows, count } = await db.quartiers.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
static async findAllAutocomplete(query, limit, offset) {
|
||||
let where = {};
|
||||
|
||||
if (query) {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ id: Utils.uuid(query) },
|
||||
Utils.ilike('quartiers', 'nom', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.quartiers.findAll({
|
||||
attributes: ['id', 'nom'],
|
||||
include: [
|
||||
{
|
||||
model: db.villages,
|
||||
as: 'village',
|
||||
attributes: ['nom'],
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
order: [['nom', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.village?.nom ? `${record.nom} (${record.village.nom})` : record.nom,
|
||||
}));
|
||||
}
|
||||
};
|
||||
207
backend/src/db/api/type_usages.js
Normal file
207
backend/src/db/api/type_usages.js
Normal file
@ -0,0 +1,207 @@
|
||||
const db = require('../models');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class Type_usagesDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const type_usages = await db.type_usages.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
nom: data.nom || null,
|
||||
tarif: data.tarif ?? 0,
|
||||
actif: data.actif ?? true,
|
||||
description: data.description || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
return type_usages;
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const rows = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
nom: item.nom || null,
|
||||
tarif: item.tarif ?? 0,
|
||||
actif: item.actif === undefined ? true : item.actif,
|
||||
description: item.description || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
return db.type_usages.bulkCreate(rows, { transaction });
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const type_usages = await db.type_usages.findByPk(id, { transaction });
|
||||
|
||||
const payload = {
|
||||
updatedById: currentUser.id,
|
||||
};
|
||||
|
||||
if (data.nom !== undefined) payload.nom = data.nom;
|
||||
if (data.tarif !== undefined) payload.tarif = data.tarif;
|
||||
if (data.actif !== undefined) payload.actif = data.actif;
|
||||
if (data.description !== undefined) payload.description = data.description;
|
||||
|
||||
await type_usages.update(payload, { transaction });
|
||||
|
||||
return type_usages;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const records = await db.type_usages.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.type_usages.findByPk(id, { transaction });
|
||||
|
||||
await record.destroy({ transaction });
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const record = await db.type_usages.findOne({
|
||||
where,
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return record;
|
||||
}
|
||||
|
||||
return record.get({ plain: true });
|
||||
}
|
||||
|
||||
static async findAll(filter, options) {
|
||||
const limit = filter.limit || 0;
|
||||
const currentPage = +filter.page || 0;
|
||||
const offset = currentPage * limit;
|
||||
let where = {};
|
||||
|
||||
if (filter.id) {
|
||||
where.id = Utils.uuid(filter.id);
|
||||
}
|
||||
|
||||
if (filter.nom) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('type_usages', 'nom', filter.nom),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.description) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('type_usages', 'description', filter.description),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.actif !== undefined) {
|
||||
where.actif = filter.actif === true || filter.actif === 'true';
|
||||
}
|
||||
|
||||
if (filter.tarifRange) {
|
||||
const values = Array.isArray(filter.tarifRange)
|
||||
? filter.tarifRange
|
||||
: [filter.tarifRange];
|
||||
const [start, end] = values;
|
||||
|
||||
if (start !== undefined && start !== '') {
|
||||
where.tarif = {
|
||||
...where.tarif,
|
||||
[Op.gte]: start,
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== '') {
|
||||
where.tarif = {
|
||||
...where.tarif,
|
||||
[Op.lte]: end,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
where,
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['nom', 'asc']],
|
||||
transaction: options?.transaction,
|
||||
logging: console.log,
|
||||
};
|
||||
|
||||
if (!options?.countOnly) {
|
||||
queryOptions.limit = limit ? Number(limit) : undefined;
|
||||
queryOptions.offset = offset ? Number(offset) : undefined;
|
||||
}
|
||||
|
||||
const { rows, count } = await db.type_usages.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
static async findAllAutocomplete(query, limit, offset) {
|
||||
let where = {};
|
||||
|
||||
if (query) {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ id: Utils.uuid(query) },
|
||||
Utils.ilike('type_usages', 'nom', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.type_usages.findAll({
|
||||
attributes: ['id', 'nom'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
order: [['nom', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.nom,
|
||||
}));
|
||||
}
|
||||
};
|
||||
226
backend/src/db/api/villages.js
Normal file
226
backend/src/db/api/villages.js
Normal file
@ -0,0 +1,226 @@
|
||||
const db = require('../models');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class VillagesDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
return db.villages.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
nom: data.nom || null,
|
||||
commune: data.commune || null,
|
||||
arrondissement: data.arrondissement || null,
|
||||
departement: data.departement || null,
|
||||
region: data.region || null,
|
||||
duree_periode_jours: data.duree_periode_jours || null,
|
||||
notes: data.notes || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const rows = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
nom: item.nom || null,
|
||||
commune: item.commune || null,
|
||||
arrondissement: item.arrondissement || null,
|
||||
departement: item.departement || null,
|
||||
region: item.region || null,
|
||||
duree_periode_jours: item.duree_periode_jours || null,
|
||||
notes: item.notes || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
return db.villages.bulkCreate(rows, { transaction });
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.villages.findByPk(id, { transaction });
|
||||
|
||||
const payload = {
|
||||
updatedById: currentUser.id,
|
||||
};
|
||||
|
||||
if (data.nom !== undefined) payload.nom = data.nom;
|
||||
if (data.commune !== undefined) payload.commune = data.commune;
|
||||
if (data.arrondissement !== undefined) payload.arrondissement = data.arrondissement;
|
||||
if (data.departement !== undefined) payload.departement = data.departement;
|
||||
if (data.region !== undefined) payload.region = data.region;
|
||||
if (data.duree_periode_jours !== undefined) payload.duree_periode_jours = data.duree_periode_jours;
|
||||
if (data.notes !== undefined) payload.notes = data.notes;
|
||||
|
||||
await record.update(payload, { transaction });
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const records = await db.villages.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.villages.findByPk(id, { transaction });
|
||||
|
||||
await record.destroy({ transaction });
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
const record = await db.villages.findOne({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: db.quartiers,
|
||||
as: 'quartiers',
|
||||
attributes: ['id', 'nom'],
|
||||
},
|
||||
],
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return record;
|
||||
}
|
||||
|
||||
return record.get({ plain: true });
|
||||
}
|
||||
|
||||
static async findAll(filter, options) {
|
||||
const limit = filter.limit || 0;
|
||||
const currentPage = +filter.page || 0;
|
||||
const offset = currentPage * limit;
|
||||
let where = {};
|
||||
|
||||
if (filter.id) {
|
||||
where.id = Utils.uuid(filter.id);
|
||||
}
|
||||
|
||||
if (filter.nom) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('villages', 'nom', filter.nom),
|
||||
};
|
||||
}
|
||||
|
||||
for (const field of ['commune', 'arrondissement', 'departement', 'region', 'notes']) {
|
||||
if (filter[field]) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('villages', field, filter[field]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.duree_periode_joursRange) {
|
||||
const values = Array.isArray(filter.duree_periode_joursRange)
|
||||
? filter.duree_periode_joursRange
|
||||
: [filter.duree_periode_joursRange];
|
||||
const [start, end] = values;
|
||||
|
||||
if (start !== undefined && start !== '') {
|
||||
where.duree_periode_jours = {
|
||||
...where.duree_periode_jours,
|
||||
[Op.gte]: start,
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== '') {
|
||||
where.duree_periode_jours = {
|
||||
...where.duree_periode_jours,
|
||||
[Op.lte]: end,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: db.quartiers,
|
||||
as: 'quartiers',
|
||||
attributes: ['id', 'nom'],
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
distinct: true,
|
||||
order: filter.field && filter.sort
|
||||
? [[filter.field, filter.sort]]
|
||||
: [['nom', 'asc']],
|
||||
transaction: options?.transaction,
|
||||
logging: console.log,
|
||||
};
|
||||
|
||||
if (!options?.countOnly) {
|
||||
queryOptions.limit = limit ? Number(limit) : undefined;
|
||||
queryOptions.offset = offset ? Number(offset) : undefined;
|
||||
}
|
||||
|
||||
const { rows, count } = await db.villages.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
static async findAllAutocomplete(query, limit, offset) {
|
||||
let where = {};
|
||||
|
||||
if (query) {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ id: Utils.uuid(query) },
|
||||
Utils.ilike('villages', 'nom', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.villages.findAll({
|
||||
attributes: ['id', 'nom'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
order: [['nom', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.nom,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,214 @@
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const villageTable = await queryInterface.sequelize.query(
|
||||
"SELECT to_regclass('public.villages') AS table_name;",
|
||||
{ transaction, type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!villageTable[0]?.table_name) {
|
||||
await queryInterface.createTable(
|
||||
'type_usages',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
tarif: {
|
||||
type: Sequelize.DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
actif: {
|
||||
type: Sequelize.DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
description: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.addIndex('type_usages', ['actif'], {
|
||||
name: 'type_usages_actif_idx',
|
||||
transaction,
|
||||
});
|
||||
|
||||
await queryInterface.createTable(
|
||||
'villages',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
commune: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
},
|
||||
arrondissement: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
},
|
||||
departement: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
},
|
||||
region: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
},
|
||||
duree_periode_jours: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
},
|
||||
notes: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'quartiers',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
notes: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
},
|
||||
villageId: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
allowNull: false,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'villages',
|
||||
},
|
||||
onDelete: 'RESTRICT',
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.addConstraint('quartiers', {
|
||||
fields: ['nom', 'villageId'],
|
||||
type: 'unique',
|
||||
name: 'quartiers_nom_village_unique',
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const villageTable = await queryInterface.sequelize.query(
|
||||
"SELECT to_regclass('public.villages') AS table_name;",
|
||||
{ transaction, type: Sequelize.QueryTypes.SELECT },
|
||||
);
|
||||
|
||||
if (!villageTable[0]?.table_name) {
|
||||
await transaction.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
await queryInterface.dropTable('quartiers', { transaction });
|
||||
await queryInterface.dropTable('villages', { transaction });
|
||||
await queryInterface.dropTable('type_usages', { transaction });
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
50
backend/src/db/models/quartiers.js
Normal file
50
backend/src/db/models/quartiers.js
Normal file
@ -0,0 +1,50 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
const quartiers = sequelize.define(
|
||||
'quartiers',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
quartiers.associate = (db) => {
|
||||
db.quartiers.belongsTo(db.villages, {
|
||||
as: 'village',
|
||||
foreignKey: {
|
||||
name: 'villageId',
|
||||
allowNull: false,
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.quartiers.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.quartiers.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return quartiers;
|
||||
};
|
||||
51
backend/src/db/models/type_usages.js
Normal file
51
backend/src/db/models/type_usages.js
Normal file
@ -0,0 +1,51 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
const type_usages = sequelize.define(
|
||||
'type_usages',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
},
|
||||
tarif: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
actif: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
type_usages.associate = (db) => {
|
||||
db.type_usages.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.type_usages.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return type_usages;
|
||||
};
|
||||
62
backend/src/db/models/villages.js
Normal file
62
backend/src/db/models/villages.js
Normal file
@ -0,0 +1,62 @@
|
||||
module.exports = function(sequelize, DataTypes) {
|
||||
const villages = sequelize.define(
|
||||
'villages',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
nom: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
commune: {
|
||||
type: DataTypes.STRING(100),
|
||||
},
|
||||
arrondissement: {
|
||||
type: DataTypes.STRING(100),
|
||||
},
|
||||
departement: {
|
||||
type: DataTypes.STRING(100),
|
||||
},
|
||||
region: {
|
||||
type: DataTypes.STRING(100),
|
||||
},
|
||||
duree_periode_jours: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
villages.associate = (db) => {
|
||||
db.villages.hasMany(db.quartiers, {
|
||||
as: 'quartiers',
|
||||
foreignKey: 'villageId',
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.villages.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.villages.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return villages;
|
||||
};
|
||||
@ -0,0 +1,115 @@
|
||||
const { QueryTypes } = require('sequelize');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const createdAt = new Date();
|
||||
const updatedAt = new Date();
|
||||
const entities = ['type_usages', 'villages', 'quartiers'];
|
||||
const actions = ['CREATE', 'READ', 'UPDATE', 'DELETE'];
|
||||
|
||||
const roles = await queryInterface.sequelize.query(
|
||||
'SELECT id, name FROM roles WHERE name IN (:roles);',
|
||||
{
|
||||
replacements: { roles: ['Administrator', 'Platform Owner', 'Product Manager'] },
|
||||
type: QueryTypes.SELECT,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
for (const entity of entities) {
|
||||
for (const action of actions) {
|
||||
const name = `${action}_${entity.toUpperCase()}`;
|
||||
const existing = await queryInterface.sequelize.query(
|
||||
'SELECT id FROM permissions WHERE name = :name LIMIT 1;',
|
||||
{
|
||||
replacements: { name },
|
||||
type: QueryTypes.SELECT,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
let permissionId = existing[0]?.id;
|
||||
|
||||
if (!permissionId) {
|
||||
permissionId = uuid();
|
||||
await queryInterface.bulkInsert(
|
||||
'permissions',
|
||||
[{ id: permissionId, name, createdAt, updatedAt }],
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
for (const role of roles) {
|
||||
const link = await queryInterface.sequelize.query(
|
||||
'SELECT 1 FROM "rolesPermissionsPermissions" WHERE "roles_permissionsId" = :roleId AND "permissionId" = :permissionId LIMIT 1;',
|
||||
{
|
||||
replacements: { roleId: role.id, permissionId },
|
||||
type: QueryTypes.SELECT,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
if (!link.length) {
|
||||
await queryInterface.bulkInsert(
|
||||
'rolesPermissionsPermissions',
|
||||
[{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: role.id,
|
||||
permissionId,
|
||||
}],
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const permissions = await queryInterface.sequelize.query(
|
||||
'SELECT id FROM permissions WHERE name LIKE ANY (ARRAY[:patterns]);',
|
||||
{
|
||||
replacements: {
|
||||
patterns: ['%TYPE_USAGES', '%VILLAGES', '%QUARTIERS'],
|
||||
},
|
||||
type: QueryTypes.SELECT,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
const ids = permissions.map((item) => item.id);
|
||||
|
||||
if (ids.length) {
|
||||
await queryInterface.bulkDelete(
|
||||
'rolesPermissionsPermissions',
|
||||
{ permissionId: ids },
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.bulkDelete(
|
||||
'permissions',
|
||||
{ id: ids },
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -36,6 +36,9 @@ const messagesRoutes = require('./routes/messages');
|
||||
const audit_logsRoutes = require('./routes/audit_logs');
|
||||
|
||||
const app_settingsRoutes = require('./routes/app_settings');
|
||||
const villagesRoutes = require('./routes/villages');
|
||||
const quartiersRoutes = require('./routes/quartiers');
|
||||
const type_usagesRoutes = require('./routes/type_usages');
|
||||
|
||||
|
||||
const getBaseUrl = (url) => {
|
||||
@ -110,6 +113,9 @@ app.use('/api/messages', passport.authenticate('jwt', {session: false}), message
|
||||
app.use('/api/audit_logs', passport.authenticate('jwt', {session: false}), audit_logsRoutes);
|
||||
|
||||
app.use('/api/app_settings', passport.authenticate('jwt', {session: false}), app_settingsRoutes);
|
||||
app.use('/api/villages', passport.authenticate('jwt', {session: false}), villagesRoutes);
|
||||
app.use('/api/quartiers', passport.authenticate('jwt', {session: false}), quartiersRoutes);
|
||||
app.use('/api/type_usages', passport.authenticate('jwt', {session: false}), type_usagesRoutes);
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
|
||||
78
backend/src/routes/quartiers.js
Normal file
78
backend/src/routes/quartiers.js
Normal file
@ -0,0 +1,78 @@
|
||||
const express = require('express');
|
||||
const QuartiersService = require('../services/quartiers');
|
||||
const QuartiersDBApi = require('../db/api/quartiers');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { parse } = require('json2csv');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(checkCrudPermissions('quartiers'));
|
||||
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
await QuartiersService.create(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
await QuartiersService.bulkImport(req, res);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await QuartiersService.update(req.body.data, req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await QuartiersService.remove(req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await QuartiersService.deleteByIds(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
const payload = await QuartiersDBApi.findAll(req.query, { currentUser: req.currentUser });
|
||||
|
||||
if (filetype === 'csv') {
|
||||
const fields = ['id', 'nom', 'notes', 'villageId'];
|
||||
const csv = parse(payload.rows, { fields });
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
const payload = await QuartiersDBApi.findAll(req.query, {
|
||||
countOnly: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/autocomplete', wrapAsync(async (req, res) => {
|
||||
const payload = await QuartiersDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await QuartiersDBApi.findBy({ id: req.params.id });
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
78
backend/src/routes/type_usages.js
Normal file
78
backend/src/routes/type_usages.js
Normal file
@ -0,0 +1,78 @@
|
||||
const express = require('express');
|
||||
const Type_usagesService = require('../services/type_usages');
|
||||
const Type_usagesDBApi = require('../db/api/type_usages');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { parse } = require('json2csv');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(checkCrudPermissions('type_usages'));
|
||||
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
await Type_usagesService.create(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
await Type_usagesService.bulkImport(req, res);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await Type_usagesService.update(req.body.data, req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await Type_usagesService.remove(req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await Type_usagesService.deleteByIds(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
const payload = await Type_usagesDBApi.findAll(req.query, { currentUser: req.currentUser });
|
||||
|
||||
if (filetype === 'csv') {
|
||||
const fields = ['id', 'nom', 'tarif', 'actif', 'description'];
|
||||
const csv = parse(payload.rows, { fields });
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
const payload = await Type_usagesDBApi.findAll(req.query, {
|
||||
countOnly: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/autocomplete', wrapAsync(async (req, res) => {
|
||||
const payload = await Type_usagesDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Type_usagesDBApi.findBy({ id: req.params.id });
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
78
backend/src/routes/villages.js
Normal file
78
backend/src/routes/villages.js
Normal file
@ -0,0 +1,78 @@
|
||||
const express = require('express');
|
||||
const VillagesService = require('../services/villages');
|
||||
const VillagesDBApi = require('../db/api/villages');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const { parse } = require('json2csv');
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(checkCrudPermissions('villages'));
|
||||
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
await VillagesService.create(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
await VillagesService.bulkImport(req, res);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await VillagesService.update(req.body.data, req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await VillagesService.remove(req.params.id, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await VillagesService.deleteByIds(req.body.data, req.currentUser);
|
||||
res.status(200).send(true);
|
||||
}));
|
||||
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
const payload = await VillagesDBApi.findAll(req.query, { currentUser: req.currentUser });
|
||||
|
||||
if (filetype === 'csv') {
|
||||
const fields = ['id', 'nom', 'commune', 'arrondissement', 'departement', 'region', 'duree_periode_jours', 'notes'];
|
||||
const csv = parse(payload.rows, { fields });
|
||||
res.status(200).attachment(csv);
|
||||
res.send(csv);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
const payload = await VillagesDBApi.findAll(req.query, {
|
||||
countOnly: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/autocomplete', wrapAsync(async (req, res) => {
|
||||
const payload = await VillagesDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await VillagesDBApi.findBy({ id: req.params.id });
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
111
backend/src/services/quartiers.js
Normal file
111
backend/src/services/quartiers.js
Normal file
@ -0,0 +1,111 @@
|
||||
const db = require('../db/models');
|
||||
const QuartiersDBApi = require('../db/api/quartiers');
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const stream = require('stream');
|
||||
|
||||
module.exports = class QuartiersService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await QuartiersDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async bulkImport(req, res) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await processFile(req, res);
|
||||
const bufferStream = new stream.PassThrough();
|
||||
const results = [];
|
||||
|
||||
await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8'));
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
bufferStream
|
||||
.pipe(csv())
|
||||
.on('data', (data) => results.push(data))
|
||||
.on('end', async () => resolve())
|
||||
.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
await QuartiersDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async update(data, id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const existing = await QuartiersDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!existing) {
|
||||
throw new ValidationError('quartiersNotFound');
|
||||
}
|
||||
|
||||
const updated = await QuartiersDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updated;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await QuartiersDBApi.deleteByIds(ids, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async remove(id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await QuartiersDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
111
backend/src/services/type_usages.js
Normal file
111
backend/src/services/type_usages.js
Normal file
@ -0,0 +1,111 @@
|
||||
const db = require('../db/models');
|
||||
const Type_usagesDBApi = require('../db/api/type_usages');
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const stream = require('stream');
|
||||
|
||||
module.exports = class Type_usagesService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await Type_usagesDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async bulkImport(req, res) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await processFile(req, res);
|
||||
const bufferStream = new stream.PassThrough();
|
||||
const results = [];
|
||||
|
||||
await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8'));
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
bufferStream
|
||||
.pipe(csv())
|
||||
.on('data', (data) => results.push(data))
|
||||
.on('end', async () => resolve())
|
||||
.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
await Type_usagesDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async update(data, id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const existing = await Type_usagesDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!existing) {
|
||||
throw new ValidationError('type_usagesNotFound');
|
||||
}
|
||||
|
||||
const updated = await Type_usagesDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updated;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await Type_usagesDBApi.deleteByIds(ids, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async remove(id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await Type_usagesDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
111
backend/src/services/villages.js
Normal file
111
backend/src/services/villages.js
Normal file
@ -0,0 +1,111 @@
|
||||
const db = require('../db/models');
|
||||
const VillagesDBApi = require('../db/api/villages');
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const stream = require('stream');
|
||||
|
||||
module.exports = class VillagesService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await VillagesDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async bulkImport(req, res) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await processFile(req, res);
|
||||
const bufferStream = new stream.PassThrough();
|
||||
const results = [];
|
||||
|
||||
await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8'));
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
bufferStream
|
||||
.pipe(csv())
|
||||
.on('data', (data) => results.push(data))
|
||||
.on('end', async () => resolve())
|
||||
.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
await VillagesDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
currentUser: req.currentUser,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async update(data, id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
const existing = await VillagesDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!existing) {
|
||||
throw new ValidationError('villagesNotFound');
|
||||
}
|
||||
|
||||
const updated = await VillagesDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updated;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await VillagesDBApi.deleteByIds(ids, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async remove(id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await VillagesDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
frontend/public/assets/vm-shot-2026-03-29T05-06-17-032Z.jpg
Normal file
BIN
frontend/public/assets/vm-shot-2026-03-29T05-06-17-032Z.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@ -1,6 +1,5 @@
|
||||
import React, {useEffect, useRef} from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||
import BaseDivider from './BaseDivider'
|
||||
import BaseIcon from './BaseIcon'
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React, { ReactNode, useEffect } from 'react'
|
||||
import { useState } from 'react'
|
||||
import React, { ReactNode, useEffect, useState } from 'react'
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
|
||||
import menuAside from '../menuAside'
|
||||
|
||||
@ -72,6 +72,14 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: 'mdiCogOutline' in icon ? icon['mdiCogOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_APP_SETTINGS'
|
||||
},
|
||||
{
|
||||
href: '/g-forage/referentials',
|
||||
label: 'Référentiels forage',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: 'mdiDatabaseCogOutline' in icon ? icon['mdiDatabaseCogOutline' as keyof typeof icon] : icon.mdiTable,
|
||||
permissions: 'READ_VILLAGES'
|
||||
},
|
||||
{
|
||||
href: '/profile',
|
||||
label: 'Profile',
|
||||
|
||||
191
frontend/src/pages/g-forage/referentials.tsx
Normal file
191
frontend/src/pages/g-forage/referentials.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { mdiTable } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
|
||||
type ItemRow = {
|
||||
id: string;
|
||||
nom: string;
|
||||
commune?: string;
|
||||
tarif?: number | string;
|
||||
actif?: boolean;
|
||||
village?: { nom?: string };
|
||||
};
|
||||
|
||||
const ReferentialsPage = () => {
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [stats, setStats] = useState({ villages: 0, quartiers: 0, typeUsages: 0 });
|
||||
const [villages, setVillages] = useState<ItemRow[]>([]);
|
||||
const [quartiers, setQuartiers] = useState<ItemRow[]>([]);
|
||||
const [typeUsages, setTypeUsages] = useState<ItemRow[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
const [villagesCount, quartiersCount, typeUsagesCount, villagesRows, quartiersRows, typeUsagesRows] = await Promise.all([
|
||||
axios.get('/villages/count'),
|
||||
axios.get('/quartiers/count'),
|
||||
axios.get('/type_usages/count'),
|
||||
axios.get('/villages', { params: { page: 0, limit: 5 } }),
|
||||
axios.get('/quartiers', { params: { page: 0, limit: 5 } }),
|
||||
axios.get('/type_usages', { params: { page: 0, limit: 5 } }),
|
||||
]);
|
||||
|
||||
setStats({
|
||||
villages: villagesCount.data.count ?? 0,
|
||||
quartiers: quartiersCount.data.count ?? 0,
|
||||
typeUsages: typeUsagesCount.data.count ?? 0,
|
||||
});
|
||||
setVillages(villagesRows.data.rows ?? []);
|
||||
setQuartiers(quartiersRows.data.rows ?? []);
|
||||
setTypeUsages(typeUsagesRows.data.rows ?? []);
|
||||
} catch (err) {
|
||||
console.error('Failed to load G-Forage referentials:', err);
|
||||
setError('Impossible de charger les référentiels G-Forage pour le moment.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [currentUser]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Référentiels G-Forage')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiTable}
|
||||
title='Référentiels G-Forage'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
||||
<CardBox>
|
||||
<div className='text-sm text-gray-500 dark:text-gray-400'>Villages</div>
|
||||
<div className='text-3xl font-semibold mt-2'>{loading ? '…' : stats.villages}</div>
|
||||
</CardBox>
|
||||
<CardBox>
|
||||
<div className='text-sm text-gray-500 dark:text-gray-400'>Quartiers</div>
|
||||
<div className='text-3xl font-semibold mt-2'>{loading ? '…' : stats.quartiers}</div>
|
||||
</CardBox>
|
||||
<CardBox>
|
||||
<div className='text-sm text-gray-500 dark:text-gray-400'>Types d’usage</div>
|
||||
<div className='text-3xl font-semibold mt-2'>{loading ? '…' : stats.typeUsages}</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<CardBox className='mb-6'>
|
||||
<p className='text-red-600 dark:text-red-400'>{error}</p>
|
||||
</CardBox>
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 xl:grid-cols-3'>
|
||||
<CardBox>
|
||||
<h3 className='text-lg font-semibold mb-4'>Derniers villages</h3>
|
||||
<table className='w-full text-sm'>
|
||||
<thead>
|
||||
<tr className='text-left border-b'>
|
||||
<th className='py-2'>Nom</th>
|
||||
<th className='py-2'>Commune</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{villages.map((item) => (
|
||||
<tr key={item.id} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{item.nom}</td>
|
||||
<td className='py-2'>{item.commune || '—'}</td>
|
||||
</tr>
|
||||
))}
|
||||
{!loading && villages.length === 0 && (
|
||||
<tr>
|
||||
<td className='py-2 text-gray-500' colSpan={2}>Aucun village pour le moment.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
|
||||
<CardBox>
|
||||
<h3 className='text-lg font-semibold mb-4'>Derniers quartiers</h3>
|
||||
<table className='w-full text-sm'>
|
||||
<thead>
|
||||
<tr className='text-left border-b'>
|
||||
<th className='py-2'>Nom</th>
|
||||
<th className='py-2'>Village</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{quartiers.map((item) => (
|
||||
<tr key={item.id} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{item.nom}</td>
|
||||
<td className='py-2'>{item.village?.nom || '—'}</td>
|
||||
</tr>
|
||||
))}
|
||||
{!loading && quartiers.length === 0 && (
|
||||
<tr>
|
||||
<td className='py-2 text-gray-500' colSpan={2}>Aucun quartier pour le moment.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
|
||||
<CardBox>
|
||||
<h3 className='text-lg font-semibold mb-4'>Types d’usage</h3>
|
||||
<table className='w-full text-sm'>
|
||||
<thead>
|
||||
<tr className='text-left border-b'>
|
||||
<th className='py-2'>Nom</th>
|
||||
<th className='py-2'>Tarif</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{typeUsages.map((item) => (
|
||||
<tr key={item.id} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{item.nom}</td>
|
||||
<td className='py-2'>{item.tarif ?? 0} F</td>
|
||||
</tr>
|
||||
))}
|
||||
{!loading && typeUsages.length === 0 && (
|
||||
<tr>
|
||||
<td className='py-2 text-gray-500' colSpan={2}>Aucun type d’usage pour le moment.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReferentialsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission='READ_VILLAGES'>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReferentialsPage;
|
||||
Loading…
x
Reference in New Issue
Block a user