@withvlibe/base-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{VlibeBaseAuth-ChuzgzDl.d.mts → VlibeBaseEcommerce-B6l17uup.d.mts} +204 -9
- package/dist/{VlibeBaseAuth-ChuzgzDl.d.ts → VlibeBaseEcommerce-B6l17uup.d.ts} +204 -9
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +534 -1
- package/dist/index.mjs +533 -1
- package/dist/react.d.mts +107 -3
- package/dist/react.d.ts +107 -3
- package/dist/react.js +173 -2
- package/dist/react.mjs +169 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
VlibeBaseAuth: () => VlibeBaseAuth,
|
|
24
24
|
VlibeBaseDatabase: () => VlibeBaseDatabase,
|
|
25
|
+
VlibeBaseEcommerce: () => VlibeBaseEcommerce,
|
|
25
26
|
VlibeBasePayments: () => VlibeBasePayments
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -59,6 +60,21 @@ var VlibeBaseDatabase = class {
|
|
|
59
60
|
this.baseUrl = resolveBaseUrl(config.baseUrl);
|
|
60
61
|
this.supabaseUrl = config.supabaseUrl || DEFAULT_SUPABASE_URL;
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the full table name with unique project prefix
|
|
65
|
+
* Uses the unique timestamp portion of the project ID to avoid collisions
|
|
66
|
+
*/
|
|
67
|
+
getFullTableName(tableName) {
|
|
68
|
+
const parts = this.projectId.split("-");
|
|
69
|
+
const lastPart = parts[parts.length - 1];
|
|
70
|
+
let prefix;
|
|
71
|
+
if (/^\d+$/.test(lastPart) && lastPart.length >= 8) {
|
|
72
|
+
prefix = lastPart.slice(0, 8);
|
|
73
|
+
} else {
|
|
74
|
+
prefix = this.projectId.replace(/^(vlibe-brief-|proj-|project-)/, "").slice(0, 8).toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
75
|
+
}
|
|
76
|
+
return `proj_${prefix}_${tableName.toLowerCase().replace(/[^a-z0-9_]/g, "")}`;
|
|
77
|
+
}
|
|
62
78
|
/**
|
|
63
79
|
* Initialize Supabase client for real-time subscriptions
|
|
64
80
|
*/
|
|
@@ -256,7 +272,7 @@ var VlibeBaseDatabase = class {
|
|
|
256
272
|
subscribe(collection, callback) {
|
|
257
273
|
this.initSupabase();
|
|
258
274
|
const channelName = `base:${this.projectId}:${collection}`;
|
|
259
|
-
const tableName =
|
|
275
|
+
const tableName = this.getFullTableName(collection);
|
|
260
276
|
const channel = this.supabase.channel(channelName).on(
|
|
261
277
|
"postgres_changes",
|
|
262
278
|
{
|
|
@@ -694,9 +710,526 @@ var VlibeBasePayments = class {
|
|
|
694
710
|
return this.baseUrl;
|
|
695
711
|
}
|
|
696
712
|
};
|
|
713
|
+
|
|
714
|
+
// src/VlibeBaseEcommerce.ts
|
|
715
|
+
var VlibeBaseEcommerce = class {
|
|
716
|
+
constructor(db) {
|
|
717
|
+
this.db = db;
|
|
718
|
+
}
|
|
719
|
+
// ===========================
|
|
720
|
+
// PRODUCTS & INVENTORY
|
|
721
|
+
// ===========================
|
|
722
|
+
/**
|
|
723
|
+
* Create a new product
|
|
724
|
+
* Auto-generates SKU if not provided
|
|
725
|
+
*/
|
|
726
|
+
async createProduct(input) {
|
|
727
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
728
|
+
const sku = input.sku || this.generateSKU();
|
|
729
|
+
const product = {
|
|
730
|
+
id: crypto.randomUUID(),
|
|
731
|
+
name: input.name,
|
|
732
|
+
description: input.description,
|
|
733
|
+
sku,
|
|
734
|
+
price: input.price,
|
|
735
|
+
currency: input.currency,
|
|
736
|
+
images: input.images || [],
|
|
737
|
+
stock: input.stock,
|
|
738
|
+
isActive: input.isActive ?? true,
|
|
739
|
+
category: input.category,
|
|
740
|
+
metadata: input.metadata,
|
|
741
|
+
created_at: now,
|
|
742
|
+
updated_at: now
|
|
743
|
+
};
|
|
744
|
+
await this.db.insert("products", product);
|
|
745
|
+
return product;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Update an existing product
|
|
749
|
+
*/
|
|
750
|
+
async updateProduct(productId, updates) {
|
|
751
|
+
const existing = await this.getProduct(productId);
|
|
752
|
+
if (!existing) {
|
|
753
|
+
throw new Error(`Product not found: ${productId}`);
|
|
754
|
+
}
|
|
755
|
+
const updated = {
|
|
756
|
+
...existing,
|
|
757
|
+
...updates,
|
|
758
|
+
id: productId,
|
|
759
|
+
// Ensure ID doesn't change
|
|
760
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
761
|
+
};
|
|
762
|
+
await this.db.update("products", productId, updated);
|
|
763
|
+
return updated;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Get a single product by ID
|
|
767
|
+
*/
|
|
768
|
+
async getProduct(productId) {
|
|
769
|
+
return await this.db.get("products", productId);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* List products with filtering and pagination
|
|
773
|
+
*/
|
|
774
|
+
async listProducts(options) {
|
|
775
|
+
const where = {};
|
|
776
|
+
if (options?.category) {
|
|
777
|
+
where.category = options.category;
|
|
778
|
+
}
|
|
779
|
+
if (options?.isActive !== void 0) {
|
|
780
|
+
where.isActive = options.isActive;
|
|
781
|
+
}
|
|
782
|
+
const products = await this.db.query("products", {
|
|
783
|
+
where,
|
|
784
|
+
orderBy: options?.sortBy,
|
|
785
|
+
orderDirection: "asc",
|
|
786
|
+
limit: options?.limit,
|
|
787
|
+
offset: options?.offset
|
|
788
|
+
});
|
|
789
|
+
const total = await this.db.count("products", where);
|
|
790
|
+
return { products, total };
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Soft delete a product (sets isActive = false)
|
|
794
|
+
*/
|
|
795
|
+
async deleteProduct(productId) {
|
|
796
|
+
await this.updateProduct(productId, { isActive: false });
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Update product inventory with atomic operations
|
|
800
|
+
*/
|
|
801
|
+
async updateInventory(productId, quantity, operation) {
|
|
802
|
+
const product = await this.getProduct(productId);
|
|
803
|
+
if (!product) {
|
|
804
|
+
throw new Error(`Product not found: ${productId}`);
|
|
805
|
+
}
|
|
806
|
+
let newStock;
|
|
807
|
+
switch (operation) {
|
|
808
|
+
case "set":
|
|
809
|
+
newStock = quantity;
|
|
810
|
+
break;
|
|
811
|
+
case "increment":
|
|
812
|
+
newStock = product.stock + quantity;
|
|
813
|
+
break;
|
|
814
|
+
case "decrement":
|
|
815
|
+
newStock = product.stock - quantity;
|
|
816
|
+
if (newStock < 0) {
|
|
817
|
+
throw new Error(`Insufficient stock for product ${productId}. Available: ${product.stock}, requested: ${quantity}`);
|
|
818
|
+
}
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
return await this.updateProduct(productId, { stock: newStock });
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Get products below stock threshold
|
|
825
|
+
*/
|
|
826
|
+
async getLowStockProducts(threshold = 10) {
|
|
827
|
+
const products = await this.db.query("products", {
|
|
828
|
+
where: { isActive: true }
|
|
829
|
+
});
|
|
830
|
+
return products.filter((p) => p.stock <= threshold);
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Bulk update inventory for multiple products
|
|
834
|
+
*/
|
|
835
|
+
async bulkUpdateInventory(updates) {
|
|
836
|
+
const results = [];
|
|
837
|
+
for (const update of updates) {
|
|
838
|
+
const product = await this.updateInventory(update.productId, update.quantity, update.operation);
|
|
839
|
+
results.push(product);
|
|
840
|
+
}
|
|
841
|
+
return results;
|
|
842
|
+
}
|
|
843
|
+
// ===========================
|
|
844
|
+
// ORDERS & CHECKOUT
|
|
845
|
+
// ===========================
|
|
846
|
+
/**
|
|
847
|
+
* Calculate order totals from cart items
|
|
848
|
+
*/
|
|
849
|
+
async calculateOrderTotal(items) {
|
|
850
|
+
const calculatedItems = [];
|
|
851
|
+
let subtotal = 0;
|
|
852
|
+
for (const item of items) {
|
|
853
|
+
const product = await this.getProduct(item.productId);
|
|
854
|
+
if (!product) {
|
|
855
|
+
throw new Error(`Product not found: ${item.productId}`);
|
|
856
|
+
}
|
|
857
|
+
const lineTotal = product.price * item.quantity;
|
|
858
|
+
const inStock = product.stock >= item.quantity;
|
|
859
|
+
calculatedItems.push({
|
|
860
|
+
productId: item.productId,
|
|
861
|
+
name: product.name,
|
|
862
|
+
price: product.price,
|
|
863
|
+
quantity: item.quantity,
|
|
864
|
+
lineTotal,
|
|
865
|
+
inStock
|
|
866
|
+
});
|
|
867
|
+
subtotal += lineTotal;
|
|
868
|
+
}
|
|
869
|
+
const allInStock = calculatedItems.every((item) => item.inStock);
|
|
870
|
+
if (!allInStock) {
|
|
871
|
+
const outOfStock = calculatedItems.filter((item) => !item.inStock);
|
|
872
|
+
throw new Error(`Items out of stock: ${outOfStock.map((i) => i.name).join(", ")}`);
|
|
873
|
+
}
|
|
874
|
+
const tax = Math.round(subtotal * 0.08);
|
|
875
|
+
const shipping = subtotal > 5e3 ? 0 : 500;
|
|
876
|
+
const total = subtotal + tax + shipping;
|
|
877
|
+
return {
|
|
878
|
+
subtotal,
|
|
879
|
+
tax,
|
|
880
|
+
shipping,
|
|
881
|
+
total,
|
|
882
|
+
items: calculatedItems
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Create an order from cart items
|
|
887
|
+
* Reduces inventory atomically
|
|
888
|
+
*/
|
|
889
|
+
async createOrder(input) {
|
|
890
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
891
|
+
const calculation = await this.calculateOrderTotal(
|
|
892
|
+
input.items.map((item) => ({ productId: item.productId, quantity: item.quantity }))
|
|
893
|
+
);
|
|
894
|
+
const orderItems = calculation.items.map((item) => ({
|
|
895
|
+
productId: item.productId,
|
|
896
|
+
name: item.name,
|
|
897
|
+
quantity: item.quantity,
|
|
898
|
+
price: item.price
|
|
899
|
+
}));
|
|
900
|
+
const order = {
|
|
901
|
+
id: crypto.randomUUID(),
|
|
902
|
+
userId: input.userId,
|
|
903
|
+
status: "pending",
|
|
904
|
+
items: orderItems,
|
|
905
|
+
subtotal: calculation.subtotal,
|
|
906
|
+
tax: calculation.tax,
|
|
907
|
+
shipping: calculation.shipping,
|
|
908
|
+
total: calculation.total,
|
|
909
|
+
shippingAddress: input.shippingAddress,
|
|
910
|
+
billingAddress: input.billingAddress,
|
|
911
|
+
paymentMethodId: input.paymentMethodId,
|
|
912
|
+
notes: input.notes,
|
|
913
|
+
created_at: now,
|
|
914
|
+
updated_at: now
|
|
915
|
+
};
|
|
916
|
+
await this.db.insert("orders", order);
|
|
917
|
+
for (const item of orderItems) {
|
|
918
|
+
await this.db.insert("order_items", {
|
|
919
|
+
id: crypto.randomUUID(),
|
|
920
|
+
orderId: order.id,
|
|
921
|
+
...item,
|
|
922
|
+
lineTotal: item.price * item.quantity,
|
|
923
|
+
created_at: now
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
await this.bulkUpdateInventory(
|
|
927
|
+
input.items.map((item) => ({
|
|
928
|
+
productId: item.productId,
|
|
929
|
+
quantity: item.quantity,
|
|
930
|
+
operation: "decrement"
|
|
931
|
+
}))
|
|
932
|
+
);
|
|
933
|
+
return order;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Get an order by ID
|
|
937
|
+
*/
|
|
938
|
+
async getOrder(orderId) {
|
|
939
|
+
const order = await this.db.get("orders", orderId);
|
|
940
|
+
if (!order) return null;
|
|
941
|
+
const items = await this.db.query("order_items", {
|
|
942
|
+
where: { orderId }
|
|
943
|
+
});
|
|
944
|
+
order.items = items.map((item) => ({
|
|
945
|
+
productId: item.productId,
|
|
946
|
+
name: item.name,
|
|
947
|
+
quantity: item.quantity,
|
|
948
|
+
price: item.price
|
|
949
|
+
}));
|
|
950
|
+
return order;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* List orders with filtering
|
|
954
|
+
*/
|
|
955
|
+
async listOrders(options) {
|
|
956
|
+
const where = {};
|
|
957
|
+
if (options?.userId) {
|
|
958
|
+
where.userId = options.userId;
|
|
959
|
+
}
|
|
960
|
+
if (options?.status) {
|
|
961
|
+
where.status = options.status;
|
|
962
|
+
}
|
|
963
|
+
const orders = await this.db.query("orders", {
|
|
964
|
+
where,
|
|
965
|
+
orderBy: options?.sortBy || "created_at",
|
|
966
|
+
orderDirection: "desc",
|
|
967
|
+
limit: options?.limit,
|
|
968
|
+
offset: options?.offset
|
|
969
|
+
});
|
|
970
|
+
for (const order of orders) {
|
|
971
|
+
const items = await this.db.query("order_items", {
|
|
972
|
+
where: { orderId: order.id }
|
|
973
|
+
});
|
|
974
|
+
order.items = items.map((item) => ({
|
|
975
|
+
productId: item.productId,
|
|
976
|
+
name: item.name,
|
|
977
|
+
quantity: item.quantity,
|
|
978
|
+
price: item.price
|
|
979
|
+
}));
|
|
980
|
+
}
|
|
981
|
+
const total = await this.db.count("orders", where);
|
|
982
|
+
return { orders, total };
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Update order status
|
|
986
|
+
*/
|
|
987
|
+
async updateOrderStatus(orderId, status) {
|
|
988
|
+
const order = await this.getOrder(orderId);
|
|
989
|
+
if (!order) {
|
|
990
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
991
|
+
}
|
|
992
|
+
const updated = {
|
|
993
|
+
...order,
|
|
994
|
+
status,
|
|
995
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
996
|
+
};
|
|
997
|
+
await this.db.update("orders", orderId, updated);
|
|
998
|
+
return updated;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Cancel an order and optionally restore inventory
|
|
1002
|
+
*/
|
|
1003
|
+
async cancelOrder(orderId, restoreInventory = true) {
|
|
1004
|
+
const order = await this.getOrder(orderId);
|
|
1005
|
+
if (!order) {
|
|
1006
|
+
throw new Error(`Order not found: ${orderId}`);
|
|
1007
|
+
}
|
|
1008
|
+
if (order.status === "cancelled") {
|
|
1009
|
+
return order;
|
|
1010
|
+
}
|
|
1011
|
+
if (restoreInventory && order.items) {
|
|
1012
|
+
await this.bulkUpdateInventory(
|
|
1013
|
+
order.items.map((item) => ({
|
|
1014
|
+
productId: item.productId,
|
|
1015
|
+
quantity: item.quantity,
|
|
1016
|
+
operation: "increment"
|
|
1017
|
+
}))
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
return await this.updateOrderStatus(orderId, "cancelled");
|
|
1021
|
+
}
|
|
1022
|
+
// ===========================
|
|
1023
|
+
// CART OPERATIONS
|
|
1024
|
+
// ===========================
|
|
1025
|
+
/**
|
|
1026
|
+
* Add item to user's cart
|
|
1027
|
+
*/
|
|
1028
|
+
async addToCart(userId, item) {
|
|
1029
|
+
const existingCart = await this.db.query("carts", {
|
|
1030
|
+
where: { userId, productId: item.productId }
|
|
1031
|
+
});
|
|
1032
|
+
if (existingCart.length > 0) {
|
|
1033
|
+
const existing = existingCart[0];
|
|
1034
|
+
await this.db.update("carts", existing.id, {
|
|
1035
|
+
...existing,
|
|
1036
|
+
quantity: existing.quantity + item.quantity,
|
|
1037
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1038
|
+
});
|
|
1039
|
+
} else {
|
|
1040
|
+
await this.db.insert("carts", {
|
|
1041
|
+
id: crypto.randomUUID(),
|
|
1042
|
+
userId,
|
|
1043
|
+
productId: item.productId,
|
|
1044
|
+
quantity: item.quantity,
|
|
1045
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1046
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
return await this.getCart(userId);
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Update cart item quantity or remove if quantity = 0
|
|
1053
|
+
*/
|
|
1054
|
+
async updateCartItem(userId, productId, quantity) {
|
|
1055
|
+
const items = await this.db.query("carts", {
|
|
1056
|
+
where: { userId, productId }
|
|
1057
|
+
});
|
|
1058
|
+
if (items.length === 0) {
|
|
1059
|
+
throw new Error(`Cart item not found for product: ${productId}`);
|
|
1060
|
+
}
|
|
1061
|
+
const item = items[0];
|
|
1062
|
+
if (quantity === 0) {
|
|
1063
|
+
await this.db.delete("carts", item.id);
|
|
1064
|
+
} else {
|
|
1065
|
+
await this.db.update("carts", item.id, {
|
|
1066
|
+
...item,
|
|
1067
|
+
quantity,
|
|
1068
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
return await this.getCart(userId);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Get user's cart
|
|
1075
|
+
*/
|
|
1076
|
+
async getCart(userId) {
|
|
1077
|
+
const items = await this.db.query("carts", {
|
|
1078
|
+
where: { userId }
|
|
1079
|
+
});
|
|
1080
|
+
return items.map((item) => ({
|
|
1081
|
+
productId: item.productId,
|
|
1082
|
+
quantity: item.quantity
|
|
1083
|
+
}));
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Clear user's cart
|
|
1087
|
+
*/
|
|
1088
|
+
async clearCart(userId) {
|
|
1089
|
+
const items = await this.db.query("carts", {
|
|
1090
|
+
where: { userId }
|
|
1091
|
+
});
|
|
1092
|
+
for (const item of items) {
|
|
1093
|
+
await this.db.delete("carts", item.id);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Checkout - convert cart to order and clear cart
|
|
1098
|
+
*/
|
|
1099
|
+
async checkout(userId, shippingAddress, paymentMethodId) {
|
|
1100
|
+
const cart = await this.getCart(userId);
|
|
1101
|
+
if (cart.length === 0) {
|
|
1102
|
+
throw new Error("Cart is empty");
|
|
1103
|
+
}
|
|
1104
|
+
const order = await this.createOrder({
|
|
1105
|
+
userId,
|
|
1106
|
+
items: cart.map((item) => ({ productId: item.productId, quantity: item.quantity })),
|
|
1107
|
+
shippingAddress,
|
|
1108
|
+
paymentMethodId
|
|
1109
|
+
});
|
|
1110
|
+
await this.clearCart(userId);
|
|
1111
|
+
return order;
|
|
1112
|
+
}
|
|
1113
|
+
// ===========================
|
|
1114
|
+
// ANALYTICS
|
|
1115
|
+
// ===========================
|
|
1116
|
+
/**
|
|
1117
|
+
* Get revenue statistics for a time period
|
|
1118
|
+
*/
|
|
1119
|
+
async getRevenueStats(period) {
|
|
1120
|
+
const now = /* @__PURE__ */ new Date();
|
|
1121
|
+
const periodStart = this.getPeriodStart(now, period);
|
|
1122
|
+
const previousPeriodStart = this.getPeriodStart(new Date(periodStart), period);
|
|
1123
|
+
const orders = await this.db.query("orders", {});
|
|
1124
|
+
const currentOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
|
|
1125
|
+
const previousOrders = orders.filter(
|
|
1126
|
+
(o) => new Date(o.created_at) >= previousPeriodStart && new Date(o.created_at) < periodStart
|
|
1127
|
+
);
|
|
1128
|
+
const totalRevenue = currentOrders.reduce((sum, o) => sum + o.total, 0);
|
|
1129
|
+
const previousRevenue = previousOrders.reduce((sum, o) => sum + o.total, 0);
|
|
1130
|
+
const totalOrders = currentOrders.length;
|
|
1131
|
+
const previousOrderCount = previousOrders.length;
|
|
1132
|
+
const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
|
|
1133
|
+
const revenueTrend = previousRevenue > 0 ? (totalRevenue - previousRevenue) / previousRevenue * 100 : 0;
|
|
1134
|
+
const ordersTrend = previousOrderCount > 0 ? (totalOrders - previousOrderCount) / previousOrderCount * 100 : 0;
|
|
1135
|
+
return {
|
|
1136
|
+
totalRevenue,
|
|
1137
|
+
totalOrders,
|
|
1138
|
+
averageOrderValue,
|
|
1139
|
+
period: {
|
|
1140
|
+
start: periodStart.toISOString(),
|
|
1141
|
+
end: now.toISOString()
|
|
1142
|
+
},
|
|
1143
|
+
trend: {
|
|
1144
|
+
revenue: Math.round(revenueTrend * 100) / 100,
|
|
1145
|
+
orders: Math.round(ordersTrend * 100) / 100
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Get top selling products
|
|
1151
|
+
*/
|
|
1152
|
+
async getTopProducts(limit = 10, period) {
|
|
1153
|
+
const orders = await this.db.query("orders", {});
|
|
1154
|
+
let filteredOrders = orders;
|
|
1155
|
+
if (period) {
|
|
1156
|
+
const periodStart = this.getPeriodStart(/* @__PURE__ */ new Date(), period);
|
|
1157
|
+
filteredOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
|
|
1158
|
+
}
|
|
1159
|
+
const productMap = /* @__PURE__ */ new Map();
|
|
1160
|
+
for (const order of filteredOrders) {
|
|
1161
|
+
if (!order.items) continue;
|
|
1162
|
+
for (const item of order.items) {
|
|
1163
|
+
const existing = productMap.get(item.productId) || { name: item.name, totalSold: 0, revenue: 0 };
|
|
1164
|
+
existing.totalSold += item.quantity;
|
|
1165
|
+
existing.revenue += item.price * item.quantity;
|
|
1166
|
+
productMap.set(item.productId, existing);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
const stats = Array.from(productMap.entries()).map(([productId, data]) => ({
|
|
1170
|
+
productId,
|
|
1171
|
+
name: data.name,
|
|
1172
|
+
totalSold: data.totalSold,
|
|
1173
|
+
revenue: data.revenue
|
|
1174
|
+
}));
|
|
1175
|
+
stats.sort((a, b) => b.revenue - a.revenue);
|
|
1176
|
+
return stats.slice(0, limit);
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Get order statistics by status
|
|
1180
|
+
*/
|
|
1181
|
+
async getOrderStats() {
|
|
1182
|
+
const orders = await this.db.query("orders", {});
|
|
1183
|
+
const stats = {
|
|
1184
|
+
pending: 0,
|
|
1185
|
+
processing: 0,
|
|
1186
|
+
shipped: 0,
|
|
1187
|
+
delivered: 0,
|
|
1188
|
+
cancelled: 0,
|
|
1189
|
+
averageOrderValue: 0
|
|
1190
|
+
};
|
|
1191
|
+
let totalValue = 0;
|
|
1192
|
+
for (const order of orders) {
|
|
1193
|
+
stats[order.status]++;
|
|
1194
|
+
totalValue += order.total;
|
|
1195
|
+
}
|
|
1196
|
+
stats.averageOrderValue = orders.length > 0 ? totalValue / orders.length : 0;
|
|
1197
|
+
return stats;
|
|
1198
|
+
}
|
|
1199
|
+
// ===========================
|
|
1200
|
+
// HELPER METHODS
|
|
1201
|
+
// ===========================
|
|
1202
|
+
generateSKU() {
|
|
1203
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
1204
|
+
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
1205
|
+
return `${timestamp}-${random}`;
|
|
1206
|
+
}
|
|
1207
|
+
getPeriodStart(date, period) {
|
|
1208
|
+
const d = new Date(date);
|
|
1209
|
+
switch (period) {
|
|
1210
|
+
case "day":
|
|
1211
|
+
d.setHours(0, 0, 0, 0);
|
|
1212
|
+
break;
|
|
1213
|
+
case "week":
|
|
1214
|
+
d.setDate(d.getDate() - d.getDay());
|
|
1215
|
+
d.setHours(0, 0, 0, 0);
|
|
1216
|
+
break;
|
|
1217
|
+
case "month":
|
|
1218
|
+
d.setDate(1);
|
|
1219
|
+
d.setHours(0, 0, 0, 0);
|
|
1220
|
+
break;
|
|
1221
|
+
case "year":
|
|
1222
|
+
d.setMonth(0, 1);
|
|
1223
|
+
d.setHours(0, 0, 0, 0);
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
return d;
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
697
1229
|
// Annotate the CommonJS export names for ESM import in node:
|
|
698
1230
|
0 && (module.exports = {
|
|
699
1231
|
VlibeBaseAuth,
|
|
700
1232
|
VlibeBaseDatabase,
|
|
1233
|
+
VlibeBaseEcommerce,
|
|
701
1234
|
VlibeBasePayments
|
|
702
1235
|
});
|