Compare commits

..

1 Commits

Author SHA1 Message Date
Flatlogic Bot
3e9432e89e v1 2026-01-12 15:05:22 +00:00
252 changed files with 41165 additions and 4225 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/backend/node_modules
/frontend/node_modules
node_modules/
*/node_modules/
**/node_modules/
*/build/

View File

@ -129,8 +129,8 @@
<p class="tip">The application is currently launching. The page will automatically refresh once site is
available.</p>
<div class="project-info">
<h2>StoreOps Manager</h2>
<p>Manage products, inventory, orders, customers, and payments with Admin and Staff roles.</p>
<h2>Store Operations Manager</h2>
<p>Store Operations Manager for catalog and full order lifecycle with products, customers, payments, and reporting.</p>
</div>
<div class="loader-container">
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"

View File

@ -1,6 +1,6 @@
# StoreOps Manager
# Store Operations Manager
## This project was generated by [Flatlogic Platform](https://flatlogic.com).

View File

@ -1,6 +1,6 @@
DB_NAME=app_37379
DB_USER=app_37379
DB_PASS=fc0de3ba-4aee-4507-88c3-64c04c521f22
DB_NAME=app_37384
DB_USER=app_37384
DB_PASS=4df3d09c-78bc-4451-8952-4e97b44f02af
DB_HOST=127.0.0.1
DB_PORT=5432
PORT=3000

View File

