feat: add Business Central CPNP sync and expand history access

- Add bc-proxy and bc-export serverless API handlers
- Add businessCentral service with updateCpnpNoInBC and downloadBusinessCentralItemsExcel
- Sync cpnpNo to BC on save (CosmeticItemsView and handleSaveAll)
- Parallelize handleSaveAll with Promise.all
- Fix array index key, a11y click/keyboard handlers, autoFocus
- Grant Change History and Pending Validation access to jingying.shi@craze-group.com

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
Christian Vidal Wolf
2026-05-14 17:17:10 +02:00
co-authored by claude-flow
parent 2961d3f86a
commit 9b5932920e
11 changed files with 585 additions and 19 deletions
+31 -7
View File
@@ -21,7 +21,7 @@ import { UndoToast } from './components/UndoToast';
import { PendingValidationView } from './components/PendingValidationView';
import { MissingDataView } from './components/MissingDataView';
import { CosmeticItemsView } from './components/CosmeticItemsView';
import { downloadBusinessCentralItemsExcel, updateCpnpNoInBC } from './services/businessCentral';
const FORCED_ZERO_STOCK_SKUS = new Set([
'11631VC', '1237VC', '1238VC', '1652VC', '1653VC', '1684VC', '1688VC', '1717VC',
'1718VC', '180VC', '1832VC', '2025VC', '2027VC', '2180VC', '2181VC', '2210VC',
@@ -69,6 +69,7 @@ export default function App() {
const [rowStatuses, setRowStatuses] = useState<Record<string, string>>({});
const [pendingRows, setPendingRows] = useState<Record<string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }>>({});
const [isSavingAll, setIsSavingAll] = useState(false);
const [isDownloadingBCExcel, setIsDownloadingBCExcel] = useState(false);
const [refreshTrigger, setRefreshTrigger] = useState(0);
useEffect(() => {
@@ -543,18 +544,27 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
let sessionIssue = false;
try {
for (const [articleNo, { newData, originalData, articleName }] of entries) {
await Promise.all(entries.map(async ([articleNo, { newData, originalData, articleName }]) => {
console.log('[handleSaveAll] Saving article:', articleNo);
const result = await saveRowToSupabase(articleNo, newData);
console.log('[handleSaveAll] Save result for', articleNo, ':', result);
if (result.success) {
// Also save to history
const histRes = await saveHistoryEntry(articleNo, articleName, originalData, newData, session?.user?.email || 'unknown');
const cpnpValue = String(newData[COLUMNS.CPNP_NO] ?? '').trim();
const cpnpChanged = cpnpValue !== String(originalData[COLUMNS.CPNP_NO] ?? '').trim();
const [histRes, bcResult] = await Promise.all([
saveHistoryEntry(articleNo, articleName, originalData, newData, session?.user?.email || 'unknown'),
cpnpValue && cpnpChanged ? updateCpnpNoInBC(articleNo, cpnpValue) : Promise.resolve({ success: true }),
]);
if (!histRes.success) {
console.warn(`[handleSaveAll] History save failed for ${articleNo}:`, histRes.error);
}
if (!bcResult.success) {
console.warn(`[handleSaveAll] BC sync failed for ${articleNo}:`, (bcResult as any).error);
}
setRowStatuses(prev => ({ ...prev, [articleNo]: 'saved' }));
setPendingRows(prev => {
const n = { ...prev };
@@ -568,7 +578,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
sessionIssue = true;
}
}
}
}));
console.log('[handleSaveAll] Finished loop. Failed:', failedArticles.length);
@@ -588,6 +598,18 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
}
};
const handleDownloadBCExcel = async () => {
setIsDownloadingBCExcel(true);
try {
const result = await downloadBusinessCentralItemsExcel();
if (!result.success) {
alert(`Failed to download Business Central Excel:\n\n${result.error || 'Unknown error'}`);
}
} finally {
setIsDownloadingBCExcel(false);
}
};
const captureState = (message: string) => {
setUndoHistory(prev => {
const newState = {
@@ -672,6 +694,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
stats={stats}
activeModule={activeModule}
onExport={handleExport}
onDownloadBCExcel={handleDownloadBCExcel}
onRefresh={() => { setRefreshTrigger(t => t + 1); }}
hasData={appState.data.length > 0}
hasUnsavedChanges={appState.hasUnsavedChanges}
@@ -686,6 +709,7 @@ const articleNoIdx = resolvedCols.ARTICLE_NO;
onSaveAll={handleSaveAll}
onRevertRow={handleRevertRow}
isSavingAll={isSavingAll}
isDownloadingBCExcel={isDownloadingBCExcel}
isMaximized={isMaximized}
onToggleMaximize={() => setIsMaximized(true)}
/>