Assets as document: replace snapshot with publish workflow

Remove assets from the snapshot system and replace with a publish-based
document workflow that generates versioned ProseMirror documents.

- Remove snapshot_id/source_id from asset and asset_vendor models
- Delete AssetFilter (no longer needed without snapshot filtering)
- Add PublishAssetList service, GraphQL mutation, MCP tool, CLI command,
  and n8n operation
- Add asset_list_document_id column to generated_documents table
- Generate ProseMirror documents with asset inventory tables
  (name, type, amount, data types stored, owner, vendors)
- Add AssetListDocument resolver on Organization type
- Update frontend to remove snapshot routes/params and add publish dialog
- Add e2e tests for asset publish (immediate, with approvers, reuse, RBAC)
- Add migration script for converting legacy asset snapshots to documents
- Exclude ASSETS from snapshot type lists and e2e snapshot tests
- Move generated_documents SQL to coredata methods on Datum and Asset
- Clear generated document and SOA references on soft delete and archive

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-21 18:40:49 +02:00
parent 565b71526c
commit b603d04d8d
40 changed files with 2315 additions and 486 deletions

View File

@@ -18,16 +18,28 @@ import { useConfirm } from "@probo/ui";
import { useMutation } from "react-relay";
import { graphql } from "relay-runtime";
import { useMutationWithToasts } from "../useMutationWithToasts";
import type {
AssetGraphCreateMutation,
AssetType,
} from "#/__generated__/core/AssetGraphCreateMutation.graphql";
import type { AssetGraphDeleteMutation } from "#/__generated__/core/AssetGraphDeleteMutation.graphql";
import type { AssetGraphUpdateMutation } from "#/__generated__/core/AssetGraphUpdateMutation.graphql";
/* eslint-disable relay/unused-fields, relay/must-colocate-fragment-spreads */
export const assetsQuery = graphql`
query AssetGraphListQuery($organizationId: ID!, $snapshotId: ID) {
query AssetGraphListQuery($organizationId: ID!) {
node(id: $organizationId) {
... on Organization {
canCreateAsset: permission(action: "core:asset:create")
...AssetsPageFragment @arguments(snapshotId: $snapshotId)
canPublishAssets: permission(action: "core:asset:publish")
assetListDocument {
id
defaultApprovers {
id
}
}
...AssetsPageFragment
}
}
}
@@ -38,7 +50,6 @@ export const assetNodeQuery = graphql`
node(id: $assetId) {
... on Asset {
id
snapshotId
name
amount
assetType
@@ -75,7 +86,6 @@ export const createAssetMutation = graphql`
assetEdge @appendEdge(connections: $connections) {
node {
id
snapshotId
name
amount
assetType
@@ -107,7 +117,6 @@ export const updateAssetMutation = graphql`
updateAsset(input: $input) {
asset {
id
snapshotId
name
amount
assetType
@@ -146,8 +155,7 @@ export const useDeleteAsset = (
asset: { id?: string; name?: string },
connectionId: string,
) => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate] = useMutation(deleteAssetMutation);
const [mutate] = useMutation<AssetGraphDeleteMutation>(deleteAssetMutation);
const confirm = useConfirm();
const { __ } = useTranslate();
@@ -160,7 +168,7 @@ export const useDeleteAsset = (
promisifyMutation(mutate)({
variables: {
input: {
assetId: asset.id,
assetId: asset.id!,
},
connections: [connectionId],
},
@@ -178,15 +186,14 @@ export const useDeleteAsset = (
};
export const useCreateAsset = (connectionId: string) => {
// eslint-disable-next-line relay/generated-typescript-types
const [mutate, isMutating] = useMutation(createAssetMutation);
const [mutate, isMutating] = useMutation<AssetGraphCreateMutation>(createAssetMutation);
const { __ } = useTranslate();
return [
(input: {
name: string;
amount: number;
assetType: string;
assetType: AssetType;
ownerId: string;
organizationId: string;
vendorIds?: string[];
@@ -228,16 +235,13 @@ export const useCreateAsset = (connectionId: string) => {
export const useUpdateAsset = () => {
const { __ } = useTranslate();
const [mutate] = useMutationWithToasts(updateAssetMutation, {
successMessage: __("Asset updated successfully"),
errorMessage: __("Failed to update asset"),
});
const [mutate] = useMutation<AssetGraphUpdateMutation>(updateAssetMutation);
return (input: {
id: string;
name?: string;
amount?: number;
assetType?: string;
assetType?: AssetType;
dataTypesStored?: string;
ownerId?: string;
vendorIds?: string[];
@@ -246,7 +250,7 @@ export const useUpdateAsset = () => {
return alert(__("Failed to update asset: asset ID is required"));
}
return mutate({
return promisifyMutation(mutate)({
variables: {
input,
},