@ -1,5 +1,5 @@
#StoreOps Manager - template backend,
#Store Operations Manager - template backend,
#### Run App on local machine:
@ -30,10 +30,10 @@
- `psql postgres -U admin`
- Type this command to creating a new database.
- `postgres=> CREATE DATABASE db_storeops_manager;`
- `postgres=> CREATE DATABASE db_store_operations_manager;`
- Then give that new user privileges to the new database then quit the `psql`.
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_storeops_manager TO admin;`
- `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_store_operations_manager TO admin;`
- `postgres=> \q`
------------

View File

@ -1,6 +1,6 @@
{
"name": "storeopsmanager",
"description": "StoreOps Manager - template backend",
"name": "storeoperationsmanager",
"description": "Store Operations Manager - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",

View File

@ -11,15 +11,15 @@ const config = {
bcrypt: {
saltRounds: 12
},
admin_pass: "fc0de3ba",
user_pass: "64c04c521f22",
admin_pass: "4df3d09c",
user_pass: "4e97b44f02af",
admin_email: "admin@flatlogic.com",
providers: {
LOCAL: 'local',
GOOGLE: 'google',
MICROSOFT: 'microsoft'
},
secret_key: process.env.SECRET_KEY || 'fc0de3ba-4aee-4507-88c3-64c04c521f22',
secret_key: process.env.SECRET_KEY || '4df3d09c-78bc-4451-8952-4e97b44f02af',
remote: '',
port: process.env.NODE_ENV === "production" ? "" : "8080",
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
@ -39,7 +39,7 @@ const config = {
},
uploadDir: os.tmpdir(),
email: {
from: 'StoreOps Manager <app@flatlogic.app>',
from: 'Store Operations Manager <app@flatlogic.app>',
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
@ -60,7 +60,7 @@ const config = {
},
project_uuid: 'fc0de3ba-4aee-4507-88c3-64c04c521f22',
project_uuid: '4df3d09c-78bc-4451-8952-4e97b44f02af',
flHost: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects',
@ -69,7 +69,7 @@ const config = {
config.pexelsKey = process.env.PEXELS_KEY || '';
config.pexelsQuery = 'sunrise over calm ocean with birds';
config.pexelsQuery = 'compass on vintage map';
config.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost";
config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`;
config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`;

View File

@ -195,6 +195,8 @@ module.exports = class CategoriesDBApi {
output.products_category = await categories.getProducts_category({
transaction
});
@ -208,6 +210,7 @@ module.exports = class CategoriesDBApi {
output.parent = await categories.getParent({
transaction
});

View File

@ -36,12 +36,12 @@ module.exports = class CustomersDBApi {
null
,
billing_address: data.billing_address
company: data.company
||
null
,
shipping_address: data.shipping_address
address: data.address
||
null
,
@ -51,10 +51,9 @@ module.exports = class CustomersDBApi {
null
,
vip: data.vip
loyalty_points: data.loyalty_points
||
false
null
,
importHash: data.importHash || null,
@ -97,12 +96,12 @@ module.exports = class CustomersDBApi {
null
,
billing_address: item.billing_address
company: item.company
||
null
,
shipping_address: item.shipping_address
address: item.address
||
null
,
@ -112,10 +111,9 @@ module.exports = class CustomersDBApi {
null
,
vip: item.vip
loyalty_points: item.loyalty_points
||
false
null
,
importHash: item.importHash || null,
@ -154,16 +152,16 @@ module.exports = class CustomersDBApi {
if (data.phone !== undefined) updatePayload.phone = data.phone;
if (data.billing_address !== undefined) updatePayload.billing_address = data.billing_address;
if (data.company !== undefined) updatePayload.company = data.company;
if (data.shipping_address !== undefined) updatePayload.shipping_address = data.shipping_address;
if (data.address !== undefined) updatePayload.address = data.address;
if (data.notes !== undefined) updatePayload.notes = data.notes;
if (data.vip !== undefined) updatePayload.vip = data.vip;
if (data.loyalty_points !== undefined) updatePayload.loyalty_points = data.loyalty_points;
updatePayload.updatedById = currentUser.id;
@ -252,6 +250,9 @@ module.exports = class CustomersDBApi {
output.orders_customer = await customers.getOrders_customer({
transaction
});
@ -333,24 +334,24 @@ module.exports = class CustomersDBApi {
};
}
if (filter.billing_address) {
if (filter.company) {
where = {
...where,
[Op.and]: Utils.ilike(
'customers',
'billing_address',
filter.billing_address,
'company',
filter.company,
),
};
}
if (filter.shipping_address) {
if (filter.address) {
where = {
...where,
[Op.and]: Utils.ilike(
'customers',
'shipping_address',
filter.shipping_address,
'address',
filter.address,
),
};
}
@ -371,6 +372,30 @@ module.exports = class CustomersDBApi {
if (filter.loyalty_pointsRange) {
const [start, end] = filter.loyalty_pointsRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
loyalty_points: {
...where.loyalty_points,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
loyalty_points: {
...where.loyalty_points,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
@ -380,13 +405,6 @@ module.exports = class CustomersDBApi {
}
if (filter.vip) {
where = {
...where,
vip: filter.vip,
};
}

View File

@ -0,0 +1,519 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class DiscountsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const discounts = await db.discounts.create(
{
id: data.id || undefined,
code: data.code
||
null
,
description: data.description
||
null
,
type: data.type
||
null
,
value: data.value
||
null
,
active: data.active
||
false
,
starts: data.starts
||
null
,
ends: data.ends
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return discounts;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const discountsData = data.map((item, index) => ({
id: item.id || undefined,
code: item.code
||
null
,
description: item.description
||
null
,
type: item.type
||
null
,
value: item.value
||
null
,
active: item.active
||
false
,
starts: item.starts
||
null
,
ends: item.ends
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const discounts = await db.discounts.bulkCreate(discountsData, { transaction });
// For each item created, replace relation files
return discounts;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const discounts = await db.discounts.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.code !== undefined) updatePayload.code = data.code;
if (data.description !== undefined) updatePayload.description = data.description;
if (data.type !== undefined) updatePayload.type = data.type;
if (data.value !== undefined) updatePayload.value = data.value;
if (data.active !== undefined) updatePayload.active = data.active;
if (data.starts !== undefined) updatePayload.starts = data.starts;
if (data.ends !== undefined) updatePayload.ends = data.ends;
updatePayload.updatedById = currentUser.id;
await discounts.update(updatePayload, {transaction});
return discounts;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const discounts = await db.discounts.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of discounts) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of discounts) {
await record.destroy({transaction});
}
});
return discounts;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const discounts = await db.discounts.findByPk(id, options);
await discounts.update({
deletedBy: currentUser.id
}, {
transaction,
});
await discounts.destroy({
transaction
});
return discounts;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const discounts = await db.discounts.findOne(
{ where },
{ transaction },
);
if (!discounts) {
return discounts;
}
const output = discounts.get({plain: true});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.code) {
where = {
...where,
[Op.and]: Utils.ilike(
'discounts',
'code',
filter.code,
),
};
}
if (filter.description) {
where = {
...where,
[Op.and]: Utils.ilike(
'discounts',
'description',
filter.description,
),
};
}
if (filter.valueRange) {
const [start, end] = filter.valueRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
value: {
...where.value,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
value: {
...where.value,
[Op.lte]: end,
},
};
}
}
if (filter.startsRange) {
const [start, end] = filter.startsRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
starts: {
...where.starts,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
starts: {
...where.starts,
[Op.lte]: end,
},
};
}
}
if (filter.endsRange) {
const [start, end] = filter.endsRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
ends: {
...where.ends,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
ends: {
...where.ends,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.type) {
where = {
...where,
type: filter.type,
};
}
if (filter.active) {
where = {
...where,
active: filter.active,
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.discounts.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'discounts',
'code',
query,
),
],
};
}
const records = await db.discounts.findAll({
attributes: [ 'id', 'code' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['code', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.code,
}));
}
};

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class InventoryDBApi {
module.exports = class Inventory_movementsDBApi {
@ -17,27 +17,27 @@ module.exports = class InventoryDBApi {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const inventory = await db.inventory.create(
const inventory_movements = await db.inventory_movements.create(
{
id: data.id || undefined,
quantity: data.quantity
||
null
,
location: data.location
||
null
,
change_type: data.change_type
||
null
,
note: data.note
||
null
,
change: data.change
||
null
,
reason: data.reason
||
null
,
date: data.date
||
null
,
@ -49,7 +49,7 @@ module.exports = class InventoryDBApi {
);
await inventory.setVariant( data.variant || null, {
await inventory_movements.setProduct( data.product || null, {
transaction,
});
@ -58,7 +58,7 @@ module.exports = class InventoryDBApi {
return inventory;
return inventory_movements;
}
@ -67,27 +67,27 @@ module.exports = class InventoryDBApi {
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const inventoryData = data.map((item, index) => ({
const inventory_movementsData = data.map((item, index) => ({
id: item.id || undefined,
quantity: item.quantity
||
null
,
location: item.location
||
null
,
change_type: item.change_type
||
null
,
note: item.note
||
null
,
change: item.change
||
null
,
reason: item.reason
||
null
,
date: item.date
||
null
,
importHash: item.importHash || null,
@ -97,12 +97,12 @@ module.exports = class InventoryDBApi {
}));
// Bulk create items
const inventory = await db.inventory.bulkCreate(inventoryData, { transaction });
const inventory_movements = await db.inventory_movements.bulkCreate(inventory_movementsData, { transaction });
// For each item created, replace relation files
return inventory;
return inventory_movements;
}
static async update(id, data, options) {
@ -110,35 +110,35 @@ module.exports = class InventoryDBApi {
const transaction = (options && options.transaction) || undefined;
const inventory = await db.inventory.findByPk(id, {}, {transaction});
const inventory_movements = await db.inventory_movements.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.quantity !== undefined) updatePayload.quantity = data.quantity;
if (data.location !== undefined) updatePayload.location = data.location;
if (data.change_type !== undefined) updatePayload.change_type = data.change_type;
if (data.note !== undefined) updatePayload.note = data.note;
if (data.change !== undefined) updatePayload.change = data.change;
if (data.reason !== undefined) updatePayload.reason = data.reason;
if (data.date !== undefined) updatePayload.date = data.date;
updatePayload.updatedById = currentUser.id;
await inventory.update(updatePayload, {transaction});
await inventory_movements.update(updatePayload, {transaction});
if (data.variant !== undefined) {
await inventory.setVariant(
if (data.product !== undefined) {
await inventory_movements.setProduct(
data.variant,
data.product,
{ transaction }
);
@ -150,14 +150,14 @@ module.exports = class InventoryDBApi {
return inventory;
return inventory_movements;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const inventory = await db.inventory.findAll({
const inventory_movements = await db.inventory_movements.findAll({
where: {
id: {
[Op.in]: ids,
@ -167,53 +167,53 @@ module.exports = class InventoryDBApi {
});
await db.sequelize.transaction(async (transaction) => {
for (const record of inventory) {
for (const record of inventory_movements) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of inventory) {
for (const record of inventory_movements) {
await record.destroy({transaction});
}
});
return inventory;
return inventory_movements;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const inventory = await db.inventory.findByPk(id, options);
const inventory_movements = await db.inventory_movements.findByPk(id, options);
await inventory.update({
await inventory_movements.update({
deletedBy: currentUser.id
}, {
transaction,
});
await inventory.destroy({
await inventory_movements.destroy({
transaction
});
return inventory;
return inventory_movements;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const inventory = await db.inventory.findOne(
const inventory_movements = await db.inventory_movements.findOne(
{ where },
{ transaction },
);
if (!inventory) {
return inventory;
if (!inventory_movements) {
return inventory_movements;
}
const output = inventory.get({plain: true});
const output = inventory_movements.get({plain: true});
@ -230,7 +230,10 @@ module.exports = class InventoryDBApi {
output.variant = await inventory.getVariant({
output.product = await inventory_movements.getProduct({
transaction
});
@ -261,15 +264,15 @@ module.exports = class InventoryDBApi {
let include = [
{
model: db.product_variants,
as: 'variant',
model: db.products,
as: 'product',
where: filter.variant ? {
where: filter.product ? {
[Op.or]: [
{ id: { [Op.in]: filter.variant.split('|').map(term => Utils.uuid(term)) } },
{ id: { [Op.in]: filter.product.split('|').map(term => Utils.uuid(term)) } },
{
sku: {
[Op.or]: filter.variant.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
name: {
[Op.or]: filter.product.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
@ -290,22 +293,11 @@ module.exports = class InventoryDBApi {
}
if (filter.location) {
where = {
...where,
[Op.and]: Utils.ilike(
'inventory',
'location',
filter.location,
),
};
}
if (filter.note) {
where = {
...where,
[Op.and]: Utils.ilike(
'inventory',
'inventory_movements',
'note',
filter.note,
),
@ -317,14 +309,14 @@ module.exports = class InventoryDBApi {
if (filter.quantityRange) {
const [start, end] = filter.quantityRange;
if (filter.changeRange) {
const [start, end] = filter.changeRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
quantity: {
...where.quantity,
change: {
...where.change,
[Op.gte]: start,
},
};
@ -333,8 +325,32 @@ module.exports = class InventoryDBApi {
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
quantity: {
...where.quantity,
change: {
...where.change,
[Op.lte]: end,
},
};
}
}
if (filter.dateRange) {
const [start, end] = filter.dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
date: {
...where.date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
date: {
...where.date,
[Op.lte]: end,
},
};
@ -350,10 +366,10 @@ module.exports = class InventoryDBApi {
}
if (filter.change_type) {
if (filter.reason) {
where = {
...where,
change_type: filter.change_type,
reason: filter.reason,
};
}
@ -408,7 +424,7 @@ module.exports = class InventoryDBApi {
}
try {
const { rows, count } = await db.inventory.findAndCountAll(queryOptions);
const { rows, count } = await db.inventory_movements.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
@ -430,25 +446,25 @@ module.exports = class InventoryDBApi {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'inventory',
'location',
'inventory_movements',
'note',
query,
),
],
};
}
const records = await db.inventory.findAll({
attributes: [ 'id', 'location' ],
const records = await db.inventory_movements.findAll({
attributes: [ 'id', 'note' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['location', 'ASC']],
orderBy: [['note', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.location,
label: record.note,
}));
}

View File

@ -21,7 +21,7 @@ module.exports = class Order_itemsDBApi {
{
id: data.id || undefined,
name: data.name
product_name: data.product_name
||
null
,
@ -38,6 +38,11 @@ module.exports = class Order_itemsDBApi {
total: data.total
||
null
,
sku: data.sku
||
null
,
@ -49,11 +54,7 @@ module.exports = class Order_itemsDBApi {
);
await order_items.setOrder( data.order || null, {
transaction,
});
await order_items.setProduct_variant( data.product_variant || null, {
await order_items.setProduct( data.product || null, {
transaction,
});
@ -74,7 +75,7 @@ module.exports = class Order_itemsDBApi {
const order_itemsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name
product_name: item.product_name
||
null
,
@ -92,6 +93,11 @@ module.exports = class Order_itemsDBApi {
total: item.total
||
null
,
sku: item.sku
||
null
,
importHash: item.importHash || null,
@ -121,7 +127,7 @@ module.exports = class Order_itemsDBApi {
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
if (data.product_name !== undefined) updatePayload.product_name = data.product_name;
if (data.quantity !== undefined) updatePayload.quantity = data.quantity;
@ -133,25 +139,19 @@ module.exports = class Order_itemsDBApi {
if (data.total !== undefined) updatePayload.total = data.total;
if (data.sku !== undefined) updatePayload.sku = data.sku;
updatePayload.updatedById = currentUser.id;
await order_items.update(updatePayload, {transaction});
if (data.order !== undefined) {
await order_items.setOrder(
if (data.product !== undefined) {
await order_items.setProduct(
data.order,
{ transaction }
);
}
if (data.product_variant !== undefined) {
await order_items.setProduct_variant(
data.product_variant,
data.product,
{ transaction }
);
@ -243,12 +243,10 @@ module.exports = class Order_itemsDBApi {
output.order = await order_items.getOrder({
transaction
});
output.product_variant = await order_items.getProduct_variant({
output.product = await order_items.getProduct({
transaction
});
@ -279,32 +277,15 @@ module.exports = class Order_itemsDBApi {
let include = [
{
model: db.orders,
as: 'order',
model: db.products,
as: 'product',
where: filter.order ? {
where: filter.product ? {
[Op.or]: [
{ id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } },
{ id: { [Op.in]: filter.product.split('|').map(term => Utils.uuid(term)) } },
{
order_number: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.product_variants,
as: 'product_variant',
where: filter.product_variant ? {
[Op.or]: [
{ id: { [Op.in]: filter.product_variant.split('|').map(term => Utils.uuid(term)) } },
{
sku: {
[Op.or]: filter.product_variant.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
name: {
[Op.or]: filter.product.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
@ -325,13 +306,24 @@ module.exports = class Order_itemsDBApi {
}
if (filter.name) {
if (filter.product_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'order_items',
'name',
filter.name,
'product_name',
filter.product_name,
),
};
}
if (filter.sku) {
where = {
...where,
[Op.and]: Utils.ilike(
'order_items',
'sku',
filter.sku,
),
};
}
@ -428,8 +420,6 @@ module.exports = class Order_itemsDBApi {
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
@ -498,7 +488,7 @@ module.exports = class Order_itemsDBApi {
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'order_items',
'name',
'product_name',
query,
),
],
@ -506,16 +496,16 @@ module.exports = class Order_itemsDBApi {
}
const records = await db.order_items.findAll({
attributes: [ 'id', 'name' ],
attributes: [ 'id', 'product_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
orderBy: [['product_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
label: record.product_name,
}));
}

View File

@ -31,28 +31,43 @@ module.exports = class OrdersDBApi {
null
,
order_date: data.order_date
||
null
,
delivery_date: data.delivery_date
||
null
,
subtotal: data.subtotal
||
null
,
tax: data.tax
||
null
,
shipping_fee: data.shipping_fee
||
null
,
total: data.total
||
null
,
placed_at: data.placed_at
||
null
,
shipped_at: data.shipped_at
||
null
,
payment_status: data.payment_status
||
null
,
shipping_address: data.shipping_address
||
null
,
billing_address: data.billing_address
||
null
,
@ -68,9 +83,25 @@ module.exports = class OrdersDBApi {
transaction,
});
await orders.setAssigned_to( data.assigned_to || null, {
transaction,
});
await orders.setItems(data.items || [], {
transaction,
});
await orders.setPayments(data.payments || [], {
transaction,
});
await orders.setShipments(data.shipments || [], {
transaction,
});
return orders;
@ -93,31 +124,46 @@ module.exports = class OrdersDBApi {
status: item.status
||
null
,
order_date: item.order_date
||
null
,
delivery_date: item.delivery_date
||
null
,
subtotal: item.subtotal
||
null
,
tax: item.tax
||
null
,
shipping_fee: item.shipping_fee
||
null
,
total: item.total
||
null
,
placed_at: item.placed_at
||
null
,
shipped_at: item.shipped_at
||
null
,
payment_status: item.payment_status
||
null
,
shipping_address: item.shipping_address
||
null
,
billing_address: item.billing_address
||
null
,
importHash: item.importHash || null,
@ -153,21 +199,30 @@ module.exports = class OrdersDBApi {
if (data.status !== undefined) updatePayload.status = data.status;
if (data.order_date !== undefined) updatePayload.order_date = data.order_date;
if (data.delivery_date !== undefined) updatePayload.delivery_date = data.delivery_date;
if (data.subtotal !== undefined) updatePayload.subtotal = data.subtotal;
if (data.tax !== undefined) updatePayload.tax = data.tax;
if (data.shipping_fee !== undefined) updatePayload.shipping_fee = data.shipping_fee;
if (data.total !== undefined) updatePayload.total = data.total;
if (data.placed_at !== undefined) updatePayload.placed_at = data.placed_at;
if (data.shipped_at !== undefined) updatePayload.shipped_at = data.shipped_at;
if (data.payment_status !== undefined) updatePayload.payment_status = data.payment_status;
if (data.shipping_address !== undefined) updatePayload.shipping_address = data.shipping_address;
if (data.billing_address !== undefined) updatePayload.billing_address = data.billing_address;
updatePayload.updatedById = currentUser.id;
await orders.update(updatePayload, {transaction});
@ -183,10 +238,31 @@ module.exports = class OrdersDBApi {
);
}
if (data.assigned_to !== undefined) {
await orders.setAssigned_to(
data.assigned_to,
{ transaction }
);
}
if (data.items !== undefined) {
await orders.setItems(data.items, { transaction });
}
if (data.payments !== undefined) {
await orders.setPayments(data.payments, { transaction });
}
if (data.shipments !== undefined) {
await orders.setShipments(data.shipments, { transaction });
}
return orders;
@ -264,28 +340,43 @@ module.exports = class OrdersDBApi {
output.order_items_order = await orders.getOrder_items_order({
output.returns_order = await orders.getReturns_order({
transaction
});
output.payments_order = await orders.getPayments_order({
transaction
});
output.shipments_order = await orders.getShipments_order({
transaction
});
output.customer = await orders.getCustomer({
transaction
});
output.items = await orders.getItems({
transaction
});
output.payments = await orders.getPayments({
transaction
});
output.shipments = await orders.getShipments({
transaction
});
output.assigned_to = await orders.getAssigned_to({
transaction
});
return output;
}
@ -328,6 +419,41 @@ module.exports = class OrdersDBApi {
},
{
model: db.users,
as: 'assigned_to',
where: filter.assigned_to ? {
[Op.or]: [
{ id: { [Op.in]: filter.assigned_to.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.assigned_to.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.order_items,
as: 'items',
required: false,
},
{
model: db.payments,
as: 'payments',
required: false,
},
{
model: db.shipments,
as: 'shipments',
required: false,
},
];
@ -363,10 +489,159 @@ module.exports = class OrdersDBApi {
};
}
if (filter.billing_address) {
where = {
...where,
[Op.and]: Utils.ilike(
'orders',
'billing_address',
filter.billing_address,
),
};
}
if (filter.calendarStart && filter.calendarEnd) {
where = {
...where,
[Op.or]: [
{
order_date: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
{
delivery_date: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
],
};
}
if (filter.order_dateRange) {
const [start, end] = filter.order_dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
order_date: {
...where.order_date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
order_date: {
...where.order_date,
[Op.lte]: end,
},
};
}
}
if (filter.delivery_dateRange) {
const [start, end] = filter.delivery_dateRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
delivery_date: {
...where.delivery_date,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
delivery_date: {
...where.delivery_date,
[Op.lte]: end,
},
};
}
}
if (filter.subtotalRange) {
const [start, end] = filter.subtotalRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
subtotal: {
...where.subtotal,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
subtotal: {
...where.subtotal,
[Op.lte]: end,
},
};
}
}
if (filter.taxRange) {
const [start, end] = filter.taxRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
tax: {
...where.tax,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
tax: {
...where.tax,
[Op.lte]: end,
},
};
}
}
if (filter.shipping_feeRange) {
const [start, end] = filter.shipping_feeRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
shipping_fee: {
...where.shipping_fee,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
shipping_fee: {
...where.shipping_fee,
[Op.lte]: end,
},
};
}
}
if (filter.totalRange) {
const [start, end] = filter.totalRange;
@ -392,54 +667,6 @@ module.exports = class OrdersDBApi {
}
}
if (filter.placed_atRange) {
const [start, end] = filter.placed_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
placed_at: {
...where.placed_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
placed_at: {
...where.placed_at,
[Op.lte]: end,
},
};
}
}
if (filter.shipped_atRange) {
const [start, end] = filter.shipped_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
shipped_at: {
...where.shipped_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
shipped_at: {
...where.shipped_at,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
@ -456,12 +683,6 @@ module.exports = class OrdersDBApi {
};
}
if (filter.payment_status) {
where = {
...where,
payment_status: filter.payment_status,
};
}
@ -469,6 +690,76 @@ module.exports = class OrdersDBApi {
if (filter.items) {
const searchTerms = filter.items.split('|');
include = [
{
model: db.order_items,
as: 'items_filter',
required: searchTerms.length > 0,
where: searchTerms.length > 0 ? {
[Op.or]: [
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
{
product_name: {
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
}
}
]
} : undefined
},
...include,
]
}
if (filter.payments) {
const searchTerms = filter.payments.split('|');
include = [
{
model: db.payments,
as: 'payments_filter',
required: searchTerms.length > 0,
where: searchTerms.length > 0 ? {
[Op.or]: [
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
{
transaction_ref: {
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
}
}
]
} : undefined
},
...include,
]
}
if (filter.shipments) {
const searchTerms = filter.shipments.split('|');
include = [
{
model: db.shipments,
as: 'shipments_filter',
required: searchTerms.length > 0,
where: searchTerms.length > 0 ? {
[Op.or]: [
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
{
tracking_number: {
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
}
}
]
} : undefined
},
...include,
]
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;

View File

@ -21,12 +21,7 @@ module.exports = class PaymentsDBApi {
{
id: data.id || undefined,
transaction_id: data.transaction_id
||
null
,
payment_method: data.payment_method
transaction_ref: data.transaction_ref
||
null
,
@ -36,6 +31,11 @@ module.exports = class PaymentsDBApi {
null
,
method: data.method
||
null
,
status: data.status
||
null
@ -46,6 +46,17 @@ module.exports = class PaymentsDBApi {
null
,
provider: data.provider
||
null
,
refunded: data.refunded
||
false
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@ -54,13 +65,19 @@ module.exports = class PaymentsDBApi {
);
await payments.setOrder( data.order || null, {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.payments.getTableName(),
belongsToColumn: 'receipt',
belongsToId: payments.id,
},
data.receipt,
options,
);
return payments;
@ -75,12 +92,7 @@ module.exports = class PaymentsDBApi {
const paymentsData = data.map((item, index) => ({
id: item.id || undefined,
transaction_id: item.transaction_id
||
null
,
payment_method: item.payment_method
transaction_ref: item.transaction_ref
||
null
,
@ -88,6 +100,11 @@ module.exports = class PaymentsDBApi {
amount: item.amount
||
null
,
method: item.method
||
null
,
status: item.status
@ -98,6 +115,17 @@ module.exports = class PaymentsDBApi {
paid_at: item.paid_at
||
null
,
provider: item.provider
||
null
,
refunded: item.refunded
||
false
,
importHash: item.importHash || null,
@ -111,6 +139,18 @@ module.exports = class PaymentsDBApi {
// For each item created, replace relation files
for (let i = 0; i < payments.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.payments.getTableName(),
belongsToColumn: 'receipt',
belongsToId: payments[i].id,
},
data[i].receipt,
options,
);
}
return payments;
}
@ -127,40 +167,47 @@ module.exports = class PaymentsDBApi {
const updatePayload = {};
if (data.transaction_id !== undefined) updatePayload.transaction_id = data.transaction_id;
if (data.payment_method !== undefined) updatePayload.payment_method = data.payment_method;
if (data.transaction_ref !== undefined) updatePayload.transaction_ref = data.transaction_ref;
if (data.amount !== undefined) updatePayload.amount = data.amount;
if (data.method !== undefined) updatePayload.method = data.method;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.paid_at !== undefined) updatePayload.paid_at = data.paid_at;
if (data.provider !== undefined) updatePayload.provider = data.provider;
if (data.refunded !== undefined) updatePayload.refunded = data.refunded;
updatePayload.updatedById = currentUser.id;
await payments.update(updatePayload, {transaction});
if (data.order !== undefined) {
await payments.setOrder(
data.order,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.payments.getTableName(),
belongsToColumn: 'receipt',
belongsToId: payments.id,
},
data.receipt,
options,
);
return payments;
@ -243,7 +290,10 @@ module.exports = class PaymentsDBApi {
output.order = await payments.getOrder({
output.receipt = await payments.getReceipt({
transaction
});
@ -273,25 +323,13 @@ module.exports = class PaymentsDBApi {
let include = [
{
model: db.orders,
as: 'order',
where: filter.order ? {
[Op.or]: [
{ id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } },
{
order_number: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
model: db.file,
as: 'receipt',
},
];
if (filter) {
@ -303,13 +341,24 @@ module.exports = class PaymentsDBApi {
}
if (filter.transaction_id) {
if (filter.transaction_ref) {
where = {
...where,
[Op.and]: Utils.ilike(
'payments',
'transaction_id',
filter.transaction_id,
'transaction_ref',
filter.transaction_ref,
),
};
}
if (filter.provider) {
where = {
...where,
[Op.and]: Utils.ilike(
'payments',
'provider',
filter.provider,
),
};
}
@ -376,10 +425,10 @@ module.exports = class PaymentsDBApi {
}
if (filter.payment_method) {
if (filter.method) {
where = {
...where,
payment_method: filter.payment_method,
method: filter.method,
};
}
@ -390,7 +439,12 @@ module.exports = class PaymentsDBApi {
};
}
if (filter.refunded) {
where = {
...where,
refunded: filter.refunded,
};
}
@ -464,7 +518,7 @@ module.exports = class PaymentsDBApi {
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'payments',
'transaction_id',
'transaction_ref',
query,
),
],
@ -472,16 +526,16 @@ module.exports = class PaymentsDBApi {
}
const records = await db.payments.findAll({
attributes: [ 'id', 'transaction_id' ],
attributes: [ 'id', 'transaction_ref' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['transaction_id', 'ASC']],
orderBy: [['transaction_ref', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.transaction_id,
label: record.transaction_ref,
}));
}

View File

@ -179,6 +179,9 @@ module.exports = class PermissionsDBApi {
return output;
}

View File

@ -41,7 +41,17 @@ module.exports = class ProductsDBApi {
null
,
weight: data.weight
cost: data.cost
||
null
,
currency: data.currency
||
null
,
stock: data.stock
||
null
,
@ -64,9 +74,17 @@ module.exports = class ProductsDBApi {
transaction,
});
await products.setSupplier( data.supplier || null, {
transaction,
});
await products.setTags(data.tags || [], {
transaction,
});
await FileDBApi.replaceRelationFiles(
{
@ -111,7 +129,17 @@ module.exports = class ProductsDBApi {
null
,
weight: item.weight
cost: item.cost
||
null
,
currency: item.currency
||
null
,
stock: item.stock
||
null
,
@ -173,7 +201,13 @@ module.exports = class ProductsDBApi {
if (data.price !== undefined) updatePayload.price = data.price;
if (data.weight !== undefined) updatePayload.weight = data.weight;
if (data.cost !== undefined) updatePayload.cost = data.cost;
if (data.currency !== undefined) updatePayload.currency = data.currency;
if (data.stock !== undefined) updatePayload.stock = data.stock;
if (data.active !== undefined) updatePayload.active = data.active;
@ -194,10 +228,23 @@ module.exports = class ProductsDBApi {
);
}
if (data.supplier !== undefined) {
await products.setSupplier(
data.supplier,
{ transaction }
);
}
if (data.tags !== undefined) {
await products.setTags(data.tags, { transaction });
}
await FileDBApi.replaceRelationFiles(
{
@ -282,13 +329,24 @@ module.exports = class ProductsDBApi {
output.product_variants_product = await products.getProduct_variants_product({
output.inventory_movements_product = await products.getInventory_movements_product({
transaction
});
output.reviews_product = await products.getReviews_product({
transaction
});
output.order_items_product = await products.getOrder_items_product({
transaction
});
@ -304,6 +362,16 @@ module.exports = class ProductsDBApi {
});
output.supplier = await products.getSupplier({
transaction
});
output.tags = await products.getTags({
transaction
});
return output;
}
@ -346,6 +414,29 @@ module.exports = class ProductsDBApi {
},
{
model: db.suppliers,
as: 'supplier',
where: filter.supplier ? {
[Op.or]: [
{ id: { [Op.in]: filter.supplier.split('|').map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: filter.supplier.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.tags,
as: 'tags',
required: false,
},
{
@ -397,6 +488,17 @@ module.exports = class ProductsDBApi {
};
}
if (filter.currency) {
where = {
...where,
[Op.and]: Utils.ilike(
'products',
'currency',
filter.currency,
),
};
}
@ -426,14 +528,14 @@ module.exports = class ProductsDBApi {
}
}
if (filter.weightRange) {
const [start, end] = filter.weightRange;
if (filter.costRange) {
const [start, end] = filter.costRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
weight: {
...where.weight,
cost: {
...where.cost,
[Op.gte]: start,
},
};
@ -442,8 +544,32 @@ module.exports = class ProductsDBApi {
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
weight: {
...where.weight,
cost: {
...where.cost,
[Op.lte]: end,
},
};
}
}
if (filter.stockRange) {
const [start, end] = filter.stockRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
stock: {
...where.stock,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
stock: {
...where.stock,
[Op.lte]: end,
},
};
@ -472,6 +598,31 @@ module.exports = class ProductsDBApi {
if (filter.tags) {
const searchTerms = filter.tags.split('|');
include = [
{
model: db.tags,
as: 'tags_filter',
required: searchTerms.length > 0,
where: searchTerms.length > 0 ? {
[Op.or]: [
{ id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` }))
}
}
]
} : undefined
},
...include,
]
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;

