C# 기반 SOLIDWORKS Manage BOM 편집 개발 가이드

SOLIDWORKS Manage에서 BOM 데이터를 편집하는 것은 실제 업무에서 매우 빈번하게 발생하는 요구사항입니다. 본 문서에서는 C#을 활용하여 BOM 항목 수량 변경, 텍스트 행 추가, 자재 추가/삭제, 버전 저장 등 기업 수준의 BOM 편집 기능을 구현하는 방법을 상세히 설명합니다.

핵심 편집 기능 목록

  • 특정 BOM 항목의 수량 조정
  • BOM에 텍스트 행(비고/설명) 추가
  • BOM 계층 구조에 새 자재 추가
  • BOM 항목(자재/텍스트 행) 삭제
  • BOM 변경 사항 저장 및 새 버전 생성

사전 조건 및 핵심 규칙

  1. BOM 편집 권한: 로그인 계정에 'BOM 편집' 권한이 있어야 합니다(Manage 백엔드에서 설정).
  2. BOM 상태: 편집 전에 BOM이 다른 사용자에게 체크아웃되지 않았는지 확인합니다(/api/CheckIn/CheckOutStatus API 호출).
  3. 작업 흐름: 모든 BOM 수정은 '편집 후 저장' 순서로 진행되며, 저장 후에만 변경 사항이 영구적으로 반영됩니다.
  4. 버전 규칙: 저장 시 메이저 버전(V1.0→V2.0) 또는 마이너 버전(V1.0→V1.1)을 지정할 수 있습니다.

C# BOM 편집 구현 (전체 코드)

1. BOM 편집 모델 정의
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace ManageApiDev
{
    public class ApiResult
    {
        public bool Success { get; set; }
        public string Message { get; set; }
        public object Payload { get; set; }
    }

    public class QtyUpdatePayload
    {
        public string ConfigId { get; set; }
        public string BomItemId { get; set; }
        public decimal Quantity { get; set; }
    }

    public class TextLineAddPayload
    {
        public string ConfigId { get; set; }
        public string ParentId { get; set; }
        public string Description { get; set; }
        public decimal Quantity { get; set; } = 1;
        public string Unit { get; set; } = "건";
    }

    public class MaterialAddPayload
    {
        public string ConfigId { get; set; }
        public string ParentId { get; set; }
        public string PartNumber { get; set; }
        public decimal Quantity { get; set; }
        public string Unit { get; set; }
        public string Description { get; set; } = "";
    }

    public class BomSavePayload
    {
        public string ConfigId { get; set; }
        public string VersionDescription { get; set; }
        public bool IsMajorVersion { get; set; }
    }
}
2. BOM 편집 유틸리티 클래스 확장
namespace ManageApiDev
{
    public class BomManager
    {
        private readonly HttpClient _httpClient;
        private readonly string _baseUrl = "https://your-manage-instance/api/";

        public BomManager(string authToken)
        {
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {authToken}");
        }

