TS2769: la anotacion explicita {location_id: string} no calzaba con el
tipo Maybe<InventoryLevel> que infiere query.graph para location_levels.
Se quita la anotacion y se deja inferir, con optional chaining.
153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework";
|
|
import { ContainerRegistrationKeys } from "@medusajs/framework/utils";
|
|
import {
|
|
createInventoryItemsWorkflow,
|
|
updateInventoryLevelsWorkflow,
|
|
updateProductVariantsWorkflow,
|
|
} from "@medusajs/medusa/core-flows";
|
|
import { attachInventoryItemToVariantWorkflow } from "../../../workflows/attach-inventory-item-to-variant";
|
|
|
|
// Panel simplificado de inventario (pensado para un usuario no tecnico,
|
|
// no reemplaza la vista nativa de Medusa Admin -- ver ADR/memoria del
|
|
// bug de Firefox Android, tema aparte). Un producto = una variante en
|
|
// este catalogo real (confirmado en vivo contra /store/products antes
|
|
// de asumirlo), asi que la tabla trabaja a nivel de producto.
|
|
|
|
const PRODUCT_FIELDS = [
|
|
"id",
|
|
"title",
|
|
"thumbnail",
|
|
"status",
|
|
"variants.id",
|
|
"variants.sku",
|
|
"variants.manage_inventory",
|
|
"variants.inventory_items.inventory_item_id",
|
|
"variants.inventory_items.inventory.location_levels.id",
|
|
"variants.inventory_items.inventory.location_levels.location_id",
|
|
"variants.inventory_items.inventory.location_levels.stocked_quantity",
|
|
];
|
|
|
|
async function defaultLocationId(req: MedusaRequest): Promise<string | null> {
|
|
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY);
|
|
const { data: locations = [] } = await query.graph({
|
|
entity: "stock_location",
|
|
fields: ["id"],
|
|
});
|
|
return locations[0]?.id ?? null;
|
|
}
|
|
|
|
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY);
|
|
const locationId = await defaultLocationId(req);
|
|
|
|
const { data: products = [] } = await query.graph({
|
|
entity: "product",
|
|
fields: PRODUCT_FIELDS,
|
|
});
|
|
|
|
const rows = products.map((product) => {
|
|
const variant = product.variants?.[0];
|
|
const inventoryItem = variant?.inventory_items?.[0];
|
|
const level = inventoryItem?.inventory?.location_levels?.find(
|
|
(candidate) => candidate?.location_id === locationId,
|
|
);
|
|
return {
|
|
productId: product.id,
|
|
variantId: variant?.id ?? null,
|
|
title: product.title,
|
|
thumbnail: product.thumbnail,
|
|
status: product.status,
|
|
sku: variant?.sku ?? null,
|
|
managed: variant?.manage_inventory ?? false,
|
|
quantity: level?.stocked_quantity ?? 0,
|
|
};
|
|
});
|
|
|
|
res.json({ rows, locationId });
|
|
}
|
|
|
|
type PanelUpdateBody = {
|
|
productId: string;
|
|
variantId: string;
|
|
managed?: boolean;
|
|
quantity?: number;
|
|
};
|
|
|
|
export async function POST(req: MedusaRequest<PanelUpdateBody>, res: MedusaResponse) {
|
|
const { productId, variantId, managed, quantity } = req.body;
|
|
if (!productId || !variantId) {
|
|
res.status(400).json({ error: "productId y variantId son obligatorios" });
|
|
return;
|
|
}
|
|
|
|
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY);
|
|
const locationId = await defaultLocationId(req);
|
|
if (!locationId) {
|
|
res.status(400).json({ error: "No hay una bodega (stock location) configurada" });
|
|
return;
|
|
}
|
|
|
|
const { data: products = [] } = await query.graph({
|
|
entity: "product",
|
|
fields: PRODUCT_FIELDS,
|
|
filters: { id: productId },
|
|
});
|
|
const variant = products[0]?.variants?.find((v: { id: string }) => v.id === variantId);
|
|
const inventoryItem = variant?.inventory_items?.[0];
|
|
let inventoryItemId: string | undefined = inventoryItem?.inventory_item_id;
|
|
|
|
if (managed === true && !inventoryItemId) {
|
|
// Primera vez que se activa el seguimiento para esta variante -- no
|
|
// trae InventoryItem todavia (confirmado en vivo: togglear
|
|
// manage_inventory=true en una variante existente NO crea uno solo,
|
|
// a diferencia de crear un producto nuevo con manage_inventory
|
|
// desde el inicio). Se crea con el nivel de stock inicial y se
|
|
// vincula aparte.
|
|
const { result: items } = await createInventoryItemsWorkflow(req.scope).run({
|
|
input: {
|
|
items: [
|
|
{
|
|
title: variant?.sku ? undefined : products[0]?.title,
|
|
sku: variant?.sku ?? undefined,
|
|
location_levels: [{ location_id: locationId, stocked_quantity: quantity ?? 0 }],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
inventoryItemId = items[0]?.id;
|
|
if (inventoryItemId) {
|
|
await attachInventoryItemToVariantWorkflow(req.scope).run({
|
|
input: { inventoryItemId, variantId },
|
|
});
|
|
}
|
|
await updateProductVariantsWorkflow(req.scope).run({
|
|
input: { product_variants: [{ id: variantId, manage_inventory: true }] },
|
|
});
|
|
} else {
|
|
if (typeof managed === "boolean") {
|
|
await updateProductVariantsWorkflow(req.scope).run({
|
|
input: { product_variants: [{ id: variantId, manage_inventory: managed }] },
|
|
});
|
|
}
|
|
if (typeof quantity === "number" && inventoryItemId) {
|
|
const level = inventoryItem?.inventory?.location_levels?.find(
|
|
(candidate) => candidate?.location_id === locationId,
|
|
);
|
|
await updateInventoryLevelsWorkflow(req.scope).run({
|
|
input: {
|
|
updates: [
|
|
{
|
|
id: level?.id,
|
|
inventory_item_id: inventoryItemId,
|
|
location_id: locationId,
|
|
stocked_quantity: quantity,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
res.json({ ok: true });
|
|
}
|