@fayz-ai/plugin-inventory 0.1.6 → 0.8.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/README.md +2 -0
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +10 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +36 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36 -26
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +9 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/package.json +7 -4
- package/src/README.md +1 -1
- package/src/data/supabase.ts +24 -23
- package/src/data/tables.ts +10 -0
- package/src/migrations/000_plg_rename.sql +27 -0
- package/src/migrations/001_inventory_base.sql +27 -27
- package/src/migrations/002_recipes.sql +13 -13
- package/src/migrations/003_measurement_units.sql +4 -4
- package/src/migrations/index.ts +161 -0
- package/src/registries.ts +5 -5
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fayz-ai/plugin-inventory)
|
|
6
6
|
[](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
+
**Status:** beta — pre-1.0. Core surface is stable; some backend facets (see `PLUGIN_PATTERNS.md`) are still landing. APIs may change before 1.0.
|
|
9
|
+
|
|
8
10
|
Inventory is the universal organ — a salon counts product, a restaurant counts ingredients, a clinic counts supplies. `plugin-inventory` is that organ as a plugin: a product catalog, stock entry/exit/history, optional recipes (bill of materials), and a dashboard that surfaces stock value and what's running low. It's `scope: universal`, so it snaps into any vertical.
|
|
9
11
|
|
|
10
12
|
It ships real data out of the box: a Supabase provider with an automatic mock fallback, so a fresh app demos immediately and a configured one persists for real. Recipes, stock locations, and batch tracking are opt-in modules; product types, currency, and labels are configurable. The host dashboard gets inventory KPI widgets, and the AI assistant can answer "what's running low?" The intent: stop rebuilding stock control per app — compose it from one plugin into a real SaaS.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA6DpD,wBAAgB,+BAA+B,IAAI,qBAAqB,CA2SvE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const T: {
|
|
2
|
+
readonly stockLocations: "plg_inventory_stock_locations";
|
|
3
|
+
readonly stockMovements: "plg_inventory_stock_movements";
|
|
4
|
+
readonly stockPositions: "plg_inventory_stock_positions";
|
|
5
|
+
readonly recipes: "plg_inventory_recipes";
|
|
6
|
+
readonly recipeIngredients: "plg_inventory_recipe_ingredients";
|
|
7
|
+
readonly measurementUnits: "plg_inventory_measurement_units";
|
|
8
|
+
readonly productCategories: "plg_inventory_product_categories";
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,CAAC;;;;;;;;CAQJ,CAAA"}
|
package/dist/index.cjs
CHANGED
|
@@ -1731,6 +1731,17 @@ function createMockInventoryProvider() {
|
|
|
1731
1731
|
return provider;
|
|
1732
1732
|
}
|
|
1733
1733
|
|
|
1734
|
+
// src/data/tables.ts
|
|
1735
|
+
var T = {
|
|
1736
|
+
stockLocations: "plg_inventory_stock_locations",
|
|
1737
|
+
stockMovements: "plg_inventory_stock_movements",
|
|
1738
|
+
stockPositions: "plg_inventory_stock_positions",
|
|
1739
|
+
recipes: "plg_inventory_recipes",
|
|
1740
|
+
recipeIngredients: "plg_inventory_recipe_ingredients",
|
|
1741
|
+
measurementUnits: "plg_inventory_measurement_units",
|
|
1742
|
+
productCategories: "plg_inventory_product_categories"
|
|
1743
|
+
};
|
|
1744
|
+
|
|
1734
1745
|
// src/data/supabase.ts
|
|
1735
1746
|
function getTenantId() {
|
|
1736
1747
|
return core.getActiveTenantId();
|
|
@@ -1779,10 +1790,10 @@ function createSupabaseInventoryProvider() {
|
|
|
1779
1790
|
function getClients() {
|
|
1780
1791
|
const supabase = core.getSupabaseClientOptional();
|
|
1781
1792
|
if (!supabase) throw new Error("Supabase not initialized");
|
|
1782
|
-
return { core: supabase
|
|
1793
|
+
return { core: supabase, pub: supabase };
|
|
1783
1794
|
}
|
|
1784
1795
|
const provider = {
|
|
1785
|
-
// --- Products (
|
|
1796
|
+
// --- Products (public.products) ---
|
|
1786
1797
|
async getProducts(query) {
|
|
1787
1798
|
const { core} = getClients();
|
|
1788
1799
|
let qb = core.from("products").select("*", { count: "exact" });
|
|
@@ -1873,7 +1884,7 @@ function createSupabaseInventoryProvider() {
|
|
|
1873
1884
|
if (m.destinationLocationId && !m.destinationLocationName) locationIds.add(m.destinationLocationId);
|
|
1874
1885
|
}
|
|
1875
1886
|
if (locationIds.size > 0) {
|
|
1876
|
-
const { data: locs } = await pub.from(
|
|
1887
|
+
const { data: locs } = await pub.from(T.stockLocations).select("id, name").in("id", [...locationIds]);
|
|
1877
1888
|
const locMap = new Map((locs ?? []).map((l) => [l.id, l.name]));
|
|
1878
1889
|
for (const m of movements) {
|
|
1879
1890
|
if (m.stockLocationId && !m.stockLocationName) m.stockLocationName = locMap.get(m.stockLocationId);
|
|
@@ -1894,7 +1905,7 @@ function createSupabaseInventoryProvider() {
|
|
|
1894
1905
|
total_cost: unitCost * input.quantity,
|
|
1895
1906
|
movement_date: input.movementDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
1896
1907
|
};
|
|
1897
|
-
const { data, error } = await pub.from(
|
|
1908
|
+
const { data, error } = await pub.from(T.stockMovements).insert(row).select().single();
|
|
1898
1909
|
if (error) throw new Error(error.message);
|
|
1899
1910
|
const { data: product } = await core.from("products").select("id, name, stock").eq("id", input.productId).single();
|
|
1900
1911
|
if (product) {
|
|
@@ -1906,11 +1917,11 @@ function createSupabaseInventoryProvider() {
|
|
|
1906
1917
|
if (input.stockLocationId) {
|
|
1907
1918
|
try {
|
|
1908
1919
|
const posDelta = input.movementType === "entry" ? input.quantity : -input.quantity;
|
|
1909
|
-
const { data: existing } = await pub.from(
|
|
1920
|
+
const { data: existing } = await pub.from(T.stockPositions).select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.stockLocationId).maybeSingle();
|
|
1910
1921
|
if (existing) {
|
|
1911
|
-
await pub.from(
|
|
1922
|
+
await pub.from(T.stockPositions).update({ quantity: (existing.quantity ?? 0) + posDelta, unit_cost: input.unitCost ?? 0 }).eq("id", existing.id);
|
|
1912
1923
|
} else {
|
|
1913
|
-
await pub.from(
|
|
1924
|
+
await pub.from(T.stockPositions).insert({
|
|
1914
1925
|
tenant_id: tenantId,
|
|
1915
1926
|
product_id: input.productId,
|
|
1916
1927
|
stock_location_id: input.stockLocationId,
|
|
@@ -1921,11 +1932,11 @@ function createSupabaseInventoryProvider() {
|
|
|
1921
1932
|
});
|
|
1922
1933
|
}
|
|
1923
1934
|
if (input.movementType === "transfer" && input.destinationLocationId) {
|
|
1924
|
-
const { data: destExisting } = await pub.from(
|
|
1935
|
+
const { data: destExisting } = await pub.from(T.stockPositions).select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.destinationLocationId).maybeSingle();
|
|
1925
1936
|
if (destExisting) {
|
|
1926
|
-
await pub.from(
|
|
1937
|
+
await pub.from(T.stockPositions).update({ quantity: (destExisting.quantity ?? 0) + input.quantity, unit_cost: input.unitCost ?? 0 }).eq("id", destExisting.id);
|
|
1927
1938
|
} else {
|
|
1928
|
-
await pub.from(
|
|
1939
|
+
await pub.from(T.stockPositions).insert({
|
|
1929
1940
|
tenant_id: tenantId,
|
|
1930
1941
|
product_id: input.productId,
|
|
1931
1942
|
stock_location_id: input.destinationLocationId,
|
|
@@ -1943,11 +1954,11 @@ function createSupabaseInventoryProvider() {
|
|
|
1943
1954
|
const movement = snakeToCamel(data);
|
|
1944
1955
|
movement.productName = product?.name;
|
|
1945
1956
|
if (input.stockLocationId) {
|
|
1946
|
-
const { data: loc } = await pub.from(
|
|
1957
|
+
const { data: loc } = await pub.from(T.stockLocations).select("name").eq("id", input.stockLocationId).single();
|
|
1947
1958
|
movement.stockLocationName = loc?.name;
|
|
1948
1959
|
}
|
|
1949
1960
|
if (input.destinationLocationId) {
|
|
1950
|
-
const { data: loc } = await pub.from(
|
|
1961
|
+
const { data: loc } = await pub.from(T.stockLocations).select("name").eq("id", input.destinationLocationId).single();
|
|
1951
1962
|
movement.destinationLocationName = loc?.name;
|
|
1952
1963
|
}
|
|
1953
1964
|
return movement;
|
|
@@ -1955,44 +1966,44 @@ function createSupabaseInventoryProvider() {
|
|
|
1955
1966
|
// --- Stock Positions ---
|
|
1956
1967
|
async getPositions(productId) {
|
|
1957
1968
|
const { pub } = getClients();
|
|
1958
|
-
const { data } = await pub.from(
|
|
1969
|
+
const { data } = await pub.from(T.stockPositions).select("*").eq("product_id", productId);
|
|
1959
1970
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
1960
1971
|
},
|
|
1961
1972
|
// --- Stock Locations ---
|
|
1962
1973
|
async getLocations() {
|
|
1963
1974
|
const { pub } = getClients();
|
|
1964
|
-
const { data } = await pub.from(
|
|
1975
|
+
const { data } = await pub.from(T.stockLocations).select("*").eq("is_active", true).order("name");
|
|
1965
1976
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
1966
1977
|
},
|
|
1967
1978
|
async createLocation(input) {
|
|
1968
1979
|
const { pub } = getClients();
|
|
1969
1980
|
const tenantId = getTenantId();
|
|
1970
|
-
const { data } = await pub.from(
|
|
1981
|
+
const { data } = await pub.from(T.stockLocations).insert({ ...camelToSnake(input), tenant_id: tenantId }).select().single();
|
|
1971
1982
|
return snakeToCamel(data);
|
|
1972
1983
|
},
|
|
1973
1984
|
// --- Recipes ---
|
|
1974
1985
|
async getRecipes() {
|
|
1975
1986
|
const { pub } = getClients();
|
|
1976
|
-
const { data } = await pub.from(
|
|
1987
|
+
const { data } = await pub.from(T.recipes).select("*").eq("is_active", true).order("name");
|
|
1977
1988
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
1978
1989
|
},
|
|
1979
1990
|
async getRecipeById(id) {
|
|
1980
1991
|
const { pub } = getClients();
|
|
1981
|
-
const { data } = await pub.from(
|
|
1992
|
+
const { data } = await pub.from(T.recipes).select("*").eq("id", id).single();
|
|
1982
1993
|
return data ? snakeToCamel(data) : null;
|
|
1983
1994
|
},
|
|
1984
1995
|
async getRecipeIngredients(recipeId) {
|
|
1985
1996
|
const { pub } = getClients();
|
|
1986
|
-
const { data } = await pub.from(
|
|
1997
|
+
const { data } = await pub.from(T.recipeIngredients).select("*").eq("recipe_id", recipeId).order("display_order");
|
|
1987
1998
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
1988
1999
|
},
|
|
1989
2000
|
async createRecipe(input) {
|
|
1990
2001
|
const { pub } = getClients();
|
|
1991
2002
|
const tenantId = getTenantId();
|
|
1992
2003
|
const { ingredients, ...recipeData } = input;
|
|
1993
|
-
const { data: recipe } = await pub.from(
|
|
2004
|
+
const { data: recipe } = await pub.from(T.recipes).insert({ ...camelToSnake(recipeData), tenant_id: tenantId }).select().single();
|
|
1994
2005
|
if (recipe && ingredients.length > 0) {
|
|
1995
|
-
await pub.from(
|
|
2006
|
+
await pub.from(T.recipeIngredients).insert(
|
|
1996
2007
|
ingredients.map((ing) => ({ ...camelToSnake(ing), recipe_id: recipe.id, tenant_id: tenantId }))
|
|
1997
2008
|
);
|
|
1998
2009
|
}
|
|
@@ -2008,7 +2019,7 @@ function createSupabaseInventoryProvider() {
|
|
|
2008
2019
|
const totalValue = items.reduce((sum, p) => sum + (p.stock ?? 0) * (p.price ?? 0), 0);
|
|
2009
2020
|
const weekAgo = /* @__PURE__ */ new Date();
|
|
2010
2021
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
2011
|
-
const { data: movements } = await pub.from(
|
|
2022
|
+
const { data: movements } = await pub.from(T.stockMovements).select("movement_type").gte("movement_date", weekAgo.toISOString().slice(0, 10));
|
|
2012
2023
|
const movs = movements ?? [];
|
|
2013
2024
|
const movementsByType = { entry: 0, exit: 0, adjustment: 0, transfer: 0, loss: 0 };
|
|
2014
2025
|
for (const m of movs) movementsByType[m.movement_type]++;
|
|
@@ -2131,8 +2142,7 @@ var supplierEntity = {
|
|
|
2131
2142
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
2132
2143
|
],
|
|
2133
2144
|
data: {
|
|
2134
|
-
table: "
|
|
2135
|
-
schema: "saas_core",
|
|
2145
|
+
table: "people",
|
|
2136
2146
|
tenantScoped: true,
|
|
2137
2147
|
filters: { kind: "supplier" },
|
|
2138
2148
|
defaults: { kind: "supplier" }
|
|
@@ -2149,7 +2159,7 @@ var measurementUnitEntity = {
|
|
|
2149
2159
|
{ key: "abbreviation", label: "Abbreviation", type: "text", required: true, showInTable: true },
|
|
2150
2160
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
2151
2161
|
],
|
|
2152
|
-
data: { table:
|
|
2162
|
+
data: { table: T.measurementUnits, tenantScoped: true }
|
|
2153
2163
|
};
|
|
2154
2164
|
var productCategoryEntity = {
|
|
2155
2165
|
name: "Product Category",
|
|
@@ -2162,7 +2172,7 @@ var productCategoryEntity = {
|
|
|
2162
2172
|
{ key: "parentId", label: "Parent", type: "text", showInTable: false },
|
|
2163
2173
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
2164
2174
|
],
|
|
2165
|
-
data: { table:
|
|
2175
|
+
data: { table: T.productCategories, tenantScoped: true }
|
|
2166
2176
|
};
|
|
2167
2177
|
var stockLocationEntity = {
|
|
2168
2178
|
name: "Stock Location",
|
|
@@ -2175,7 +2185,7 @@ var stockLocationEntity = {
|
|
|
2175
2185
|
{ key: "description", label: "Description", type: "textarea", showInTable: true },
|
|
2176
2186
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
2177
2187
|
],
|
|
2178
|
-
data: { table:
|
|
2188
|
+
data: { table: T.stockLocations, tenantScoped: true }
|
|
2179
2189
|
};
|
|
2180
2190
|
var inventoryRegistries = [
|
|
2181
2191
|
// --- Editable ---
|