        public async Task ModifyItemQtyAsync(string configId, string itemId, decimal newQty)
        {
            try
            {
                var url = $"{_baseUrl}Bom/UpdateQuantity";
                var body = new QtyUpdatePayload
                {
                    ConfigId = configId,
                    BomItemId = itemId,
                    Quantity = newQty
                };

                var response = await _httpClient.PostAsJsonAsync(url, body);
                response.EnsureSuccessStatusCode();

                var json = await response.Content.ReadAsStringAsync();
                var result = JsonConvert.DeserializeObject<ApiResult>(json);

                if (!result.Success)
                    throw new Exception($"수량 변경 실패: {result.Message}");

                Console.WriteLine($"항목 {itemId} 수량이 {newQty}(으)로 변경되었습니다.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"수량 변경 중 오류 발생: {ex.Message}");
                throw;
            }
        }

        public async Task InsertTextLineAsync(string configId, string parentId, string description)
        {
            try
            {
                var url = $"{_baseUrl}Bom/AddTextLine";
                var body = new TextLineAddPayload
                {
                    ConfigId = configId,
                    ParentId = parentId,
                    Description = description
                };

                var response = await _httpClient.PostAsJsonAsync(url, body);
                response.EnsureSuccessStatusCode();

                var result = JsonConvert.DeserializeObject<ApiResult>(await response.Content.ReadAsStringAsync());
                if (!result.Success)
                    throw new Exception($"텍스트 행 추가 실패: {result.Message}");

                Console.WriteLine("BOM 텍스트 행이 추가되었습니다.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"텍스트 행 추가 중 오류 발생: {ex.Message}");
                throw;
            }
        }

        public async Task AddMaterialAsync(string configId, string parentId, string partNumber, decimal qty, string unit, string desc = "")
        {
            try
            {
                var url = $"{_baseUrl}Bom/AddItem";
                var body = new MaterialAddPayload
                {
                    ConfigId = configId,
                    ParentId = parentId,
                    PartNumber = partNumber,
                    Quantity = qty,
                    Unit = unit,
                    Description = desc
                };

                var response = await _httpClient.PostAsJsonAsync(url, body);
                response.EnsureSuccessStatusCode();

                var result = JsonConvert.DeserializeObject<ApiResult>(await response.Content.ReadAsStringAsync());
                if (!result.Success)
                    throw new Exception($"자재 추가 실패: {result.Message}");

                Console.WriteLine($"자재 {partNumber}이(가) BOM에 추가되었습니다.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"자재 추가 중 오류 발생: {ex.Message}");
                throw;
            }
        }

        public async Task RemoveBomItemAsync(string configId, string itemId)
        {
            try
            {
                var url = $"{_baseUrl}Bom/DeleteItem?configId={configId}&bomItemId={itemId}";
                var response = await _httpClient.PostAsync(url, null);
                response.EnsureSuccessStatusCode();

                var result = JsonConvert.DeserializeObject<ApiResult>(await response.Content.ReadAsStringAsync());
                if (!result.Success)
                    throw new Exception($"BOM 항목 삭제 실패: {result.Message}");

                Console.WriteLine($"BOM 항목 {itemId}이(가) 삭제되었습니다.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"BOM 항목 삭제 중 오류 발생: {ex.Message}");
                throw;
            }
        }

        public async Task SaveBomRevisionAsync(string configId, string versionDesc, bool isMajor = false)
        {
            try
            {
                var url = $"{_baseUrl}Bom/SaveChanges";
                var body = new BomSavePayload
                {
                    ConfigId = configId,
                    VersionDescription = versionDesc,
                    IsMajorVersion = isMajor
                };

                var response = await _httpClient.PostAsJsonAsync(url, body);
                response.EnsureSuccessStatusCode();

                var result = JsonConvert.DeserializeObject<ApiResult>(await response.Content.ReadAsStringAsync());
                if (!result.Success)
                    throw new Exception($"변경 사항 저장 실패: {result.Message}");

                Console.WriteLine($"BOM 변경 사항이 저장되었습니다. ({(isMajor ? "메이저" : "마이너")} 버전)");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"변경 사항 저장 중 오류 발생: {ex.Message}");
                throw;
            }
        }

        public void Dispose()
        {
            _httpClient?.Dispose();
        }
    }
}
3. 전체 호출 예제
namespace ManageApiDev
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var auth = new AuthHelper();
            string token = await auth.GetTokenAsync();

            var bomMgr = new BomManager(token);

            try
            {
                string objId = "101";
                string recId = "123456";
                string versionNote = "자재 수량 조정 및 비고 추가";

                string configId = await bomMgr.GetBomConfigIdAsync(objId, recId);
                var bomData = await bomMgr.GetBomStructureAsync(configId);

                var firstChild = bomData.Items.FirstOrDefault(i => i.ParentId == bomData.RootId);
                if (firstChild != null)
                    await bomMgr.ModifyItemQtyAsync(configId, firstChild.Id, 10);

                await bomMgr.InsertTextLineAsync(configId, bomData.RootId, "비고: 양산 버전 전용 BOM");

                await bomMgr.AddMaterialAsync(configId, bomData.RootId, "PART-007", 2, "개", "신규 체결 부품");

                await bomMgr.SaveBomRevisionAsync(configId, versionNote, false);

                var updatedBom = await bomMgr.GetBomStructureAsync(configId);
                Console.WriteLine("\n===== 수정된 BOM 구조 =====");
                bomMgr.PrintBomStructure(updatedBom.Items, updatedBom.RootId);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"BOM 편집 중 오류 발생: {ex.Message}");
            }
            finally
            {
                bomMgr.Dispose();
                auth.Dispose();
            }

            Console.ReadKey();
        }
    }
}

자주 발생하는 문제와 해결 방법

문제 현상원인해결 방법
수량 변경 시 'BOM이 체크아웃됨'다른 사용자가 BOM을 잠금/api/CheckIn/CheckInBom API를 호출하여 체크인
자재 추가 시 '자재 없음'PartNumber 오류 또는 Manage에 미등록자재 코드 확인 후 시스템에 등록
저장 시 '버전 설명 누락'VersionDescription 필드 누락반드시 버전 설명 입력 (예: "202405-수량조정")
수정 후 조회 시 변화 없음SaveBomRevisionAsync 미호출모든 수정은 메모리 상태이므로 저장 필수
403 Forbidden계정에 BOM 편집 권한 없음백엔드에서 'BOM 편집' 및 '버전 관리' 권한 할당

성능 및 보안 권장 사항

  • 일괄 처리 최적화: 여러 BOM 항목 수정 시 '일괄 수정 후 한 번 저장'하여 SaveChanges 호출 최소화
  • 트랜잭션 보장: BOM 수정에 내장 트랜잭션이 없으므로, 수정 전 BOM 구조를 백업하고 실패 시 복구 로직 구현
  • 권한 검증: 코드 내에서 /api/User/GetCurrentUser API를 호출하여 계정 권한 확인
  • 로깅: 모든 BOM 수정 작업(누가, 언제, 무엇을 수정했는지)을 기록하여 추적 가능성 확보

핵심 요약

  • BOM 쓰기 작업은 '수정 → 저장' 프로세스를 따르며, SaveChanges API만이 변경 사항을 영구적으로 반영합니다.
  • 모든 쓰기 API는 POST 방식이며 JSON 요청 본문과 인증 토큰이 필요합니다.
  • 수정 전 BOM 상태(체크아웃 여부), 자재 존재 여부, 계정 권한을 확인하여 오류를 방지해야 합니다.
  • 저장 시 버전 설명과 버전 유형(메이저/마이너)은 비즈니스 규칙에 따라 설정해야 합니다.

태그: C# SOLIDWORKS Manage BOM 편집 API 개발 PLM

6월 25일 21:30에 게시됨