@withvlibe/base-sdk 1.0.1 → 1.1.1
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/README.md +284 -0
- package/dist/{VlibeBaseAuth-BxS9T0vB.d.mts → VlibeBaseEcommerce-DXjHdN_L.d.mts} +208 -8
- package/dist/{VlibeBaseAuth-BxS9T0vB.d.ts → VlibeBaseEcommerce-DXjHdN_L.d.ts} +208 -8
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +538 -0
- package/dist/index.mjs +537 -0
- package/dist/react.d.mts +108 -3
- package/dist/react.d.ts +108 -3
- package/dist/react.js +177 -2
- package/dist/react.mjs +173 -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);
|
|
@@ -709,9 +710,546 @@ var VlibeBasePayments = class {
|
|
|
709
710
|
return this.baseUrl;
|
|
710
711
|
}
|
|
711
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
|
+
* Get user's cart with full product details
|
|
1087
|
+
*/
|
|
1088
|
+
async getCartWithDetails(userId) {
|
|
1089
|
+
const cart = await this.getCart(userId);
|
|
1090
|
+
const enriched = [];
|
|
1091
|
+
for (const item of cart) {
|
|
1092
|
+
const product = await this.getProduct(item.productId);
|
|
1093
|
+
if (!product) {
|
|
1094
|
+
throw new Error(`Product not found: ${item.productId}`);
|
|
1095
|
+
}
|
|
1096
|
+
enriched.push({
|
|
1097
|
+
productId: item.productId,
|
|
1098
|
+
quantity: item.quantity,
|
|
1099
|
+
product,
|
|
1100
|
+
lineTotal: product.price * item.quantity
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return enriched;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Clear user's cart
|
|
1107
|
+
*/
|
|
1108
|
+
async clearCart(userId) {
|
|
1109
|
+
const items = await this.db.query("carts", {
|
|
1110
|
+
where: { userId }
|
|
1111
|
+
});
|
|
1112
|
+
for (const item of items) {
|
|
1113
|
+
await this.db.delete("carts", item.id);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Checkout - convert cart to order and clear cart
|
|
1118
|
+
*/
|
|
1119
|
+
async checkout(userId, shippingAddress, paymentMethodId) {
|
|
1120
|
+
const cart = await this.getCart(userId);
|
|
1121
|
+
if (cart.length === 0) {
|
|
1122
|
+
throw new Error("Cart is empty");
|
|
1123
|
+
}
|
|
1124
|
+
const order = await this.createOrder({
|
|
1125
|
+
userId,
|
|
1126
|
+
items: cart.map((item) => ({ productId: item.productId, quantity: item.quantity })),
|
|
1127
|
+
shippingAddress,
|
|
1128
|
+
paymentMethodId
|
|
1129
|
+
});
|
|
1130
|
+
await this.clearCart(userId);
|
|
1131
|
+
return order;
|
|
1132
|
+
}
|
|
1133
|
+
// ===========================
|
|
1134
|
+
// ANALYTICS
|
|
1135
|
+
// ===========================
|
|
1136
|
+
/**
|
|
1137
|
+
* Get revenue statistics for a time period
|
|
1138
|
+
*/
|
|
1139
|
+
async getRevenueStats(period) {
|
|
1140
|
+
const now = /* @__PURE__ */ new Date();
|
|
1141
|
+
const periodStart = this.getPeriodStart(now, period);
|
|
1142
|
+
const previousPeriodStart = this.getPeriodStart(new Date(periodStart), period);
|
|
1143
|
+
const orders = await this.db.query("orders", {});
|
|
1144
|
+
const currentOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
|
|
1145
|
+
const previousOrders = orders.filter(
|
|
1146
|
+
(o) => new Date(o.created_at) >= previousPeriodStart && new Date(o.created_at) < periodStart
|
|
1147
|
+
);
|
|
1148
|
+
const totalRevenue = currentOrders.reduce((sum, o) => sum + o.total, 0);
|
|
1149
|
+
const previousRevenue = previousOrders.reduce((sum, o) => sum + o.total, 0);
|
|
1150
|
+
const totalOrders = currentOrders.length;
|
|
1151
|
+
const previousOrderCount = previousOrders.length;
|
|
1152
|
+
const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
|
|
1153
|
+
const revenueTrend = previousRevenue > 0 ? (totalRevenue - previousRevenue) / previousRevenue * 100 : 0;
|
|
1154
|
+
const ordersTrend = previousOrderCount > 0 ? (totalOrders - previousOrderCount) / previousOrderCount * 100 : 0;
|
|
1155
|
+
return {
|
|
1156
|
+
totalRevenue,
|
|
1157
|
+
totalOrders,
|
|
1158
|
+
averageOrderValue,
|
|
1159
|
+
period: {
|
|
1160
|
+
start: periodStart.toISOString(),
|
|
1161
|
+
end: now.toISOString()
|
|
1162
|
+
},
|
|
1163
|
+
trend: {
|
|
1164
|
+
revenue: Math.round(revenueTrend * 100) / 100,
|
|
1165
|
+
orders: Math.round(ordersTrend * 100) / 100
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Get top selling products
|
|
1171
|
+
*/
|
|
1172
|
+
async getTopProducts(limit = 10, period) {
|
|
1173
|
+
const orders = await this.db.query("orders", {});
|
|
1174
|
+
let filteredOrders = orders;
|
|
1175
|
+
if (period) {
|
|
1176
|
+
const periodStart = this.getPeriodStart(/* @__PURE__ */ new Date(), period);
|
|
1177
|
+
filteredOrders = orders.filter((o) => new Date(o.created_at) >= periodStart);
|
|
1178
|
+
}
|
|
1179
|
+
const productMap = /* @__PURE__ */ new Map();
|
|
1180
|
+
for (const order of filteredOrders) {
|
|
1181
|
+
if (!order.items) continue;
|
|
1182
|
+
for (const item of order.items) {
|
|
1183
|
+
const existing = productMap.get(item.productId) || { name: item.name, totalSold: 0, revenue: 0 };
|
|
1184
|
+
existing.totalSold += item.quantity;
|
|
1185
|
+
existing.revenue += item.price * item.quantity;
|
|
1186
|
+
productMap.set(item.productId, existing);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const stats = Array.from(productMap.entries()).map(([productId, data]) => ({
|
|
1190
|
+
productId,
|
|
1191
|
+
name: data.name,
|
|
1192
|
+
totalSold: data.totalSold,
|
|
1193
|
+
revenue: data.revenue
|
|
1194
|
+
}));
|
|
1195
|
+
stats.sort((a, b) => b.revenue - a.revenue);
|
|
1196
|
+
return stats.slice(0, limit);
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Get order statistics by status
|
|
1200
|
+
*/
|
|
1201
|
+
async getOrderStats() {
|
|
1202
|
+
const orders = await this.db.query("orders", {});
|
|
1203
|
+
const stats = {
|
|
1204
|
+
pending: 0,
|
|
1205
|
+
processing: 0,
|
|
1206
|
+
shipped: 0,
|
|
1207
|
+
delivered: 0,
|
|
1208
|
+
cancelled: 0,
|
|
1209
|
+
averageOrderValue: 0
|
|
1210
|
+
};
|
|
1211
|
+
let totalValue = 0;
|
|
1212
|
+
for (const order of orders) {
|
|
1213
|
+
stats[order.status]++;
|
|
1214
|
+
totalValue += order.total;
|
|
1215
|
+
}
|
|
1216
|
+
stats.averageOrderValue = orders.length > 0 ? totalValue / orders.length : 0;
|
|
1217
|
+
return stats;
|
|
1218
|
+
}
|
|
1219
|
+
// ===========================
|
|
1220
|
+
// HELPER METHODS
|
|
1221
|
+
// ===========================
|
|
1222
|
+
generateSKU() {
|
|
1223
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
1224
|
+
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
1225
|
+
return `${timestamp}-${random}`;
|
|
1226
|
+
}
|
|
1227
|
+
getPeriodStart(date, period) {
|
|
1228
|
+
const d = new Date(date);
|
|
1229
|
+
switch (period) {
|
|
1230
|
+
case "day":
|
|
1231
|
+
d.setHours(0, 0, 0, 0);
|
|
1232
|
+
break;
|
|
1233
|
+
case "week":
|
|
1234
|
+
d.setDate(d.getDate() - d.getDay());
|
|
1235
|
+
d.setHours(0, 0, 0, 0);
|
|
1236
|
+
break;
|
|
1237
|
+
case "month":
|
|
1238
|
+
d.setDate(1);
|
|
1239
|
+
d.setHours(0, 0, 0, 0);
|
|
1240
|
+
break;
|
|
1241
|
+
case "year":
|
|
1242
|
+
d.setMonth(0, 1);
|
|
1243
|
+
d.setHours(0, 0, 0, 0);
|
|
1244
|
+
break;
|
|
1245
|
+
}
|
|
1246
|
+
return d;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
712
1249
|
// Annotate the CommonJS export names for ESM import in node:
|
|
713
1250
|
0 && (module.exports = {
|
|
714
1251
|
VlibeBaseAuth,
|
|
715
1252
|
VlibeBaseDatabase,
|
|
1253
|
+
VlibeBaseEcommerce,
|
|
716
1254
|
VlibeBasePayments
|
|
717
1255
|
});
|