View File

@ -0,0 +1,552 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class ReturnsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const returns = await db.returns.create(
{
id: data.id || undefined,
rma_number: data.rma_number
||
null
,
reason: data.reason
||
null
,
status: data.status
||
null
,
requested_at: data.requested_at
||
null
,
processed_at: data.processed_at
||
null
,
refund_amount: data.refund_amount
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
await returns.setOrder( data.order || null, {
transaction,
});
return returns;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const returnsData = data.map((item, index) => ({
id: item.id || undefined,
rma_number: item.rma_number
||
null
,
reason: item.reason
||
null
,
status: item.status
||
null
,
requested_at: item.requested_at
||
null
,
processed_at: item.processed_at
||
null
,
refund_amount: item.refund_amount
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const returns = await db.returns.bulkCreate(returnsData, { transaction });
// For each item created, replace relation files
return returns;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const returns = await db.returns.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.rma_number !== undefined) updatePayload.rma_number = data.rma_number;
if (data.reason !== undefined) updatePayload.reason = data.reason;
if (data.status !== undefined) updatePayload.status = data.status;
if (data.requested_at !== undefined) updatePayload.requested_at = data.requested_at;
if (data.processed_at !== undefined) updatePayload.processed_at = data.processed_at;
if (data.refund_amount !== undefined) updatePayload.refund_amount = data.refund_amount;
updatePayload.updatedById = currentUser.id;
await returns.update(updatePayload, {transaction});
if (data.order !== undefined) {
await returns.setOrder(
data.order,
{ transaction }
);
}
return returns;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const returns = await db.returns.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of returns) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of returns) {
await record.destroy({transaction});
}
});
return returns;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const returns = await db.returns.findByPk(id, options);
await returns.update({
deletedBy: currentUser.id
}, {
transaction,
});
await returns.destroy({
transaction
});
return returns;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const returns = await db.returns.findOne(
{ where },
{ transaction },
);
if (!returns) {
return returns;
}
const output = returns.get({plain: true});
output.order = await returns.getOrder({
transaction
});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
{
model: db.orders,
as: 'order',
where: filter.order ? {
[Op.or]: [
{ id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } },
{
order_number: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.rma_number) {
where = {
...where,
[Op.and]: Utils.ilike(
'returns',
'rma_number',
filter.rma_number,
),
};
}
if (filter.reason) {
where = {
...where,
[Op.and]: Utils.ilike(
'returns',
'reason',
filter.reason,
),
};
}
if (filter.calendarStart && filter.calendarEnd) {
where = {
...where,
[Op.or]: [
{
requested_at: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
{
processed_at: {
[Op.between]: [filter.calendarStart, filter.calendarEnd],
},
},
],
};
}
if (filter.requested_atRange) {
const [start, end] = filter.requested_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
requested_at: {
...where.requested_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
requested_at: {
...where.requested_at,
[Op.lte]: end,
},
};
}
}
if (filter.processed_atRange) {
const [start, end] = filter.processed_atRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
processed_at: {
...where.processed_at,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
processed_at: {
...where.processed_at,
[Op.lte]: end,
},
};
}
}
if (filter.refund_amountRange) {
const [start, end] = filter.refund_amountRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
refund_amount: {
...where.refund_amount,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
refund_amount: {
...where.refund_amount,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.status) {
where = {
...where,
status: filter.status,
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.returns.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'returns',
'rma_number',
query,
),
],
};
}
const records = await db.returns.findAll({
attributes: [ 'id', 'rma_number' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['rma_number', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.rma_number,
}));
}
};

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class Product_variantsDBApi {
module.exports = class ReviewsDBApi {
@ -17,36 +17,31 @@ module.exports = class Product_variantsDBApi {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const product_variants = await db.product_variants.create(
const reviews = await db.reviews.create(
{
id: data.id || undefined,
sku: data.sku
author_name: data.author_name
||
null
,
title: data.title
rating: data.rating
||
null
,
price: data.price
comment: data.comment
||
null
,
stock: data.stock
created: data.created
||
null
,
attributes: data.attributes
||
null
,
active: data.active
approved: data.approved
||
false
@ -60,11 +55,7 @@ module.exports = class Product_variantsDBApi {
);
await product_variants.setProduct( data.product || null, {
transaction,
});
await product_variants.setSupplier( data.supplier || null, {
await reviews.setProduct( data.product || null, {
transaction,
});
@ -72,18 +63,8 @@ module.exports = class Product_variantsDBApi {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.product_variants.getTableName(),
belongsToColumn: 'images',
belongsToId: product_variants.id,
},
data.images,
options,
);
return product_variants;
return reviews;
}
@ -92,35 +73,30 @@ module.exports = class Product_variantsDBApi {
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const product_variantsData = data.map((item, index) => ({
const reviewsData = data.map((item, index) => ({
id: item.id || undefined,
sku: item.sku
author_name: item.author_name
||
null
,
title: item.title
rating: item.rating
||
null
,
price: item.price
comment: item.comment
||
null
,
stock: item.stock
created: item.created
||
null
,
attributes: item.attributes
||
null
,
active: item.active
approved: item.approved
||
false
@ -133,24 +109,12 @@ module.exports = class Product_variantsDBApi {
}));
// Bulk create items
const product_variants = await db.product_variants.bulkCreate(product_variantsData, { transaction });
const reviews = await db.reviews.bulkCreate(reviewsData, { transaction });
// For each item created, replace relation files
for (let i = 0; i < product_variants.length; i++) {
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.product_variants.getTableName(),
belongsToColumn: 'images',
belongsToId: product_variants[i].id,
},
data[i].images,
options,
);
}
return product_variants;
return reviews;
}
static async update(id, data, options) {
@ -158,39 +122,36 @@ module.exports = class Product_variantsDBApi {
const transaction = (options && options.transaction) || undefined;
const product_variants = await db.product_variants.findByPk(id, {}, {transaction});
const reviews = await db.reviews.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.sku !== undefined) updatePayload.sku = data.sku;
if (data.author_name !== undefined) updatePayload.author_name = data.author_name;
if (data.title !== undefined) updatePayload.title = data.title;
if (data.rating !== undefined) updatePayload.rating = data.rating;
if (data.price !== undefined) updatePayload.price = data.price;
if (data.comment !== undefined) updatePayload.comment = data.comment;
if (data.stock !== undefined) updatePayload.stock = data.stock;
if (data.created !== undefined) updatePayload.created = data.created;
if (data.attributes !== undefined) updatePayload.attributes = data.attributes;
if (data.active !== undefined) updatePayload.active = data.active;
if (data.approved !== undefined) updatePayload.approved = data.approved;
updatePayload.updatedById = currentUser.id;
await product_variants.update(updatePayload, {transaction});
await reviews.update(updatePayload, {transaction});
if (data.product !== undefined) {
await product_variants.setProduct(
await reviews.setProduct(
data.product,
@ -198,39 +159,20 @@ module.exports = class Product_variantsDBApi {
);
}
if (data.supplier !== undefined) {
await product_variants.setSupplier(
data.supplier,
{ transaction }
);
}
await FileDBApi.replaceRelationFiles(
{
belongsTo: db.product_variants.getTableName(),
belongsToColumn: 'images',
belongsToId: product_variants.id,
},
data.images,
options,
);
return product_variants;
return reviews;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const product_variants = await db.product_variants.findAll({
const reviews = await db.reviews.findAll({
where: {
id: {
[Op.in]: ids,
@ -240,53 +182,53 @@ module.exports = class Product_variantsDBApi {
});
await db.sequelize.transaction(async (transaction) => {
for (const record of product_variants) {
for (const record of reviews) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of product_variants) {
for (const record of reviews) {
await record.destroy({transaction});
}
});
return product_variants;
return reviews;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const product_variants = await db.product_variants.findByPk(id, options);
const reviews = await db.reviews.findByPk(id, options);
await product_variants.update({
await reviews.update({
deletedBy: currentUser.id
}, {
transaction,
});
await product_variants.destroy({
await reviews.destroy({
transaction
});
return product_variants;
return reviews;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const product_variants = await db.product_variants.findOne(
const reviews = await db.reviews.findOne(
{ where },
{ transaction },
);
if (!product_variants) {
return product_variants;
if (!reviews) {
return reviews;
}
const output = product_variants.get({plain: true});
const output = reviews.get({plain: true});
@ -296,32 +238,17 @@ module.exports = class Product_variantsDBApi {
output.inventory_variant = await product_variants.getInventory_variant({
transaction
});
output.order_items_product_variant = await product_variants.getOrder_items_product_variant({
transaction
});
output.product = await product_variants.getProduct({
transaction
});
output.supplier = await product_variants.getSupplier({
transaction
});
output.images = await product_variants.getImages({
output.product = await reviews.getProduct({
transaction
});
@ -368,29 +295,7 @@ module.exports = class Product_variantsDBApi {
},
{
model: db.suppliers,
as: 'supplier',
where: filter.supplier ? {
[Op.or]: [
{ id: { [Op.in]: filter.supplier.split('|').map(term => Utils.uuid(term)) } },
{
name: {
[Op.or]: filter.supplier.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
{
model: db.file,
as: 'images',
},
];
@ -403,35 +308,24 @@ module.exports = class Product_variantsDBApi {
}
if (filter.sku) {
if (filter.author_name) {
where = {
...where,
[Op.and]: Utils.ilike(
'product_variants',
'sku',
filter.sku,
'reviews',
'author_name',
filter.author_name,
),
};
}
if (filter.title) {
if (filter.comment) {
where = {
...where,
[Op.and]: Utils.ilike(
'product_variants',
'title',
filter.title,
),
};
}
if (filter.attributes) {
where = {
...where,
[Op.and]: Utils.ilike(
'product_variants',
'attributes',
filter.attributes,
'reviews',
'comment',
filter.comment,
),
};
}
@ -441,14 +335,14 @@ module.exports = class Product_variantsDBApi {
if (filter.priceRange) {
const [start, end] = filter.priceRange;
if (filter.ratingRange) {
const [start, end] = filter.ratingRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
price: {
...where.price,
rating: {
...where.rating,
[Op.gte]: start,
},
};
@ -457,22 +351,22 @@ module.exports = class Product_variantsDBApi {
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
price: {
...where.price,
rating: {
...where.rating,
[Op.lte]: end,
},
};
}
}
if (filter.stockRange) {
const [start, end] = filter.stockRange;
if (filter.createdRange) {
const [start, end] = filter.createdRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
stock: {
...where.stock,
created: {
...where.created,
[Op.gte]: start,
},
};
@ -481,8 +375,8 @@ module.exports = class Product_variantsDBApi {
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
stock: {
...where.stock,
created: {
...where.created,
[Op.lte]: end,
},
};
@ -498,10 +392,10 @@ module.exports = class Product_variantsDBApi {
}
if (filter.active) {
if (filter.approved) {
where = {
...where,
active: filter.active,
approved: filter.approved,
};
}
@ -511,8 +405,6 @@ module.exports = class Product_variantsDBApi {
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
@ -558,7 +450,7 @@ module.exports = class Product_variantsDBApi {
}
try {
const { rows, count } = await db.product_variants.findAndCountAll(queryOptions);
const { rows, count } = await db.reviews.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
@ -580,25 +472,25 @@ module.exports = class Product_variantsDBApi {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'product_variants',
'sku',
'reviews',
'author_name',
query,
),
],
};
}
const records = await db.product_variants.findAll({
attributes: [ 'id', 'sku' ],
const records = await db.reviews.findAll({
attributes: [ 'id', 'author_name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['sku', 'ASC']],
orderBy: [['author_name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.sku,
label: record.author_name,
}));
}

View File

@ -203,6 +203,9 @@ module.exports = class RolesDBApi {
output.permissions = await roles.getPermissions({
transaction
});

View File

@ -21,12 +21,12 @@ module.exports = class ShipmentsDBApi {
{
id: data.id || undefined,
carrier: data.carrier
tracking_number: data.tracking_number
||
null
,
tracking_number: data.tracking_number
carrier: data.carrier
||
null
,
@ -46,7 +46,7 @@ module.exports = class ShipmentsDBApi {
null
,
cost: data.cost
notes: data.notes
||
null
,
@ -59,10 +59,6 @@ module.exports = class ShipmentsDBApi {
);
await shipments.setOrder( data.order || null, {
transaction,
});
@ -80,12 +76,12 @@ module.exports = class ShipmentsDBApi {
const shipmentsData = data.map((item, index) => ({
id: item.id || undefined,
carrier: item.carrier
tracking_number: item.tracking_number
||
null
,
tracking_number: item.tracking_number
carrier: item.carrier
||
null
,
@ -105,7 +101,7 @@ module.exports = class ShipmentsDBApi {
null
,
cost: item.cost
notes: item.notes
||
null
,
@ -137,12 +133,12 @@ module.exports = class ShipmentsDBApi {
const updatePayload = {};
if (data.carrier !== undefined) updatePayload.carrier = data.carrier;
if (data.tracking_number !== undefined) updatePayload.tracking_number = data.tracking_number;
if (data.carrier !== undefined) updatePayload.carrier = data.carrier;
if (data.status !== undefined) updatePayload.status = data.status;
@ -152,7 +148,7 @@ module.exports = class ShipmentsDBApi {
if (data.delivered_at !== undefined) updatePayload.delivered_at = data.delivered_at;
if (data.cost !== undefined) updatePayload.cost = data.cost;
if (data.notes !== undefined) updatePayload.notes = data.notes;
updatePayload.updatedById = currentUser.id;
@ -161,15 +157,6 @@ module.exports = class ShipmentsDBApi {
if (data.order !== undefined) {
await shipments.setOrder(
data.order,
{ transaction }
);
}
@ -256,9 +243,7 @@ module.exports = class ShipmentsDBApi {
output.order = await shipments.getOrder({
transaction
});
@ -286,23 +271,6 @@ module.exports = class ShipmentsDBApi {
let include = [
{
model: db.orders,
as: 'order',
where: filter.order ? {
[Op.or]: [
{ id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } },
{
order_number: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
} : {},
},
];
@ -316,6 +284,17 @@ module.exports = class ShipmentsDBApi {
}
if (filter.tracking_number) {
where = {
...where,
[Op.and]: Utils.ilike(
'shipments',
'tracking_number',
filter.tracking_number,
),
};
}
if (filter.carrier) {
where = {
...where,
@ -327,13 +306,13 @@ module.exports = class ShipmentsDBApi {
};
}
if (filter.tracking_number) {
if (filter.notes) {
where = {
...where,
[Op.and]: Utils.ilike(
'shipments',
'tracking_number',
filter.tracking_number,
'notes',
filter.notes,
),
};
}
@ -409,30 +388,6 @@ module.exports = class ShipmentsDBApi {
}
}
if (filter.costRange) {
const [start, end] = filter.costRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
cost: {
...where.cost,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
cost: {
...where.cost,
[Op.lte]: end,
},
};
}
}
if (filter.active !== undefined) {
where = {
@ -453,8 +408,6 @@ module.exports = class ShipmentsDBApi {
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;

View File

@ -31,12 +31,12 @@ module.exports = class SuppliersDBApi {
null
,
phone: data.phone
email: data.email
||
null
,
email: data.email
phone: data.phone
||
null
,
@ -86,12 +86,12 @@ module.exports = class SuppliersDBApi {
null
,
phone: item.phone
email: item.email
||
null
,
email: item.email
phone: item.phone
||
null
,
@ -139,12 +139,12 @@ module.exports = class SuppliersDBApi {
if (data.contact_name !== undefined) updatePayload.contact_name = data.contact_name;
if (data.phone !== undefined) updatePayload.phone = data.phone;
if (data.email !== undefined) updatePayload.email = data.email;
if (data.phone !== undefined) updatePayload.phone = data.phone;
if (data.address !== undefined) updatePayload.address = data.address;
@ -235,7 +235,8 @@ module.exports = class SuppliersDBApi {
output.product_variants_supplier = await suppliers.getProduct_variants_supplier({
output.products_supplier = await suppliers.getProducts_supplier({
transaction
});
@ -248,6 +249,8 @@ module.exports = class SuppliersDBApi {
return output;
}
@ -307,17 +310,6 @@ module.exports = class SuppliersDBApi {
};
}
if (filter.phone) {
where = {
...where,
[Op.and]: Utils.ilike(
'suppliers',
'phone',
filter.phone,
),
};
}
if (filter.email) {
where = {
...where,
@ -329,6 +321,17 @@ module.exports = class SuppliersDBApi {
};
}
if (filter.phone) {
where = {
...where,
[Op.and]: Utils.ilike(
'suppliers',
'phone',
filter.phone,
),
};
}
if (filter.address) {
where = {
...where,

366
backend/src/db/api/tags.js Normal file
View File

@ -0,0 +1,366 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const Sequelize = db.Sequelize;
const Op = Sequelize.Op;
module.exports = class TagsDBApi {
static async create(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.create(
{
id: data.id || undefined,
name: data.name
||
null
,
color: data.color
||
null
,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
},
{ transaction },
);
return tags;
}
static async bulkImport(data, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method
const tagsData = data.map((item, index) => ({
id: item.id || undefined,
name: item.name
||
null
,
color: item.color
||
null
,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
createdAt: new Date(Date.now() + index * 1000),
}));
// Bulk create items
const tags = await db.tags.bulkCreate(tagsData, { transaction });
// For each item created, replace relation files
return tags;
}
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findByPk(id, {}, {transaction});
const updatePayload = {};
if (data.name !== undefined) updatePayload.name = data.name;
if (data.color !== undefined) updatePayload.color = data.color;
updatePayload.updatedById = currentUser.id;
await tags.update(updatePayload, {transaction});
return tags;
}
static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findAll({
where: {
id: {
[Op.in]: ids,
},
},
transaction,
});
await db.sequelize.transaction(async (transaction) => {
for (const record of tags) {
await record.update(
{deletedBy: currentUser.id},
{transaction}
);
}
for (const record of tags) {
await record.destroy({transaction});
}
});
return tags;
}
static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findByPk(id, options);
await tags.update({
deletedBy: currentUser.id
}, {
transaction,
});
await tags.destroy({
transaction
});
return tags;
}
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const tags = await db.tags.findOne(
{ where },
{ transaction },
);
if (!tags) {
return tags;
}
const output = tags.get({plain: true});
return output;
}
static async findAll(
filter,
options
) {
const limit = filter.limit || 0;
let offset = 0;
let where = {};
const currentPage = +filter.page;
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
];
if (filter) {
if (filter.id) {
where = {
...where,
['id']: Utils.uuid(filter.id),
};
}
if (filter.name) {
where = {
...where,
[Op.and]: Utils.ilike(
'tags',
'name',
filter.name,
),
};
}
if (filter.color) {
where = {
...where,
[Op.and]: Utils.ilike(
'tags',
'color',
filter.color,
),
};
}
if (filter.active !== undefined) {
where = {
...where,
active: filter.active === true || filter.active === 'true'
};
}
if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange;
if (start !== undefined && start !== null && start !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.gte]: start,
},
};
}
if (end !== undefined && end !== null && end !== '') {
where = {
...where,
['createdAt']: {
...where.createdAt,
[Op.lte]: end,
},
};
}
}
}
const queryOptions = {
where,
include,
distinct: true,
order: filter.field && filter.sort
? [[filter.field, filter.sort]]
: [['createdAt', 'desc']],
transaction: options?.transaction,
logging: console.log
};
if (!options?.countOnly) {
queryOptions.limit = limit ? Number(limit) : undefined;
queryOptions.offset = offset ? Number(offset) : undefined;
}
try {
const { rows, count } = await db.tags.findAndCountAll(queryOptions);
return {
rows: options?.countOnly ? [] : rows,
count: count
};
} catch (error) {
console.error('Error executing query:', error);
throw error;
}
}
static async findAllAutocomplete(query, limit, offset, ) {
let where = {};
if (query) {
where = {
[Op.or]: [
{ ['id']: Utils.uuid(query) },
Utils.ilike(
'tags',
'name',
query,
),
],
};
}
const records = await db.tags.findAll({
attributes: [ 'id', 'name' ],
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],
});
return records.map((record) => ({
id: record.id,
label: record.name,
}));
}
};

View File

@ -411,6 +411,13 @@ module.exports = class UsersDBApi {
output.orders_assigned_to = await users.getOrders_assigned_to({
transaction
});

View File

@ -15,7 +15,7 @@ module.exports = {
username: 'postgres',
dialect: 'postgres',
password: '',
database: 'db_storeops_manager',
database: 'db_store_operations_manager',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',

View File

@ -52,6 +52,8 @@ description: {
db.categories.hasMany(db.products, {
as: 'products_category',
foreignKey: {
@ -69,6 +71,7 @@ description: {
//end loop

View File

@ -35,14 +35,14 @@ phone: {
},
billing_address: {
company: {
type: DataTypes.TEXT,
},
shipping_address: {
address: {
type: DataTypes.TEXT,
@ -56,11 +56,8 @@ notes: {
},
vip: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
loyalty_points: {
type: DataTypes.INTEGER,
@ -93,6 +90,9 @@ vip: {
db.customers.hasMany(db.orders, {
as: 'orders_customer',
foreignKey: {

View File

@ -0,0 +1,135 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const discounts = sequelize.define(
'discounts',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
code: {
type: DataTypes.TEXT,
},
description: {
type: DataTypes.TEXT,
},
type: {
type: DataTypes.ENUM,
values: [
"Percentage",
"Fixed"
],
},
value: {
type: DataTypes.DECIMAL,
},
active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
starts: {
type: DataTypes.DATE,
},
ends: {
type: DataTypes.DATE,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
discounts.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.discounts.belongsTo(db.users, {
as: 'createdBy',
});
db.discounts.belongsTo(db.users, {
as: 'updatedBy',
});
};
return discounts;
};

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const inventory = sequelize.define(
'inventory',
const inventory_movements = sequelize.define(
'inventory_movements',
{
id: {
type: DataTypes.UUID,
@ -14,44 +14,44 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
quantity: {
note: {
type: DataTypes.TEXT,
},
change: {
type: DataTypes.INTEGER,
},
location: {
type: DataTypes.TEXT,
},
change_type: {
reason: {
type: DataTypes.ENUM,
values: [
"addition",
"Purchase",
"deduction",
"Sale",
"adjustment",
"Return",
"transfer"
"Adjustment"
],
},
note: {
type: DataTypes.TEXT,
date: {
type: DataTypes.DATE,
@ -70,7 +70,7 @@ note: {
},
);
inventory.associate = (db) => {
inventory_movements.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
@ -90,14 +90,17 @@ note: {
//end loop
db.inventory.belongsTo(db.product_variants, {
as: 'variant',
db.inventory_movements.belongsTo(db.products, {
as: 'product',
foreignKey: {
name: 'variantId',
name: 'productId',
},
constraints: false,
});
@ -105,18 +108,18 @@ note: {
db.inventory.belongsTo(db.users, {
db.inventory_movements.belongsTo(db.users, {
as: 'createdBy',
});
db.inventory.belongsTo(db.users, {
db.inventory_movements.belongsTo(db.users, {
as: 'updatedBy',
});
};
return inventory;
return inventory_movements;
};

View File

@ -14,7 +14,7 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
name: {
product_name: {
type: DataTypes.TEXT,
@ -40,6 +40,13 @@ total: {
},
sku: {
type: DataTypes.TEXT,
},
importHash: {
@ -75,22 +82,17 @@ total: {
//end loop
db.order_items.belongsTo(db.orders, {
as: 'order',
db.order_items.belongsTo(db.products, {
as: 'product',
foreignKey: {
name: 'orderId',
},
constraints: false,
});
db.order_items.belongsTo(db.product_variants, {
as: 'product_variant',
foreignKey: {
name: 'product_variantId',
name: 'productId',
},
constraints: false,
});

View File

@ -49,6 +49,41 @@ status: {
},
order_date: {
type: DataTypes.DATE,
},
delivery_date: {
type: DataTypes.DATE,
},
subtotal: {
type: DataTypes.DECIMAL,
},
tax: {
type: DataTypes.DECIMAL,
},
shipping_fee: {
type: DataTypes.DECIMAL,
},
total: {
type: DataTypes.DECIMAL,
@ -56,47 +91,18 @@ total: {
},
placed_at: {
type: DataTypes.DATE,
},
shipped_at: {
type: DataTypes.DATE,
},
payment_status: {
type: DataTypes.ENUM,
values: [
"Paid",
"Unpaid",
"PartiallyRefunded",
"Refunded"
],
},
shipping_address: {
type: DataTypes.TEXT,
},
billing_address: {
type: DataTypes.TEXT,
},
importHash: {
@ -114,6 +120,60 @@ shipping_address: {
orders.associate = (db) => {
db.orders.belongsToMany(db.order_items, {
as: 'items',
foreignKey: {
name: 'orders_itemsId',
},
constraints: false,
through: 'ordersItemsOrder_items',
});
db.orders.belongsToMany(db.order_items, {
as: 'items_filter',
foreignKey: {
name: 'orders_itemsId',
},
constraints: false,
through: 'ordersItemsOrder_items',
});
db.orders.belongsToMany(db.payments, {
as: 'payments',
foreignKey: {
name: 'orders_paymentsId',
},
constraints: false,
through: 'ordersPaymentsPayments',
});
db.orders.belongsToMany(db.payments, {
as: 'payments_filter',
foreignKey: {
name: 'orders_paymentsId',
},
constraints: false,
through: 'ordersPaymentsPayments',
});
db.orders.belongsToMany(db.shipments, {
as: 'shipments',
foreignKey: {
name: 'orders_shipmentsId',
},
constraints: false,
through: 'ordersShipmentsShipments',
});
db.orders.belongsToMany(db.shipments, {
as: 'shipments_filter',
foreignKey: {
name: 'orders_shipmentsId',
},
constraints: false,
through: 'ordersShipmentsShipments',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
@ -127,8 +187,14 @@ shipping_address: {
db.orders.hasMany(db.order_items, {
as: 'order_items_order',
db.orders.hasMany(db.returns, {
as: 'returns_order',
foreignKey: {
name: 'orderId',
},
@ -136,25 +202,6 @@ shipping_address: {
});
db.orders.hasMany(db.payments, {
as: 'payments_order',
foreignKey: {
name: 'orderId',
},
constraints: false,
});
db.orders.hasMany(db.shipments, {
as: 'shipments_order',
foreignKey: {
name: 'orderId',
},
constraints: false,
});
//end loop
@ -168,6 +215,14 @@ shipping_address: {
constraints: false,
});
db.orders.belongsTo(db.users, {
as: 'assigned_to',
foreignKey: {
name: 'assigned_toId',
},
constraints: false,
});

View File

@ -14,14 +14,21 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
transaction_id: {
transaction_ref: {
type: DataTypes.TEXT,
},
payment_method: {
amount: {
type: DataTypes.DECIMAL,
},
method: {
type: DataTypes.ENUM,
@ -43,13 +50,6 @@ payment_method: {
},
amount: {
type: DataTypes.DECIMAL,
},
status: {
type: DataTypes.ENUM,
@ -57,15 +57,15 @@ status: {
values: [
"Succeeded",
"Pending",
"Completed",
"Failed",
"Pending",
"Refunded"
],
@ -77,6 +77,23 @@ paid_at: {
},
provider: {
type: DataTypes.TEXT,
},
refunded: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
},
importHash: {
@ -112,21 +129,26 @@ paid_at: {
//end loop
db.payments.belongsTo(db.orders, {
as: 'order',
foreignKey: {
name: 'orderId',
},
db.payments.hasMany(db.file, {
as: 'receipt',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.payments.getTableName(),
belongsToColumn: 'receipt',
},
});
db.payments.belongsTo(db.users, {
as: 'createdBy',
});

View File

@ -54,6 +54,9 @@ name: {
//end loop

View File

@ -42,11 +42,25 @@ price: {
},
weight: {
cost: {
type: DataTypes.DECIMAL,
},
currency: {
type: DataTypes.TEXT,
},
stock: {
type: DataTypes.INTEGER,
},
active: {
@ -74,6 +88,24 @@ active: {
products.associate = (db) => {
db.products.belongsToMany(db.tags, {
as: 'tags',
foreignKey: {
name: 'products_tagsId',
},
constraints: false,
through: 'productsTagsTags',
});
db.products.belongsToMany(db.tags, {
as: 'tags_filter',
foreignKey: {
name: 'products_tagsId',
},
constraints: false,
through: 'productsTagsTags',
});
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
@ -84,8 +116,19 @@ active: {
db.products.hasMany(db.product_variants, {
as: 'product_variants_product',
db.products.hasMany(db.inventory_movements, {
as: 'inventory_movements_product',
foreignKey: {
name: 'productId',
},
constraints: false,
});
db.products.hasMany(db.reviews, {
as: 'reviews_product',
foreignKey: {
name: 'productId',
},
@ -95,6 +138,14 @@ active: {
db.products.hasMany(db.order_items, {
as: 'order_items_product',
foreignKey: {
name: 'productId',
},
constraints: false,
});
@ -112,6 +163,14 @@ active: {
constraints: false,
});
db.products.belongsTo(db.suppliers, {
as: 'supplier',
foreignKey: {
name: 'supplierId',
},
constraints: false,
});
db.products.hasMany(db.file, {

View File

@ -0,0 +1,139 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const returns = sequelize.define(
'returns',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
rma_number: {
type: DataTypes.TEXT,
},
reason: {
type: DataTypes.TEXT,
},
status: {
type: DataTypes.ENUM,
values: [
"Requested",
"Approved",
"Rejected",
"Completed"
],
},
requested_at: {
type: DataTypes.DATE,
},
processed_at: {
type: DataTypes.DATE,
},
refund_amount: {
type: DataTypes.DECIMAL,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
returns.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.returns.belongsTo(db.orders, {
as: 'order',
foreignKey: {
name: 'orderId',
},
constraints: false,
});
db.returns.belongsTo(db.users, {
as: 'createdBy',
});
db.returns.belongsTo(db.users, {
as: 'updatedBy',
});
};
return returns;
};

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const product_variants = sequelize.define(
'product_variants',
const reviews = sequelize.define(
'reviews',
{
id: {
type: DataTypes.UUID,
@ -14,42 +14,35 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
sku: {
author_name: {
type: DataTypes.TEXT,
},
title: {
type: DataTypes.TEXT,
},
price: {
type: DataTypes.DECIMAL,
},
stock: {
rating: {
type: DataTypes.INTEGER,
},
attributes: {
comment: {
type: DataTypes.TEXT,
},
active: {
created: {
type: DataTypes.DATE,
},
approved: {
type: DataTypes.BOOLEAN,
allowNull: false,
@ -72,7 +65,7 @@ active: {
},
);
product_variants.associate = (db) => {
reviews.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
@ -85,23 +78,10 @@ active: {
db.product_variants.hasMany(db.inventory, {
as: 'inventory_variant',
foreignKey: {
name: 'variantId',
},
constraints: false,
});
db.product_variants.hasMany(db.order_items, {
as: 'order_items_product_variant',
foreignKey: {
name: 'product_variantId',
},
constraints: false,
});
@ -112,7 +92,7 @@ active: {
db.product_variants.belongsTo(db.products, {
db.reviews.belongsTo(db.products, {
as: 'product',
foreignKey: {
name: 'productId',
@ -120,39 +100,21 @@ active: {
constraints: false,
});
db.product_variants.belongsTo(db.suppliers, {
as: 'supplier',
foreignKey: {
name: 'supplierId',
},
constraints: false,
});
db.product_variants.hasMany(db.file, {
as: 'images',
foreignKey: 'belongsToId',
constraints: false,
scope: {
belongsTo: db.product_variants.getTableName(),
belongsToColumn: 'images',
},
});
db.product_variants.belongsTo(db.users, {
db.reviews.belongsTo(db.users, {
as: 'createdBy',
});
db.product_variants.belongsTo(db.users, {
db.reviews.belongsTo(db.users, {
as: 'updatedBy',
});
};
return product_variants;
return reviews;
};

View File

@ -87,6 +87,9 @@ role_customization: {
//end loop

View File

@ -14,14 +14,14 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true,
},
carrier: {
tracking_number: {
type: DataTypes.TEXT,
},
tracking_number: {
carrier: {
type: DataTypes.TEXT,
@ -35,7 +35,7 @@ status: {
values: [
"Pending",
"LabelCreated",
"InTransit",
@ -64,8 +64,8 @@ delivered_at: {
},
cost: {
type: DataTypes.DECIMAL,
notes: {
type: DataTypes.TEXT,
@ -104,18 +104,13 @@ cost: {
//end loop
db.shipments.belongsTo(db.orders, {
as: 'order',
foreignKey: {
name: 'orderId',
},
constraints: false,
});

View File

@ -28,14 +28,14 @@ contact_name: {
},
phone: {
email: {
type: DataTypes.TEXT,
},
email: {
phone: {
type: DataTypes.TEXT,
@ -81,8 +81,9 @@ notes: {
db.suppliers.hasMany(db.product_variants, {
as: 'product_variants_supplier',
db.suppliers.hasMany(db.products, {
as: 'products_supplier',
foreignKey: {
name: 'supplierId',
},
@ -97,6 +98,8 @@ notes: {
//end loop

View File

@ -0,0 +1,88 @@
const config = require('../../config');
const providers = config.providers;
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const moment = require('moment');
module.exports = function(sequelize, DataTypes) {
const tags = sequelize.define(
'tags',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
color: {
type: DataTypes.TEXT,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
tags.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
//end loop
db.tags.belongsTo(db.users, {
as: 'createdBy',
});
db.tags.belongsTo(db.users, {
as: 'updatedBy',
});
};
return tags;
};

View File

@ -152,6 +152,17 @@ provider: {
db.users.hasMany(db.orders, {
as: 'orders_assigned_to',
foreignKey: {
name: 'assigned_toId',
},
constraints: false,
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const pexelsRoutes = require('./routes/pexels');
const dashboardRoutes = require('./routes/dashboard');
const openaiRoutes = require('./routes/openai');
@ -30,11 +31,17 @@ const customersRoutes = require('./routes/customers');
const categoriesRoutes = require('./routes/categories');
const suppliersRoutes = require('./routes/suppliers');
const tagsRoutes = require('./routes/tags');
const productsRoutes = require('./routes/products');
const product_variantsRoutes = require('./routes/product_variants');
const inventory_movementsRoutes = require('./routes/inventory_movements');
const inventoryRoutes = require('./routes/inventory');
const reviewsRoutes = require('./routes/reviews');
const discountsRoutes = require('./routes/discounts');
const ordersRoutes = require('./routes/orders');
@ -44,7 +51,7 @@ const paymentsRoutes = require('./routes/payments');
const shipmentsRoutes = require('./routes/shipments');
const suppliersRoutes = require('./routes/suppliers');
const returnsRoutes = require('./routes/returns');
const getBaseUrl = (url) => {
@ -57,8 +64,8 @@ const options = {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "StoreOps Manager",
description: "StoreOps Manager Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
title: "Store Operations Manager",
description: "Store Operations Manager Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.",
},
servers: [
{
@ -101,6 +108,7 @@ app.use(bodyParser.json());
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/pexels', pexelsRoutes);
app.use('/api/dashboard', passport.authenticate('jwt', {session: false}), dashboardRoutes);
app.enable('trust proxy');
@ -114,11 +122,17 @@ app.use('/api/customers', passport.authenticate('jwt', {session: false}), custom
app.use('/api/categories', passport.authenticate('jwt', {session: false}), categoriesRoutes);
app.use('/api/suppliers', passport.authenticate('jwt', {session: false}), suppliersRoutes);
app.use('/api/tags', passport.authenticate('jwt', {session: false}), tagsRoutes);
app.use('/api/products', passport.authenticate('jwt', {session: false}), productsRoutes);
app.use('/api/product_variants', passport.authenticate('jwt', {session: false}), product_variantsRoutes);
app.use('/api/inventory_movements', passport.authenticate('jwt', {session: false}), inventory_movementsRoutes);
app.use('/api/inventory', passport.authenticate('jwt', {session: false}), inventoryRoutes);
app.use('/api/reviews', passport.authenticate('jwt', {session: false}), reviewsRoutes);
app.use('/api/discounts', passport.authenticate('jwt', {session: false}), discountsRoutes);
app.use('/api/orders', passport.authenticate('jwt', {session: false}), ordersRoutes);
@ -128,7 +142,7 @@ app.use('/api/payments', passport.authenticate('jwt', {session: false}), payment
app.use('/api/shipments', passport.authenticate('jwt', {session: false}), shipmentsRoutes);
app.use('/api/suppliers', passport.authenticate('jwt', {session: false}), suppliersRoutes);
app.use('/api/returns', passport.authenticate('jwt', {session: false}), returnsRoutes);
app.use(
'/api/openai',

View File

@ -26,6 +26,12 @@ router.use(checkCrudPermissions('categories'));
* type: object
* properties:
* name:
* type: string
* default: name
* description:
* type: string
* default: description
@ -285,7 +291,7 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','name','description',

View File

@ -26,7 +26,28 @@ router.use(checkCrudPermissions('customers'));
* type: object
* properties:
* name:
* type: string
* default: name
* email:
* type: string
* default: email
* phone:
* type: string
* default: phone
* company:
* type: string
* default: company
* address:
* type: string
* default: address
* notes:
* type: string
* default: notes
* loyalty_points:
* type: integer
* format: int64
*/
@ -285,8 +306,8 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','name','email','phone','company','address','notes',
'loyalty_points',
];

View File

@ -0,0 +1,60 @@
const models = require('../db/models');
const { Op } = require('sequelize');
const moment = require('moment');
module.exports = function (app) {
app.get('/api/dashboard/summary', async (req, res) => {
try {
const totalRevenue = await models.payments.sum('amount', {
where: { status: 'Completed' },
});
const salesToday = await models.payments.sum('amount', {
where: {
status: 'Completed',
createdAt: {
[Op.gte]: moment().startOf('day').toDate(),
},
},
});
const newCustomers = await models.customers.count({
where: {
createdAt: {
[Op.gte]: moment().subtract(30, 'days').toDate(),
},
},
});
const pendingOrders = await models.orders.count({
where: {
status: 'Pending',
},
});
const recentOrders = await models.orders.findAll({
limit: 5,
order: [['createdAt', 'DESC']],
include: [
{
model: models.customers,
as: 'customer',
attributes: ['name'],
},
],
});
res.json({
totalRevenue: totalRevenue || 0,
salesToday: salesToday || 0,
newCustomers: newCustomers || 0,
pendingOrders: pendingOrders || 0,
recentOrders,
});
} catch (error) {
console.error(error);
res.status(500).send({ error: 'Internal server error' });
}
});
};

View File

@ -1,8 +1,8 @@
const express = require('express');
const Product_variantsService = require('../services/product_variants');
const Product_variantsDBApi = require('../db/api/product_variants');
const DiscountsService = require('../services/discounts');
const DiscountsDBApi = require('../db/api/discounts');
const wrapAsync = require('../helpers').wrapAsync;
@ -15,36 +15,46 @@ const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('product_variants'));
router.use(checkCrudPermissions('discounts'));
/**
* @swagger
* components:
* schemas:
* Product_variants:
* Discounts:
* type: object
* properties:
* code:
* type: string
* default: code
* description:
* type: string
* default: description
* value:
* type: integer
* format: int64
*
*/
/**
* @swagger
* tags:
* name: Product_variants
* description: The Product_variants managing API
* name: Discounts
* description: The Discounts managing API
*/
/**
* @swagger
* /api/product_variants:
* /api/discounts:
* post:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Add new item
* description: Add new item
* requestBody:
@ -56,14 +66,14 @@ router.use(checkCrudPermissions('product_variants'));
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -74,7 +84,7 @@ router.use(checkCrudPermissions('product_variants'));
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Product_variantsService.create(req.body.data, req.currentUser, true, link.host);
await DiscountsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
@ -85,7 +95,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
@ -98,14 +108,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -117,18 +127,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Product_variantsService.bulkImport(req, res, true, link.host);
await DiscountsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/product_variants/{id}:
* /api/discounts/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
@ -151,7 +161,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* required:
* - id
* responses:
@ -160,7 +170,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 400:
* description: Invalid ID supplied
* 401:
@ -171,18 +181,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Product_variantsService.update(req.body.data, req.body.id, req.currentUser);
await DiscountsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/product_variants/{id}:
* /api/discounts/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
@ -198,7 +208,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 400:
* description: Invalid ID supplied
* 401:
@ -209,18 +219,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Product_variantsService.remove(req.params.id, req.currentUser);
await DiscountsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/product_variants/deleteByIds:
* /api/discounts/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
@ -238,7 +248,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -247,29 +257,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Product_variantsService.deleteByIds(req.body.data, req.currentUser);
await DiscountsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/product_variants:
* /api/discounts:
* get:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* summary: Get all product_variants
* description: Get all product_variants
* tags: [Discounts]
* summary: Get all discounts
* description: Get all discounts
* responses:
* 200:
* description: Product_variants list successfully received
* description: Discounts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -281,14 +291,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Product_variantsDBApi.findAll(
const payload = await DiscountsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','code','description',
'value',
'starts','ends',
];
const opts = { fields };
try {
@ -307,22 +317,22 @@ router.get('/', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/product_variants/count:
* /api/discounts/count:
* get:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* summary: Count all product_variants
* description: Count all product_variants
* tags: [Discounts]
* summary: Count all discounts
* description: Count all discounts
* responses:
* 200:
* description: Product_variants count successfully received
* description: Discounts count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -333,7 +343,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Product_variantsDBApi.findAll(
const payload = await DiscountsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
@ -344,22 +354,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/product_variants/autocomplete:
* /api/discounts/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* summary: Find all product_variants that match search criteria
* description: Find all product_variants that match search criteria
* tags: [Discounts]
* summary: Find all discounts that match search criteria
* description: Find all discounts that match search criteria
* responses:
* 200:
* description: Product_variants list successfully received
* description: Discounts list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -369,7 +379,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Product_variantsDBApi.findAllAutocomplete(
const payload = await DiscountsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
@ -381,11 +391,11 @@ router.get('/autocomplete', async (req, res) => {
/**
* @swagger
* /api/product_variants/{id}:
* /api/discounts/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Product_variants]
* tags: [Discounts]
* summary: Get selected item
* description: Get selected item
* parameters:
@ -401,7 +411,7 @@ router.get('/autocomplete', async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Product_variants"
* $ref: "#/components/schemas/Discounts"
* 400:
* description: Invalid ID supplied
* 401:
@ -412,7 +422,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Product_variantsDBApi.findBy(
const payload = await DiscountsDBApi.findBy(
{ id: req.params.id },
);

View File

@ -0,0 +1,433 @@
const express = require('express');
const Inventory_movementsService = require('../services/inventory_movements');
const Inventory_movementsDBApi = require('../db/api/inventory_movements');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('inventory_movements'));
/**
* @swagger
* components:
* schemas:
* Inventory_movements:
* type: object
* properties:
* note:
* type: string
* default: note
* change:
* type: integer
* format: int64
*
*/
/**
* @swagger
* tags:
* name: Inventory_movements
* description: The Inventory_movements managing API
*/
/**
* @swagger
* /api/inventory_movements:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory_movements"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Inventory_movementsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Inventory_movements"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await Inventory_movementsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_movements/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory_movements"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await Inventory_movementsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_movements/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await Inventory_movementsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_movements/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await Inventory_movementsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_movements:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Get all inventory_movements
* description: Get all inventory_movements
* responses:
* 200:
* description: Inventory_movements list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await Inventory_movementsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','note',
'change',
'date',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/inventory_movements/count:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Count all inventory_movements
* description: Count all inventory_movements
* responses:
* 200:
* description: Inventory_movements count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Inventory_movementsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory_movements/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Find all inventory_movements that match search criteria
* description: Find all inventory_movements that match search criteria
* responses:
* 200:
* description: Inventory_movements list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory_movements"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await Inventory_movementsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/inventory_movements/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory_movements]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory_movements"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Inventory_movementsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -26,8 +26,23 @@ router.use(checkCrudPermissions('order_items'));
* type: object
* properties:
* product_name:
* type: string
* default: product_name
* sku:
* type: string
* default: sku
* quantity:
* type: integer
* format: int64
* unit_price:
* type: integer
* format: int64
* total:
* type: integer
* format: int64
*/
@ -285,9 +300,9 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','product_name','sku',
'quantity',
'unit_price','total',
];
const opts = { fields };

View File

@ -26,9 +26,31 @@ router.use(checkCrudPermissions('orders'));
* type: object
* properties:
* order_number:
* type: string
* default: order_number
* shipping_address:
* type: string
* default: shipping_address
* billing_address:
* type: string
* default: billing_address
* subtotal:
* type: integer
* format: int64
* tax:
* type: integer
* format: int64
* shipping_fee:
* type: integer
* format: int64
* total:
* type: integer
* format: int64
*
*/
/**
@ -285,10 +307,10 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','order_number','shipping_address','billing_address',
'subtotal','tax','shipping_fee','total',
'order_date','delivery_date',
];
const opts = { fields };
try {

View File

@ -26,9 +26,20 @@ router.use(checkCrudPermissions('payments'));
* type: object
* properties:
* transaction_ref:
* type: string
* default: transaction_ref
* provider:
* type: string
* default: provider
* amount:
* type: integer
* format: int64
*
*
*/
/**
@ -285,10 +296,10 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','transaction_ref','provider',
'amount',
'paid_at',
];
const opts = { fields };
try {

View File

@ -26,8 +26,29 @@ router.use(checkCrudPermissions('products'));
* type: object
* properties:
* name:
* type: string
* default: name
* sku:
* type: string
* default: sku
* description:
* type: string
* default: description
* currency:
* type: string
* default: currency
* stock:
* type: integer
* format: int64
* price:
* type: integer
* format: int64
* cost:
* type: integer
* format: int64
*/
@ -285,9 +306,9 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','name','sku','description','currency',
'stock',
'price','cost',
];
const opts = { fields };

View File

@ -0,0 +1,436 @@
const express = require('express');
const ReturnsService = require('../services/returns');
const ReturnsDBApi = require('../db/api/returns');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('returns'));
/**
* @swagger
* components:
* schemas:
* Returns:
* type: object
* properties:
* rma_number:
* type: string
* default: rma_number
* reason:
* type: string
* default: reason
* refund_amount:
* type: integer
* format: int64
*
*/
/**
* @swagger
* tags:
* name: Returns
* description: The Returns managing API
*/
/**
* @swagger
* /api/returns:
* post:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Returns"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReturnsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Returns"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReturnsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/returns/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Returns"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await ReturnsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/returns/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await ReturnsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/returns/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await ReturnsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/returns:
* get:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Get all returns
* description: Get all returns
* responses:
* 200:
* description: Returns list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await ReturnsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','rma_number','reason',
'refund_amount',
'requested_at','processed_at',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/returns/count:
* get:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Count all returns
* description: Count all returns
* responses:
* 200:
* description: Returns count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await ReturnsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/returns/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Find all returns that match search criteria
* description: Find all returns that match search criteria
* responses:
* 200:
* description: Returns list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Returns"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await ReturnsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/returns/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Returns]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Returns"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ReturnsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -0,0 +1,435 @@
const express = require('express');
const ReviewsService = require('../services/reviews');
const ReviewsDBApi = require('../db/api/reviews');
const wrapAsync = require('../helpers').wrapAsync;
const router = express.Router();
const { parse } = require('json2csv');
const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('reviews'));
/**
* @swagger
* components:
* schemas:
* Reviews:
* type: object
* properties:
* author_name:
* type: string
* default: author_name
* comment:
* type: string
* default: comment
* rating:
* type: integer
* format: int64
*/
/**
* @swagger
* tags:
* name: Reviews
* description: The Reviews managing API
*/
/**
* @swagger
* /api/reviews:
* post:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Add new item
* description: Add new item
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Reviews"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*/
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReviewsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/budgets/bulk-import:
* post:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* data:
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Reviews"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
* description: Invalid input data
* 500:
* description: Some server error
*
*/
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await ReviewsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reviews/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to update
* required: true
* schema:
* type: string
* requestBody:
* description: Set new item data
* required: true
* content:
* application/json:
* schema:
* properties:
* id:
* description: ID of the updated item
* type: string
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Reviews"
* required:
* - id
* responses:
* 200:
* description: The item data was successfully updated
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await ReviewsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reviews/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
* - in: path
* name: id
* description: Item ID to delete
* required: true
* schema:
* type: string
* responses:
* 200:
* description: The item was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await ReviewsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reviews/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* ids:
* description: IDs of the updated items
* type: array
* responses:
* 200:
* description: The items was successfully deleted
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Items not found
* 500:
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await ReviewsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reviews:
* get:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Get all reviews
* description: Get all reviews
* responses:
* 200:
* description: Reviews list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await ReviewsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id','author_name','comment',
'rating',
'created',
];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
res.status(200).attachment(csv);
res.send(csv)
} catch (err) {
console.error(err);
}
} else {
res.status(200).send(payload);
}
}));
/**
* @swagger
* /api/reviews/count:
* get:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Count all reviews
* description: Count all reviews
* responses:
* 200:
* description: Reviews count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await ReviewsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
res.status(200).send(payload);
}));
/**
* @swagger
* /api/reviews/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Find all reviews that match search criteria
* description: Find all reviews that match search criteria
* responses:
* 200:
* description: Reviews list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Reviews"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Data not found
* 500:
* description: Some server error
*/
router.get('/autocomplete', async (req, res) => {
const payload = await ReviewsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
);
res.status(200).send(payload);
});
/**
* @swagger
* /api/reviews/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Reviews]
* summary: Get selected item
* description: Get selected item
* parameters:
* - in: path
* name: id
* description: ID of item to get
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Selected item successfully received
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Reviews"
* 400:
* description: Invalid ID supplied
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
* description: Item not found
* 500:
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ReviewsDBApi.findBy(
{ id: req.params.id },
);
res.status(200).send(payload);
}));
router.use('/', require('../helpers').commonErrorHandler);
module.exports = router;

View File

@ -26,9 +26,19 @@ router.use(checkCrudPermissions('shipments'));
* type: object
* properties:
* tracking_number:
* type: string
* default: tracking_number
* carrier:
* type: string
* default: carrier
* notes:
* type: string
* default: notes
*
*/
/**
@ -285,10 +295,10 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','tracking_number','carrier','notes',
'shipped_at','delivered_at',
];
const opts = { fields };
try {

View File

@ -26,6 +26,24 @@ router.use(checkCrudPermissions('suppliers'));
* type: object
* properties:
* name:
* type: string
* default: name
* contact_name:
* type: string
* default: contact_name
* email:
* type: string
* default: email
* phone:
* type: string
* default: phone
* address:
* type: string
* default: address
* notes:
* type: string
* default: notes
@ -285,7 +303,7 @@ router.get('/', wrapAsync(async (req, res) => {
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','name','contact_name','email','phone','address','notes',

View File

@ -1,8 +1,8 @@
const express = require('express');
const InventoryService = require('../services/inventory');
const InventoryDBApi = require('../db/api/inventory');
const TagsService = require('../services/tags');
const TagsDBApi = require('../db/api/tags');
const wrapAsync = require('../helpers').wrapAsync;
@ -15,17 +15,23 @@ const {
checkCrudPermissions,
} = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('inventory'));
router.use(checkCrudPermissions('tags'));
/**
* @swagger
* components:
* schemas:
* Inventory:
* Tags:
* type: object
* properties:
* name:
* type: string
* default: name
* color:
* type: string
* default: color
@ -34,17 +40,17 @@ router.use(checkCrudPermissions('inventory'));
/**
* @swagger
* tags:
* name: Inventory
* description: The Inventory managing API
* name: Tags
* description: The Tags managing API
*/
/**
* @swagger
* /api/inventory:
* /api/tags:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Add new item
* description: Add new item
* requestBody:
@ -56,14 +62,14 @@ router.use(checkCrudPermissions('inventory'));
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* responses:
* 200:
* description: The item was successfully added
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -74,7 +80,7 @@ router.use(checkCrudPermissions('inventory'));
router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await InventoryService.create(req.body.data, req.currentUser, true, link.host);
await TagsService.create(req.body.data, req.currentUser, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
@ -85,7 +91,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Bulk import items
* description: Bulk import items
* requestBody:
@ -98,14 +104,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items
* type: array
* items:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* responses:
* 200:
* description: The items were successfully imported
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 405:
@ -117,18 +123,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer);
await InventoryService.bulkImport(req, res, true, link.host);
await TagsService.bulkImport(req, res, true, link.host);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory/{id}:
* /api/tags/{id}:
* put:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Update the data of the selected item
* description: Update the data of the selected item
* parameters:
@ -151,7 +157,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data:
* description: Data of the updated item
* type: object
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* required:
* - id
* responses:
@ -160,7 +166,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 400:
* description: Invalid ID supplied
* 401:
@ -171,18 +177,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.put('/:id', wrapAsync(async (req, res) => {
await InventoryService.update(req.body.data, req.body.id, req.currentUser);
await TagsService.update(req.body.data, req.body.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory/{id}:
* /api/tags/{id}:
* delete:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Delete the selected item
* description: Delete the selected item
* parameters:
@ -198,7 +204,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 400:
* description: Invalid ID supplied
* 401:
@ -209,18 +215,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.delete('/:id', wrapAsync(async (req, res) => {
await InventoryService.remove(req.params.id, req.currentUser);
await TagsService.remove(req.params.id, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory/deleteByIds:
* /api/tags/deleteByIds:
* post:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Delete the selected item list
* description: Delete the selected item list
* requestBody:
@ -238,7 +244,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -247,29 +253,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error
*/
router.post('/deleteByIds', wrapAsync(async (req, res) => {
await InventoryService.deleteByIds(req.body.data, req.currentUser);
await TagsService.deleteByIds(req.body.data, req.currentUser);
const payload = true;
res.status(200).send(payload);
}));
/**
* @swagger
* /api/inventory:
* /api/tags:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory]
* summary: Get all inventory
* description: Get all inventory
* tags: [Tags]
* summary: Get all tags
* description: Get all tags
* responses:
* 200:
* description: Inventory list successfully received
* description: Tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -281,11 +287,11 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype
const currentUser = req.currentUser;
const payload = await InventoryDBApi.findAll(
const payload = await TagsDBApi.findAll(
req.query, { currentUser }
);
if (filetype && filetype === 'csv') {
const fields = ['id',
const fields = ['id','name','color',
@ -307,22 +313,22 @@ router.get('/', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/inventory/count:
* /api/tags/count:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory]
* summary: Count all inventory
* description: Count all inventory
* tags: [Tags]
* summary: Count all tags
* description: Count all tags
* responses:
* 200:
* description: Inventory count successfully received
* description: Tags count successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -333,7 +339,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await InventoryDBApi.findAll(
const payload = await TagsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
@ -344,22 +350,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/**
* @swagger
* /api/inventory/autocomplete:
* /api/tags/autocomplete:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory]
* summary: Find all inventory that match search criteria
* description: Find all inventory that match search criteria
* tags: [Tags]
* summary: Find all tags that match search criteria
* description: Find all tags that match search criteria
* responses:
* 200:
* description: Inventory list successfully received
* description: Tags list successfully received
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 401:
* $ref: "#/components/responses/UnauthorizedError"
* 404:
@ -369,7 +375,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/
router.get('/autocomplete', async (req, res) => {
const payload = await InventoryDBApi.findAllAutocomplete(
const payload = await TagsDBApi.findAllAutocomplete(
req.query.query,
req.query.limit,
req.query.offset,
@ -381,11 +387,11 @@ router.get('/autocomplete', async (req, res) => {
/**
* @swagger
* /api/inventory/{id}:
* /api/tags/{id}:
* get:
* security:
* - bearerAuth: []
* tags: [Inventory]
* tags: [Tags]
* summary: Get selected item
* description: Get selected item
* parameters:
@ -401,7 +407,7 @@ router.get('/autocomplete', async (req, res) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Inventory"
* $ref: "#/components/schemas/Tags"
* 400:
* description: Invalid ID supplied
* 401:
@ -412,7 +418,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error
*/
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await InventoryDBApi.findBy(
const payload = await TagsDBApi.findBy(
{ id: req.params.id },
);

View File

@ -1,5 +1,5 @@
const db = require('../db/models');
const InventoryDBApi = require('../db/api/inventory');
const DiscountsDBApi = require('../db/api/discounts');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
@ -11,11 +11,11 @@ const stream = require('stream');
module.exports = class InventoryService {
module.exports = class DiscountsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await InventoryDBApi.create(
await DiscountsDBApi.create(
data,
{
currentUser,
@ -51,7 +51,7 @@ module.exports = class InventoryService {
.on('error', (error) => reject(error));
})
await InventoryDBApi.bulkImport(results, {
await DiscountsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
@ -68,18 +68,18 @@ module.exports = class InventoryService {
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let inventory = await InventoryDBApi.findBy(
let discounts = await DiscountsDBApi.findBy(
{id},
{transaction},
);
if (!inventory) {
if (!discounts) {
throw new ValidationError(
'inventoryNotFound',
'discountsNotFound',
);
}
const updatedInventory = await InventoryDBApi.update(
const updatedDiscounts = await DiscountsDBApi.update(
id,
data,
{
@ -89,7 +89,7 @@ module.exports = class InventoryService {
);
await transaction.commit();
return updatedInventory;
return updatedDiscounts;
} catch (error) {
await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class InventoryService {
const transaction = await db.sequelize.transaction();
try {
await InventoryDBApi.deleteByIds(ids, {
await DiscountsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
@ -117,7 +117,7 @@ module.exports = class InventoryService {
const transaction = await db.sequelize.transaction();
try {
await InventoryDBApi.remove(
await DiscountsDBApi.remove(
id,
{
currentUser,

View File

@ -0,0 +1,138 @@
const db = require('../db/models');
const Inventory_movementsDBApi = require('../db/api/inventory_movements');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class Inventory_movementsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Inventory_movementsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
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")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await Inventory_movementsDBApi.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 {
let inventory_movements = await Inventory_movementsDBApi.findBy(
{id},
{transaction},
);
if (!inventory_movements) {
throw new ValidationError(
'inventory_movementsNotFound',
);
}
const updatedInventory_movements = await Inventory_movementsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedInventory_movements;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Inventory_movementsDBApi.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 Inventory_movementsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -1,6 +1,6 @@
const errors = {
app: {
title: 'StoreOps Manager',
title: 'Store Operations Manager',
},
auth: {

View File

@ -1,5 +1,5 @@
const db = require('../db/models');
const Product_variantsDBApi = require('../db/api/product_variants');
const ReturnsDBApi = require('../db/api/returns');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
@ -11,11 +11,11 @@ const stream = require('stream');
module.exports = class Product_variantsService {
module.exports = class ReturnsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await Product_variantsDBApi.create(
await ReturnsDBApi.create(
data,
{
currentUser,
@ -51,7 +51,7 @@ module.exports = class Product_variantsService {
.on('error', (error) => reject(error));
})
await Product_variantsDBApi.bulkImport(results, {
await ReturnsDBApi.bulkImport(results, {
transaction,
ignoreDuplicates: true,
validate: true,
@ -68,18 +68,18 @@ module.exports = class Product_variantsService {
static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction();
try {
let product_variants = await Product_variantsDBApi.findBy(
let returns = await ReturnsDBApi.findBy(
{id},
{transaction},
);
if (!product_variants) {
if (!returns) {
throw new ValidationError(
'product_variantsNotFound',
'returnsNotFound',
);
}
const updatedProduct_variants = await Product_variantsDBApi.update(
const updatedReturns = await ReturnsDBApi.update(
id,
data,
{
@ -89,7 +89,7 @@ module.exports = class Product_variantsService {
);
await transaction.commit();
return updatedProduct_variants;
return updatedReturns;
} catch (error) {
await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class Product_variantsService {
const transaction = await db.sequelize.transaction();
try {
await Product_variantsDBApi.deleteByIds(ids, {
await ReturnsDBApi.deleteByIds(ids, {
currentUser,
transaction,
});
@ -117,7 +117,7 @@ module.exports = class Product_variantsService {
const transaction = await db.sequelize.transaction();
try {
await Product_variantsDBApi.remove(
await ReturnsDBApi.remove(
id,
{
currentUser,

View File

@ -0,0 +1,138 @@
const db = require('../db/models');
const ReviewsDBApi = require('../db/api/reviews');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class ReviewsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ReviewsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
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")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await ReviewsDBApi.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 {
let reviews = await ReviewsDBApi.findBy(
{id},
{transaction},
);
if (!reviews) {
throw new ValidationError(
'reviewsNotFound',
);
}
const updatedReviews = await ReviewsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedReviews;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await ReviewsDBApi.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 ReviewsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -66,52 +66,193 @@ module.exports = class SearchService {
"customers": [
"name",
"email",
"phone",
"company",
"address",
"notes",
],
"categories": [
"name",
"description",
],
"suppliers": [
"name",
"contact_name",
"email",
"phone",
"address",
"notes",
],
"tags": [
"name",
"color",
],
"products": [
"name",
"sku",
"description",
"currency",
],
"inventory_movements": [
"note",
],
"reviews": [
"author_name",
"comment",
],
"discounts": [
"code",
"description",
],
"orders": [
"order_number",
"shipping_address",
"billing_address",
],
"order_items": [
"product_name",
"sku",
],
"payments": [
"transaction_ref",
"provider",
],
"shipments": [
"tracking_number",
"carrier",
"notes",
],
"returns": [
"rma_number",
"reason",
],
};
const columnsInt = {
@ -124,6 +265,11 @@ module.exports = class SearchService {
"customers": [
"loyalty_points",
],
@ -141,6 +287,85 @@ module.exports = class SearchService {
"products": [
"price",
"cost",
"stock",
],
"inventory_movements": [
"change",
],
"reviews": [
"rating",
],
"discounts": [
"value",
],
"orders": [
"subtotal",
"tax",
"shipping_fee",
"total",
],
"order_items": [
"quantity",
"unit_price",
"total",
],
"payments": [
"amount",
],
@ -150,15 +375,11 @@ module.exports = class SearchService {
"returns": [
"refund_amount",
],
};

View File

@ -0,0 +1,138 @@
const db = require('../db/models');
const TagsDBApi = require('../db/api/tags');
const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
module.exports = class TagsService {
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await TagsDBApi.create(
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async bulkImport(req, res, sendInvitationEmails = true, host) {
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")); // convert Buffer to Stream
await new Promise((resolve, reject) => {
bufferStream
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', async () => {
console.log('CSV results', results);
resolve();
})
.on('error', (error) => reject(error));
})
await TagsDBApi.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 {
let tags = await TagsDBApi.findBy(
{id},
{transaction},
);
if (!tags) {
throw new ValidationError(
'tagsNotFound',
);
}
const updatedTags = await TagsDBApi.update(
id,
data,
{
currentUser,
transaction,
},
);
await transaction.commit();
return updatedTags;
} catch (error) {
await transaction.rollback();
throw error;
}
};
static async deleteByIds(ids, currentUser) {
const transaction = await db.sequelize.transaction();
try {
await TagsDBApi.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 TagsDBApi.remove(
id,
{
currentUser,
transaction,
},
);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
}
};

View File

@ -25,7 +25,7 @@ services:
- ./data/db:/var/lib/postgresql/data
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
- POSTGRES_DB=db_storeops_manager
- POSTGRES_DB=db_store_operations_manager
ports:
- "5432:5432"
logging:

View File

@ -1,4 +1,4 @@
# StoreOps Manager
# Store Operations Manager
## This project was generated by Flatlogic Platform.
## Install

View File

@ -1,7 +1,7 @@
import type { ColorButtonKey } from './interfaces'
export const gradientBgBase = 'bg-gradient-to-tr'
export const colorBgBase = "bg-skyBlueTheme-mainBG"
export const colorBgBase = "bg-violet-50/50"
export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`
export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}`
export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`;
@ -9,7 +9,7 @@ export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to
export const colorsBgLight = {
white: 'bg-white text-black',
light: ' bg-skyBlueTheme-outsideCardColor text-primaryText text-primaryText dark:bg-dark-900 dark:text-white',
light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white',
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
danger: 'bg-red-500 border-red-500 text-white',
@ -19,7 +19,7 @@ export const colorsBgLight = {
export const colorsText = {
white: 'text-black dark:text-slate-100',
light: 'text-primaryText dark:text-slate-400',
light: 'text-gray-700 dark:text-slate-400',
contrast: 'dark:text-white',
success: 'text-emerald-500',
danger: 'text-red-500',
@ -50,13 +50,13 @@ export const getButtonColor = (
const colors = {
ring: {
white: 'ring-gray-200 dark:ring-gray-500',
whiteDark: 'ring-skyBlueTheme-outsideCardColor dark:ring-dark-500',
whiteDark: 'ring-gray-200 dark:ring-dark-500',
lightDark: 'ring-gray-200 dark:ring-gray-500',
contrast: 'ring-gray-300 dark:ring-gray-400',
success: 'ring-emerald-300 dark:ring-pavitra-blue',
danger: 'ring-red-300 dark:ring-red-700',
warning: 'ring-yellow-300 dark:ring-yellow-700',
info: "ring-skyBlueTheme-buttonColor dark:ring-pavitra-blue",
info: "ring-blue-300 dark:ring-pavitra-blue",
},
active: {
white: 'bg-gray-100',
@ -66,21 +66,21 @@ export const getButtonColor = (
success: 'bg-emerald-700 dark:bg-pavitra-blue',
danger: 'bg-red-700 dark:bg-red-600',
warning: 'bg-yellow-700 dark:bg-yellow-600',
info: 'bg-skyBlueTheme-buttonColor dark:bg-pavitra-blue',
info: 'bg-blue-700 dark:bg-pavitra-blue',
},
bg: {
white: 'bg-white text-black',
whiteDark: 'bg-skyBlueTheme-outsideCardColor text-primaryText dark:bg-dark-900 dark:text-white',
whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white',
lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white',
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
success: 'bg-emerald-600 dark:bg-pavitra-blue text-white',
danger: 'bg-skyBlueTheme-outsideCardColor text-red-500 dark:text-white dark:bg-red-500 ',
danger: 'bg-red-600 text-white dark:bg-red-500 ',
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
info: " bg-skyBlueTheme-buttonColor dark:bg-pavitra-blue text-white ",
info: " bg-blue-600 dark:bg-pavitra-blue text-white ",
},
bgHover: {
white: 'hover:bg-gray-100',
whiteDark: 'hover:bg-skyBlueTheme-outsideCardColor hover:dark:bg-dark-800',
whiteDark: 'hover:bg-gray-100 hover:dark:bg-dark-800',
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100',
success:
@ -89,24 +89,24 @@ export const getButtonColor = (
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
warning:
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
info: "hover:bg-skyBlueTheme-800 hover:border-skyBlueTheme-buttonColor hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
},
borders: {
white: 'border-white',
whiteDark: 'border-skyBlueTheme-outsideCardColor dark:border-dark-900',
whiteDark: 'border-white dark:border-dark-900',
lightDark: 'border-gray-100 dark:border-slate-800',
contrast: 'border-gray-800 dark:border-white',
success: 'border-emerald-600 dark:border-pavitra-blue',
danger: 'border-red-600 dark:border-red-500',
warning: 'border-yellow-600 dark:border-yellow-500',
info: "border-skyBlueTheme-buttonColor border-blue-600 dark:border-pavitra-blue",
info: "border-blue-600 border-blue-600 dark:border-pavitra-blue",
},
text: {
contrast: 'dark:text-slate-100',
success: 'text-emerald-600 dark:text-pavitra-blue',
danger: 'text-red-600 dark:text-red-500',
warning: 'text-yellow-600 dark:text-yellow-500',
info: ' dark:text-pavitra-blue',
info: 'text-blue-600 dark:text-pavitra-blue',
},
outlineHover: {
contrast:
@ -116,7 +116,7 @@ export const getButtonColor = (
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
warning:
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
info: "hover:bg-skyBlueTheme-buttonColor text-skyBlueTheme-buttonColor hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
},
}

View File

@ -39,7 +39,7 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
>
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
<b className="font-black">StoreOps Manager</b>
<b className="font-black">Store Operations Manager</b>
</div>

View File

@ -76,6 +76,42 @@ const CardCategories = ({
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>CategoryName</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.name }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Description</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.description }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ParentCategory</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.categoriesOneListFormatter(item.parent) }
</div>
</dd>
</div>
</dl>
</li>
))}

View File

@ -37,7 +37,7 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
{!loading && categories.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/categories/categories-view/?id=${item.id}`}
@ -46,6 +46,30 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>CategoryName</p>
<p className={'line-clamp-2'}>{ item.name }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Description</p>
<p className={'line-clamp-2'}>{ item.description }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ParentCategory</p>
<p className={'line-clamp-2'}>{ dataFormatter.categoriesOneListFormatter(item.parent) }</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}

View File

@ -18,7 +18,7 @@ import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
import CardCategories from './CardCategories';
import ListCategories from './ListCategories';
const perPage = 10
@ -414,13 +414,13 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
<div className="flex">
<BaseButton
className="my-2 mr-3"
type='submit' color='info'
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
type='reset' color='info' outline
color='info'
label='Cancel'
onClick={handleReset}
/>
@ -443,7 +443,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
{categories && Array.isArray(categories) && !showGrid && (
<CardCategories
<ListCategories
categories={categories}
loading={loading}
onDelete={handleDeleteModalAction}

View File

@ -41,6 +41,58 @@ export const loadColumns = async (
return [
{
field: 'name',
headerName: 'CategoryName',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'description',
headerName: 'Description',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'parent',
headerName: 'ParentCategory',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
sortable: false,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('categories'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'actions',
type: 'actions',

View File

@ -76,6 +76,90 @@ const CardCustomers = ({
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>CustomerName</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.name }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Email</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.email }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Phone</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.phone }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Company</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.company }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Address</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.address }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Notes</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.notes }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>LoyaltyPoints</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.loyalty_points }
</div>
</dd>
</div>
</dl>
</li>
))}

View File

@ -37,7 +37,7 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
{!loading && customers.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/customers/customers-view/?id=${item.id}`}
@ -46,6 +46,62 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>CustomerName</p>
<p className={'line-clamp-2'}>{ item.name }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Email</p>
<p className={'line-clamp-2'}>{ item.email }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Phone</p>
<p className={'line-clamp-2'}>{ item.phone }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Company</p>
<p className={'line-clamp-2'}>{ item.company }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Address</p>
<p className={'line-clamp-2'}>{ item.address }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Notes</p>
<p className={'line-clamp-2'}>{ item.notes }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>LoyaltyPoints</p>
<p className={'line-clamp-2'}>{ item.loyalty_points }</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}

View File

@ -18,8 +18,6 @@ import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
import ListCustomers from './ListCustomers';
const perPage = 10
@ -414,13 +412,13 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
<div className="flex">
<BaseButton
className="my-2 mr-3"
type='submit' color='info'
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
type='reset' color='info' outline
color='info'
label='Cancel'
onClick={handleReset}
/>
@ -442,21 +440,10 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
</CardBoxModal>
{customers && Array.isArray(customers) && !showGrid && (
<ListCustomers
customers={customers}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{dataGrid}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(

View File

@ -41,6 +41,112 @@ export const loadColumns = async (
return [
{
field: 'name',
headerName: 'CustomerName',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'email',
headerName: 'Email',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'phone',
headerName: 'Phone',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'company',
headerName: 'Company',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'address',
headerName: 'Address',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'notes',
headerName: 'Notes',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'loyalty_points',
headerName: 'LoyaltyPoints',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'actions',
type: 'actions',

View File

@ -0,0 +1,183 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
discounts: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardDiscounts = ({
discounts,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DISCOUNTS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && discounts.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/discounts/discounts-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.code}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/discounts/discounts-edit/?id=${item.id}`}
pathView={`/discounts/discounts-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Code</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.code }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Description</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.description }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Type</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.type }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Value</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.value }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Active</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.booleanFormatter(item.active) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>StartsAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.starts) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>EndsAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.ends) }
</div>
</dd>
</div>
</dl>
</li>
))}
{!loading && discounts.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardDiscounts;

View File

@ -0,0 +1,136 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
discounts: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListDiscounts = ({ discounts, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DISCOUNTS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && discounts.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/discounts/discounts-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Code</p>
<p className={'line-clamp-2'}>{ item.code }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Description</p>
<p className={'line-clamp-2'}>{ item.description }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Type</p>
<p className={'line-clamp-2'}>{ item.type }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Value</p>
<p className={'line-clamp-2'}>{ item.value }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Active</p>
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.active) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>StartsAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.starts) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>EndsAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.ends) }</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/discounts/discounts-edit/?id=${item.id}`}
pathView={`/discounts/discounts-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && discounts.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListDiscounts

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/product_variants/product_variantsSlice'
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/discounts/discountsSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureProduct_variantsCols";
import {loadColumns} from "./configureDiscountsCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles";
const perPage = 10
const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, showGrid }) => {
const TableSampleDiscounts = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
@ -40,7 +40,7 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
},
]);
const { product_variants, loading, count, notify: product_variantsNotify, refetch } = useAppSelector((state) => state.product_variants)
const { discounts, loading, count, notify: discountsNotify, refetch } = useAppSelector((state) => state.discounts)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -60,10 +60,10 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
};
useEffect(() => {
if (product_variantsNotify.showNotification) {
notify(product_variantsNotify.typeNotification, product_variantsNotify.textNotification);
if (discountsNotify.showNotification) {
notify(discountsNotify.typeNotification, discountsNotify.textNotification);
}
}, [product_variantsNotify.showNotification]);
}, [discountsNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
@ -177,7 +177,7 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
loadColumns(
handleDeleteModalAction,
`product_variants`,
`discounts`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
@ -215,7 +215,7 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={product_variants ?? []}
rows={discounts ?? []}
columns={columns}
initialState={{
pagination: {
@ -412,13 +412,13 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
<div className="flex">
<BaseButton
className="my-2 mr-3"
type='submit' color='info'
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
type='reset' color='info' outline
color='info'
label='Cancel'
onClick={handleReset}
/>
@ -460,4 +460,4 @@ const TableSampleProduct_variants = ({ filterItems, setFilterItems, filters, sho
)
}
export default TableSampleProduct_variants
export default TableSampleDiscounts

View File

@ -0,0 +1,181 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_DISCOUNTS')
return [
{
field: 'code',
headerName: 'Code',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'description',
headerName: 'Description',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'type',
headerName: 'Type',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'value',
headerName: 'Value',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'active',
headerName: 'Active',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'boolean',
},
{
field: 'starts',
headerName: 'StartsAt',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.starts),
},
{
field: 'ends',
headerName: 'EndsAt',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.ends),
},
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/discounts/discounts-edit/?id=${params?.row?.id}`}
pathView={`/discounts/discounts-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -38,9 +38,9 @@ const FormField = ({ icons = [], ...props }: Props) => {
`${focusRing}`,
props.hasTextareaHeight ? 'h-24' : 'h-12',
props.isBorderless ? 'border-0' : 'border',
props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? ` bg-skyBlueTheme-websiteBG ` : bgColor} dark:bg-dark-800`,
props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? ` bg-white` : bgColor} dark:bg-dark-800`,
props.disabled ? 'bg-gray-200 text-gray-100 dark:bg-dark-900 disabled' : '',
props.borderButtom ? `border-0 border-b ${props.diversity ? "placeholder-primaryText border-primaryText" : " placeholder-white border-white "} rounded-none focus:ring-0` : '',
props.borderButtom ? `border-0 border-b ${props.diversity ? "border-gray-400" : " placeholder-white border-gray-300/10 border-white "} rounded-none focus:ring-0` : '',
].join(' ');
return (

View File

@ -77,7 +77,7 @@ const FormImagePicker = ({ label, icon, accept, color, isRoundIcon, path, schema
/>
</label>
{showFilename && !loading && (
<div className={` ${cornersRight} px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 `}>
<div className={` ${cornersRight} px-4 py-2 max-w-full flex-grow-0 overflow-x-hidden ${bgColor} dark:bg-slate-800 border-gray-200 dark:border-slate-700 border `}>
<span className='text-ellipsis max-w-full line-clamp-1'>
{file.name}
</span>

View File

@ -34,7 +34,7 @@ export default function ImageField({
className={`rounded-full block h-auto w-full max-w-full bg-gray-100 dark:bg-dark-900 ${imageClassName}`}
/>
) : (
<div className={'flex h-full dark:bg-dark-900/70'}>
<div className={'flex h-full bg-slate-100 dark:bg-dark-900/70'}>
<BaseIcon
className='text-black dark:text-white'
w='w-full'

View File

@ -1,99 +0,0 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
inventory: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardInventory = ({
inventory,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INVENTORY')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && inventory.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/inventory/inventory-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.location}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/inventory/inventory-edit/?id=${item.id}`}
pathView={`/inventory/inventory-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
</dl>
</li>
))}
{!loading && inventory.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardInventory;

View File

@ -0,0 +1,159 @@
import React from 'react';
import ImageField from '../ImageField';
import ListActionsPopover from '../ListActionsPopover';
import { useAppSelector } from '../../stores/hooks';
import dataFormatter from '../../helpers/dataFormatter';
import { Pagination } from '../Pagination';
import {saveFile} from "../../helpers/fileSaver";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
inventory_movements: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const CardInventory_movements = ({
inventory_movements,
loading,
onDelete,
currentPage,
numPages,
onPageChange,
}: Props) => {
const asideScrollbarsStyle = useAppSelector(
(state) => state.style.asideScrollbarsStyle,
);
const bgColor = useAppSelector((state) => state.style.cardsColor);
const darkMode = useAppSelector((state) => state.style.darkMode);
const corners = useAppSelector((state) => state.style.corners);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INVENTORY_MOVEMENTS')
return (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && inventory_movements.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/inventory_movements/inventory_movements-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.note}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/inventory_movements/inventory_movements-edit/?id=${item.id}`}
pathView={`/inventory_movements/inventory_movements-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Note</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.note }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Product</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.productsOneListFormatter(item.product) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>QuantityChange</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.change }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Reason</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.reason }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Date</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.date) }
</div>
</dd>
</div>
</dl>
</li>
))}
{!loading && inventory_movements.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardInventory_movements;

View File

@ -0,0 +1,120 @@
import React from 'react';
import CardBox from '../CardBox';
import ImageField from '../ImageField';
import dataFormatter from '../../helpers/dataFormatter';
import {saveFile} from "../../helpers/fileSaver";
import ListActionsPopover from "../ListActionsPopover";
import {useAppSelector} from "../../stores/hooks";
import {Pagination} from "../Pagination";
import LoadingSpinner from "../LoadingSpinner";
import Link from 'next/link';
import {hasPermission} from "../../helpers/userPermissions";
type Props = {
inventory_movements: any[];
loading: boolean;
onDelete: (id: string) => void;
currentPage: number;
numPages: number;
onPageChange: (page: number) => void;
};
const ListInventory_movements = ({ inventory_movements, loading, onDelete, currentPage, numPages, onPageChange }: Props) => {
const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INVENTORY_MOVEMENTS')
const corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor);
return (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && inventory_movements.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/inventory_movements/inventory_movements-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Note</p>
<p className={'line-clamp-2'}>{ item.note }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Product</p>
<p className={'line-clamp-2'}>{ dataFormatter.productsOneListFormatter(item.product) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>QuantityChange</p>
<p className={'line-clamp-2'}>{ item.change }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Reason</p>
<p className={'line-clamp-2'}>{ item.reason }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Date</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.date) }</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/inventory_movements/inventory_movements-edit/?id=${item.id}`}
pathView={`/inventory_movements/inventory_movements-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && inventory_movements.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListInventory_movements

View File

@ -0,0 +1,476 @@
import React, { useEffect, useState, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox";
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/inventory_movements/inventory_movementsSlice'
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik";
import {
DataGrid,
GridColDef,
} from '@mui/x-data-grid';
import {loadColumns} from "./configureInventory_movementsCols";
import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles";
import ListInventory_movements from './ListInventory_movements';
const perPage = 10
const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, showGrid }) => {
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch();
const router = useRouter();
const pagesList = [];
const [id, setId] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
const [filterRequest, setFilterRequest] = React.useState('');
const [columns, setColumns] = useState<GridColDef[]>([]);
const [selectedRows, setSelectedRows] = useState([]);
const [sortModel, setSortModel] = useState([
{
field: '',
sort: 'desc',
},
]);
const { inventory_movements, loading, count, notify: inventory_movementsNotify, refetch } = useAppSelector((state) => state.inventory_movements)
const { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const corners = useAppSelector((state) => state.style.corners);
const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
for (let i = 0; i < numPages; i++) {
pagesList.push(i);
}
const loadData = async (page = currentPage, request = filterRequest) => {
if (page !== currentPage) setCurrentPage(page);
if (request !== filterRequest) setFilterRequest(request);
const { sort, field } = sortModel[0];
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
dispatch(fetch({ limit: perPage, page, query }));
};
useEffect(() => {
if (inventory_movementsNotify.showNotification) {
notify(inventory_movementsNotify.typeNotification, inventory_movementsNotify.textNotification);
}
}, [inventory_movementsNotify.showNotification]);
useEffect(() => {
if (!currentUser) return;
loadData();
}, [sortModel, currentUser]);
useEffect(() => {
if (refetch) {
loadData(0);
dispatch(setRefetch(false));
}
}, [refetch, dispatch]);
const [isModalInfoActive, setIsModalInfoActive] = useState(false)
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
const handleModalAction = () => {
setIsModalInfoActive(false)
setIsModalTrashActive(false)
}
const handleDeleteModalAction = (id: string) => {
setId(id)
setIsModalTrashActive(true)
}
const handleDeleteAction = async () => {
if (id) {
await dispatch(deleteItem(id));
await loadData(0);
setIsModalTrashActive(false);
}
};
const generateFilterRequests = useMemo(() => {
let request = '&';
filterItems.forEach((item) => {
const isRangeFilter = filters.find(
(filter) =>
filter.title === item.fields.selectedField &&
(filter.number || filter.date),
);
if (isRangeFilter) {
const from = item.fields.filterValueFrom;
const to = item.fields.filterValueTo;
if (from) {
request += `${item.fields.selectedField}Range=${from}&`;
}
if (to) {
request += `${item.fields.selectedField}Range=${to}&`;
}
} else {
const value = item.fields.filterValue;
if (value) {
request += `${item.fields.selectedField}=${value}&`;
}
}
});
return request;
}, [filterItems, filters]);
const deleteFilter = (value) => {
const newItems = filterItems.filter((item) => item.id !== value);
if (newItems.length) {
setFilterItems(newItems);
} else {
loadData(0, '');
setFilterItems(newItems);
}
};
const handleSubmit = () => {
loadData(0, generateFilterRequests);
};
const handleChange = (id) => (e) => {
const value = e.target.value;
const name = e.target.name;
setFilterItems(
filterItems.map((item) => {
if (item.id !== id) return item;
if (name === 'selectedField') return { id, fields: { [name]: value } };
return { id, fields: { ...item.fields, [name]: value } }
}),
);
};
const handleReset = () => {
setFilterItems([]);
loadData(0, '');
};
const onPageChange = (page: number) => {
loadData(page);
setCurrentPage(page);
};
useEffect(() => {
if (!currentUser) return;
loadColumns(
handleDeleteModalAction,
`inventory_movements`,
currentUser,
).then((newCols) => setColumns(newCols));
}, [currentUser]);
const handleTableSubmit = async (id: string, data) => {
if (!_.isEmpty(data)) {
await dispatch(update({ id, data }))
.unwrap()
.then((res) => res)
.catch((err) => {
throw new Error(err);
});
}
};
const onDeleteRows = async (selectedRows) => {
await dispatch(deleteItemsByIds(selectedRows));
await loadData(0);
};
const controlClasses =
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
` ${bgColor} ${focusRing} ${corners} ` +
'dark:bg-slate-800 border';
const dataGrid = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `datagrid--row`}
rows={inventory_movements ?? []}
columns={columns}
initialState={{
pagination: {
paginationModel: {
pageSize: 10,
},
},
}}
disableRowSelectionOnClick
onProcessRowUpdateError={(params) => {
console.log('Error', params);
}}
processRowUpdate={async (newRow, oldRow) => {
const data = dataFormatter.dataGridEditFormatter(newRow);
try {
await handleTableSubmit(newRow.id, data);
return newRow;
} catch {
return oldRow;
}
}}
sortingMode={'server'}
checkboxSelection
onRowSelectionModelChange={(ids) => {
setSelectedRows(ids)
}}
onSortModelChange={(params) => {
params.length
? setSortModel(params)
: setSortModel([{ field: '', sort: 'desc' }]);
}}
rowCount={count}
pageSizeOptions={[10]}
paginationMode={'server'}
loading={loading}
onPaginationModelChange={(params) => {
onPageChange(params.page);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
return (
<div key={filterItem.id} className="flex mb-4">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Filter</div>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.type === 'enum' ? (
<div className="flex flex-col w-full mr-3">
<div className="text-gray-500 font-bold">
Value
</div>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.number ? (
<div className="flex flex-row w-full mr-3">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">From</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className="flex flex-col w-full">
<div className=" text-gray-500 font-bold">To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.date ? (
<div className='flex flex-row w-full mr-3'>
<div className='flex flex-col w-full mr-3'>
<div className=' text-gray-500 font-bold'>
From
</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className='flex flex-col w-full'>
<div className=' text-gray-500 font-bold'>To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{inventory_movements && Array.isArray(inventory_movements) && !showGrid && (
<ListInventory_movements
inventory_movements={inventory_movements}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleInventory_movements

View File

@ -0,0 +1,154 @@
import React from 'react';
import BaseIcon from '../BaseIcon';
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
import axios from 'axios';
import {
GridActionsCellItem,
GridRowParams,
GridValueGetterParams,
} from '@mui/x-data-grid';
import ImageField from '../ImageField';
import {saveFile} from "../../helpers/fileSaver";
import dataFormatter from '../../helpers/dataFormatter'
import DataGridMultiSelect from "../DataGridMultiSelect";
import ListActionsPopover from '../ListActionsPopover';
import {hasPermission} from "../../helpers/userPermissions";
type Params = (id: string) => void;
export const loadColumns = async (
onDelete: Params,
entityName: string,
user
) => {
async function callOptionsApi(entityName: string) {
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
try {
const data = await axios(`/${entityName}/autocomplete?limit=100`);
return data.data;
} catch (error) {
console.log(error);
return [];
}
}
const hasUpdatePermission = hasPermission(user, 'UPDATE_INVENTORY_MOVEMENTS')
return [
{
field: 'note',
headerName: 'Note',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'product',
headerName: 'Product',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
sortable: false,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('products'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'change',
headerName: 'QuantityChange',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'reason',
headerName: 'Reason',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'date',
headerName: 'Date',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.date),
},
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/inventory_movements/inventory_movements-edit/?id=${params?.row?.id}`}
pathView={`/inventory_movements/inventory_movements-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -34,7 +34,7 @@ const KanbanCard = ({
<div
ref={drag}
className={
`bg-skyBlueTheme-cardColor dark:bg-dark-800 rounded-md space-y-2 p-4 relative ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
`bg-gray-50 dark:bg-dark-800 rounded-md space-y-2 p-4 relative ${isDragging ? 'cursor-grabbing' : 'cursor-grab'}`
}
>
<div className={'flex items-center justify-between'}>

View File

@ -188,7 +188,7 @@ const KanbanColumn = ({
</div>
))}
{!data?.length && (
<p className={'text-center py-8 bg-skyBlueTheme-cardColor dark:bg-dark-800'}>No data</p>
<p className={'text-center py-8 bg-gray-50 dark:bg-dark-800'}>No data</p>
)}
</div>
</CardBox>

View File

@ -53,7 +53,7 @@ const ListActionsPopover = ({
size={'small'}
>
<BaseIcon
className={`text-primaryText dark:text-white ${iconClassName}`}
className={`text-black dark:text-white ${iconClassName}`}
w='w-10'
h='h-10'
size={24}

View File

@ -8,7 +8,7 @@ const LoadingSpinner = () => {
className='w-12 h-12 rounded-full absolute border-4 border-solid border-gray-200 dark:border-slate-800'
></div>
<div
className="w-12 h-12 rounded-full animate-spin absolute border-4 border-solid border-skyBlueTheme-iconsColor dark:border-blue-500 border-t-transparent"
className="w-12 h-12 rounded-full animate-spin absolute border-4 border-solid border-blue-500 dark:border-blue-500 border-t-transparent"
></div>
</div>
</div>

View File

@ -37,7 +37,7 @@ export default function NavBar({ menu, className = '', children }: Props) {
<nav
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 transition-position w-screen lg:w-auto dark:bg-dark-800`}
>
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-skyBlueTheme-outsideCardColor dark:border-dark-700`}`}>
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-pavitra-400 dark:border-dark-700`}`}>
<div className="flex flex-1 items-stretch h-14">{children}</div>
<div className="flex-none items-stretch flex h-14 lg:hidden">
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>

View File

@ -106,7 +106,7 @@ export default function NavBarItem({ item }: Props) {
<div
className={`${
!isDropdownActive ? 'lg:hidden' : ''
} text-sm border-b border-gray-100 lg:border lg:bg-skyBlueTheme-cardColor lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`}
} text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`}
>
<ClickOutside onClickOutside={() => setIsDropdownActive(false)} excludedElements={[excludedRef]}>
<NavBarMenuList menu={item.menu} />

View File

@ -58,7 +58,7 @@ const CardOrder_items = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/order_items/order_items-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.name}
{item.product_name}
</Link>
@ -76,6 +76,78 @@ const CardOrder_items = ({
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ProductName</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.product_name }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Product</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.productsOneListFormatter(item.product) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Quantity</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.quantity }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>UnitPrice</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.unit_price }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Total</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.total }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>SKU</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.sku }
</div>
</dd>
</div>
</dl>
</li>
))}

View File

@ -37,7 +37,7 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
{!loading && order_items.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex ${bgColor} ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/order_items/order_items-view/?id=${item.id}`}
@ -46,6 +46,54 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ProductName</p>
<p className={'line-clamp-2'}>{ item.product_name }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Product</p>
<p className={'line-clamp-2'}>{ dataFormatter.productsOneListFormatter(item.product) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Quantity</p>
<p className={'line-clamp-2'}>{ item.quantity }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>UnitPrice</p>
<p className={'line-clamp-2'}>{ item.unit_price }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Total</p>
<p className={'line-clamp-2'}>{ item.total }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>SKU</p>
<p className={'line-clamp-2'}>{ item.sku }</p>
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}

View File

@ -412,13 +412,13 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid
<div className="flex">
<BaseButton
className="my-2 mr-3"
type='submit' color='info'
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
type='reset' color='info' outline
color='info'
label='Cancel'
onClick={handleReset}
/>

View File

@ -41,6 +41,106 @@ export const loadColumns = async (
return [
{
field: 'product_name',
headerName: 'ProductName',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'product',
headerName: 'Product',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
sortable: false,
type: 'singleSelect',
getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('products'),
valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value,
},
{
field: 'quantity',
headerName: 'Quantity',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'unit_price',
headerName: 'UnitPrice',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'total',
headerName: 'Total',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'sku',
headerName: 'SKU',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'actions',
type: 'actions',

View File

@ -76,6 +76,186 @@ const CardOrders = ({
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>OrderNumber</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.order_number }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Customer</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.customersOneListFormatter(item.customer) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.status }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>OrderDate</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.order_date) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>DeliveryDate</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.delivery_date) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Subtotal</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.subtotal }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Tax</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.tax }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ShippingFee</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.shipping_fee }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Total</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.total }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ShippingAddress</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.shipping_address }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>BillingAddress</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.billing_address }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>OrderItems</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.order_itemsManyListFormatter(item.items).join(', ')}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Payments</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.paymentsManyListFormatter(item.payments).join(', ')}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Shipments</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.shipmentsManyListFormatter(item.shipments).join(', ')}
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>AssignedTo</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.usersOneListFormatter(item.assigned_to) }
</div>
</dd>
</div>
</dl>
</li>
))}

Some files were not shown because too many files have changed in this diff Show More