Hi!
I’m trying to add a document to a datasource, but when I try and add it using aaApi_CreateDocument() it fails…
The reason is that there are some attributes are required or requires a value != null and I guess they don’t have a default value set.
Is there a way to upload a new document and set required attributes simultainously?
If I try and add a document manually without using a wizard I get the same issue, “Last Error [ 58116 ] Error creating new document. Required field has no value.” so the user is forced to use the wizard.
Any help would be greatly appreciated!
/Per
It would really be helpful if you posted a code snippet to show how you are using the function. I would encourage anyone with "problems" with code, to post code snippets to help others help you as it really doesn't help to say "something doesn't work, why is that?" Please don't take this personally, as it is very common, and we all do it at some point and to some degree. I suspect that the environment for the folder where you are trying to create a document is configured to create an attribute row when a document is created and that you are not accounting for that in your code. What are you are specifying the value for the parameter "ulFlags"? If you haven't already done so, try setting this parameter to AADMSDOCCREF_NO_ATTR_RECORD and see if that "fixes" your problem. As the developer, you need to make sure that you follow the intention of the various datasource and user settings as not all of them (most are not) enforced at the API level as that is what makes using the API functions so powerful as you can "override" default behavior (but not security and some datasource or user settings).
To answer your question about "is it possible", no, but you can make it look like it is as that is what the Wizard is doing. It creates the document first, but has the information that it needs to create the attribute row, so after creating the document without an attribute row, it then creates the attribute row using the values that the user specified in the wizard (and it probably first validated that those values would work). You probably should do the same in your code. Make sure that you have (or know) any required attribute values before creating the document so that you (the developer) don't break the intention of the PW Administrator of not having documents created without an attribute row.
Answer Verified By: Per Bodin - AEC AB
Hi,
I have tried this method and it works fine to create new document, but then I have a problem to update attribute values.
Is it possible create document with required fields (document code) and write attribute data values?
Thanks you.
Darius,
"Words are getting in the way" again. I'm not sure what you are trying to accomplish since you can't update attribute values if they don't exist, and you say "I have tried this method and it works fine to create a new document", i.e., you create a document without attributes, etc.
I think you mean that you want to create the attributes after the document has been created without any attributes, and that some attributes are required, and there is a document code involved.
The "good news" is that you can create document codes with the API. Look under Modules -> ProjectWise DMS API -> Document Code...
The "not so good news" is that you will need to follow whatever the rules are for the document coding for the document's environment and that probably requires you (the developer) to understand what the contraints are for providing any parts of the code for the context.
If this is done as an extenion to ProjectWise Explorer, then you can always prompt the user for the details. If this is a "background" tool, then you will need to determine what the rules are for creating the document code for a document and then implement those rules.
I've done such a thing, but as most things goes, the 80/20 rule comes into play, i.e. you get the requirements and implement them, and then "discover" that you only implemented 80% of what the user needed because you didn't know all the exceptions to the rules...
As for "writing" attribute values, i.e. creating attribute, there are three steps:
1. Initialize the INSERT buffer
2 .Populate the INSERT buffer
3. Write the buffer to the database.
The actual process to do this is a bit involved. You need information about the table behind the environment, as well as other information.
Updating attributes is similar:
1. Initialize the UPDATE buffer
2. Populate the UPDATE buffer
Here's the code I provide for the solution to the related lecture and exercies in the PW SDK class I teach:
/**************************************************************************** * * ProjectWise(TM) CONNECT Edition Software Development Kit * Sample Application * Copyright (C) 2018 Bentley Systems, Incorporated * All Rights Reserved * ****************************************************************************/ // Solution for exercise "Working with Attributes" // MyAttributes01.cpp : Defines the initialization routines for the DLL. // #include "stdafx.h" #include "MyAttributes01.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // //TODO: If this DLL is dynamically linked against the MFC DLLs, // any functions exported from this DLL which call into // MFC must have the AFX_MANAGE_STATE macro added at the // very beginning of the function. // // For example: // // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // normal function body here // } // // It is very important that this macro appear in each // function, prior to any calls into MFC. This means that // it must appear as the first statement within the // function, even before any object variable declarations // as their constructors may generate calls into the MFC // DLL. // // Please see MFC Technical Notes 33 and 58 for additional // details. // // CMyAttributes01App BEGIN_MESSAGE_MAP(CMyAttributes01App, CWinApp) END_MESSAGE_MAP() // CMyAttributes01App construction CMyAttributes01App::CMyAttributes01App() { // TODO: add construction code here, // Place all significant initialization in InitInstance } // The one and only CMyAttributes01App object CMyAttributes01App theApp; // CMyAttributes01App initialization BOOL CMyAttributes01App::InitInstance() { CWinApp::InitInstance(); return TRUE; } int CMyAttributes01App::ExitInstance() { // TODO: Add your specialized code here and/or call the base class return CWinApp::ExitInstance(); } /****************************************************************************** Exercise 1, Step 4 Set a breakpoint at the beginning of this function. *******************************************************************************/ extern "C" int WINAPI DocCmd_CreateNewSheet ( unsigned int count, long* pProjects, long* pDocuments ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); /****************************************************************************** Exercise 2, Step 1 Create a loop to process each document. In the real world, you would want to give the user a way to cancel the loop if you show each error, or a way to process all of the documents and review the errors later. We won't do that in this exercise. Just show errors as you go and don't worry about giving the user a way to cancel the processing, just stop on the first error. Hints: Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error messages. To make your error codes unique from ProjectWise, you should use AAERR_USERERROR_FIRST as your starting point for your custom error codes. ******************************************************************************/ for (LONG i=0; i<(LONG)count; i++) { /****************************************************************************** Exercise 2, Step 2 We need some information about the environment. Hint: We can get two key items that we need from the document's folder by using aaApi_GetEnvTableinfoByProject(). ******************************************************************************/ LONG lEnvId = 0L; LONG lTableId = 0L; LONG lAttrColId = 0L; // the column id that holds the attribute id's value if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+1, L"Could not get environment info.", L"Problem with selecting environment info using project id."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+1; } /************************************************************************** Exercise 2, Step 3 We need to determine the column ids that we will need for our values. Since the documents passed to use could be from different folders with different environments, we will look these up for each document to keep things simple. Hints: Use aaApi_SelectColumnsByTable(), then get the names of each using aaApi_GetColumnStringProperty() and when you find a match, use aaApi_GetColumnNumericProperty() and save that column id to a local variable with a meaninful name like LONG lo_string10ColId. We will need it later. Make sure you add error handling, but keep it simple by displaying your own error message and then returning from the function. ***************************************************************************/ LONG lo_string10ColId = 0L; LONG lo_string40ColId = 0L; LONG lo_string80ColId = 0L; LONG lColCount = aaApi_SelectColumnsByTable( lTableId ); if (lColCount==0) { aaApi_SetLastError (AAERR_USERERROR_FIRST+2, L"Could not get column info.", L"Table appears to have no columns!"); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+2; } else if (lColCount==-1) { aaApi_SetLastError (AAERR_USERERROR_FIRST+3, L"Could not get column info.", L"Problem with selecting column info using table id."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+3; } // sentinal value of zero... lo_string10ColId = 0L; lo_string40ColId = 0L; lo_string80ColId = 0L; for (LONG j=0; j<lColCount; j++) { if (_tcsicmp(_T("o_string10"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0) { lo_string10ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j); } else if (_tcsicmp(_T("o_string40"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0) { lo_string40ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j); } else if (_tcsicmp(_T("o_string80"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0) { lo_string80ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j); } } /************************************************************************** Exercise 2, Step 4 If any of our column ids are zero, the environment didn't contain it, so report an error and return. ***************************************************************************/ if ((lo_string10ColId==0) || (lo_string40ColId==0) || (lo_string80ColId==0)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+4, L"Could not determine column ids.", L"Environment table is missing one or more target columns."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+4; } /************************************************************************** Exercise 2, Step 6 Now we have the information we need to create the new row, so now we need to initialize the "Insert" buffer. Hint: Use aaApi_FreeLinkDataInsertDesc() - read the documentation ***************************************************************************/ aaApi_FreeLinkDataInsertDesc(); /************************************************************************** Exercise 2, Step 7 Next we populate the insert buffer with the data that we want to write. You must always change the data into a string and account for the correct format if using dates. For this exercise we are only updating string data. Hints: Use aaApi_SetLinkDataColumnValue(). Place any text you like into each user attribute. ***************************************************************************/ if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string10ColId, _T("via API" ))) { aaApi_SetLastError (AAERR_USERERROR_FIRST+5, L"Could not populate buffer for o_string10.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+5; } if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string40ColId, aaApi_GetOSUserName())) { aaApi_SetLastError (AAERR_USERERROR_FIRST+6, L"Could not populate buffer for o_string40.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+6; } if (!aaApi_SetLinkDataColumnValue (lTableId, lo_string80ColId, aaApi_GetHostName())) { aaApi_SetLastError (AAERR_USERERROR_FIRST+7, L"Could not populate buffer for o_string80.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+7; } /************************************************************************** Exercise 2, Step 8 Now we create the row. To do this we need to populate a structure that we then pass via aaApi_CreateEnvAttr(). Hints: Use aaApi_CreateEnvAttr() and read the documentation. Make sure that reset the structure's contents each time you use it. ***************************************************************************/ // solution using aaApi_CreateLinkDataAndLink() WCHAR szColAttrValue[256] = {0}; memset (szColAttrValue, '\0',sizeof (szColAttrValue)/sizeof(WCHAR)); if(!aaApi_CreateLinkDataAndLink (lTableId, AADMS_EALNK_DOCUMENT, *(pProjects+i), *(pDocuments+i), &lAttrColId, szColAttrValue, sizeof (szColAttrValue)/sizeof(WCHAR))) { aaApi_SetLastError (AAERR_USERERROR_FIRST+8, L"Could not create attribute row.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+8; } /* // alternate solution using aaApi_CreateEnvAtt() LONG lAttrNo = 0L; AAEALINKAGE linkage; // check the documentation lAttrNo = 0; // always set this to zero before you pass it memset(&linkage, 0, sizeof(linkage)); linkage.lLinkageType = AADMS_EALNK_DOCUMENT; linkage.documentId.lProjectId = *(pProjects+i); linkage.documentId.lDocumentId = *(pDocuments+i); if (!aaApi_CreateEnvAttr (lTableId, &linkage, &lAttrNo)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+8, L"Could not create attribute row.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+8; } */ } // for each document return 0; // all documents were successfully processed! } /****************************************************************************** Exercise 1, Step 4 Set a breakpoint at the beginning of this function. *******************************************************************************/ extern "C" int WINAPI DocCmd_UpdateAttributes ( unsigned int count, long* pProjects, long* pDocuments ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); /****************************************************************************** Exercise 3, Step 1 Create a loop to process each document. In the real world, you would want to give the user a way to cancel the loop if you show each error, or a way to process all of the documents and review the errors later. We won't do that in this exercise. Just show errors as you go and don't worry about giving the user a way to cancel the processing, just stop on the first error. Hints: Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error messages. To make your error codes unique from ProjectWise, you should use AAERR_USERERROR_FIRST as your starting point for your custom error codes. ******************************************************************************/ for (LONG i=0; i<(LONG)count; i++) { /****************************************************************************** Exercise 3, Step 2 We need some information about the environment. Hint: We can get two key items that we need from the document's folder by using aaApi_GetEnvTableinfoByProject(). ******************************************************************************/ LONG lEnvId = 0L; LONG lTableId = 0L; LONG lAttrColId = 0L; // the column id that holds the attribute id's value if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+11, L"Could not get environment info.", L"Problem with selecting environment info using project id."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+11; } /************************************************************************** Exercise 3, Step 3 We need to determine the column ids that we will need for our values. Since the documents passed to use could be from different folders with different environments, we will look these up for each document to keep things simple. Hints: Use aaApi_SelectColumnsByTable(), then get the names of each using aaApi_GetColumnStringProperty() and when you find a match, use aaApi_GetColumnNumericProperty() and save that column id to a local variable with a meaninful name like LONG lo_string10ColId. We will need it later. Make sure you add error handling, but keep it simple by displaying your own error message and then returning from the function. ***************************************************************************/ LONG lo_string10ColId = 0L; LONG lo_string40ColId = 0L; LONG lColCount = aaApi_SelectColumnsByTable( lTableId ); if (lColCount==0) { aaApi_SetLastError (AAERR_USERERROR_FIRST+12, L"Could not get column info.", L"Table appears to have no columns!"); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+12; } else if (lColCount==-1) { aaApi_SetLastError (AAERR_USERERROR_FIRST+13, L"Could not get column info.", L"Problem with selecting column info using table id."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+13; } // sentinal value of zero... lo_string10ColId = 0L; lo_string40ColId = 0L; for (LONG j=0; j<lColCount; j++) { if (_tcsicmp(_T("o_string10"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0) { lo_string10ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j); } else if (_tcsicmp(_T("o_string40"), aaApi_GetColumnStringProperty( COLUMN_PROP_NAME, j)) == 0) { lo_string40ColId = aaApi_GetColumnNumericProperty( COLUMN_PROP_COLUMN_ID, j); } } /************************************************************************** Exercise 3, Step 4 If any of our column ids are zero, the environment didn't contain it, so report an error and return. ***************************************************************************/ if ((lo_string10ColId==0) || (lo_string40ColId==0)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+14, L"Could not determine column ids.", L"Environment table is missing one or more target columns."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+14; } /************************************************************************** Exercise 3, Step 6 Now we have the information we need to update any existing rows for the current document, so we will need to create a loop to process all the existing rows. Hints: Use aaApi_SelectLinks() and if there are no rows to process, just skip the document. If there is an error, report it and return. ***************************************************************************/ LONG lLinkRows = aaApi_SelectLinks(*(pProjects+i), *(pDocuments+i)); if (-1==lLinkRows) { aaApi_SetLastError (AAERR_USERERROR_FIRST+15, L"Could not select document sheets.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+15; } else if (0==lLinkRows) { // no rows to update - skip this document... continue; } /************************************************************************** Exercise 3, Step 7 Create a loop to process each sheet. Hint: You can just uncomment the for loop that is provided. ***************************************************************************/ for (LONG k=0; k<lLinkRows; k++) { /************************************************************************** Exercise 3, Step 8 For each existing row, we first need to initialize the "Update" buffer. Hint: Use aaApi_FreeLinkDataUpdateDesc() - read the documentation ***************************************************************************/ aaApi_FreeLinkDataUpdateDesc(); /************************************************************************** Exercise 3, Step 9 Next we populate the update buffer with the data that we want to write. You must always change the data into a string and account for the correct format if using dates. For this exercise we are only updating string data. Hints: Use aaApi_UpdateLinkDataColumnValue(). Place any text you like into each user attribute. Make it obvious that the value has changed. ***************************************************************************/ if (!aaApi_UpdateLinkDataColumnValue (lTableId, lo_string10ColId, _T("Updated!"))) { aaApi_SetLastError (AAERR_USERERROR_FIRST+16, L"Could not populate update buffer for o_string10.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+16; } WCHAR szDSName[41]; memset (szDSName,'\0', sizeof(szDSName)/sizeof(WCHAR)); aaApi_GetActiveDatasourceName(szDSName,sizeof(szDSName)/sizeof(WCHAR)); if (!aaApi_UpdateLinkDataColumnValue (lTableId, lo_string40ColId, szDSName)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+17, L"Could not populate update buffer for o_string40.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+17; } /************************************************************************** Exercise 3, Step 10 - "part 1" To update the existing row, we need to know the value of the a_attrno column. We can get that value with aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k). ***************************************************************************/ LPCWSTR lpcwstrColumnValue = aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k); /************************************************************************** Exercise 3, Step 10 - "part 2" Now we update the row. We can do it with aaApi_UpdateLinkData() or aaApi_UpdateEnvAttr() ***************************************************************************/ // solution using aaApi_UpdateLinkData() if (!aaApi_UpdateLinkData(lTableId, lAttrColId, lpcwstrColumnValue)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+18, L"Could not update attribute row.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+18; } /* // alternate solution using aaApi_UpdateEnvAttr() LONG lAttrRecId = _wtol(lpcwstrColumnValue); if (!aaApi_UpdateEnvAttr (lTableId, lAttrRecId)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+18, L"Could not update attribute row.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+18; } */ } // for each sheet } // for each document return 0; // all documents were successfully processed! } /****************************************************************************** Exercise 1, Step 4 Set a breakpoint at the beginning of this function. *******************************************************************************/ extern "C" int WINAPI DocCmd_DeleteExtraSheets ( unsigned int count, long* pProjects, long* pDocuments ) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); /****************************************************************************** Exercise 4, Step 1 Create a loop to process each document. In the real world, you would want to give the user a way to cancel the loop if you show each error, or a way to process all of the documents and review the errors later. We won't do that in this exercise. Just show errors as you go and don't worry about giving the user a way to cancel the processing, just stop on the first error. Hints: Use aaApi_SetLastError() and aaApi_ShowLastError() for custom error messages. To make your error codes unique from ProjectWise, you should use AAERR_USERERROR_FIRST as your starting point for your custom error codes. ******************************************************************************/ for (LONG i=0; i<(LONG)count; i++) { /****************************************************************************** Exercise 4, Step 2 We need some information about the environment. Hint: We can get two key items that we need from the document's folder by using aaApi_GetEnvTableinfoByProject(). ******************************************************************************/ LONG lEnvId = 0L; LONG lTableId = 0L; LONG lAttrColId = 0L; if (!aaApi_GetEnvTableInfoByProject (*(pProjects+i), &lEnvId, &lTableId, &lAttrColId)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+21, L"Could not get environment info.", L"Problem with selecting environment info using project id."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+21; } /************************************************************************** Exercise 4, Step 3 Now we have the information we need to delete any existing rows for the current document, so we will need to create a loop to process all the existing rows. Hints: Use aaApi_SelectLinks() and if there are 0 or 1 row to process, just skip the document. If there is an error, report it and return. ***************************************************************************/ LONG lLinkRows = aaApi_SelectLinks(*(pProjects+i), *(pDocuments+i)); if (-1==lLinkRows) { aaApi_SetLastError (AAERR_USERERROR_FIRST+22, L"Could not select document sheets for delete.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+22; } else if (lLinkRows<2) { // no rows to delete - skip this document... continue; } /************************************************************************** Exercise 4, Step 4 For each "extra" existing row, delete it! Hint: Use aaApi_DeleteEnvAttr() or aaApi_DeleteLinkData() - read the documentation. ***************************************************************************/ for (LONG k=1; k<lLinkRows; k++) { // solution using aaApi_DeleteLinkData() if (!aaApi_DeleteLinkData (*(pProjects+i), *(pDocuments+i), lTableId, lAttrColId, aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k))) { aaApi_SetLastError (AAERR_USERERROR_FIRST+23, L"Could not delete extra sheet.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+23; } /* // alternative solution using aaApi_DeleteEnvAttr() LONG lAttrRecId = 0; lAttrRecId = _wtol(aaApi_GetLinkStringProperty (LINK_PROP_COLUMN_VALUE, k)); if (!aaApi_DeleteEnvAttr (lTableId, lAttrRecId)) { aaApi_SetLastError (AAERR_USERERROR_FIRST+23, L"Could not delete extra sheet.", L"In the real world, we would also want to know the PW error here."); aaApi_ShowLastError (); return AAERR_USERERROR_FIRST+23; } */ } // for each sheet } // for each document return 0; // all documents were successfully processed! }
This code provides menu items for ProjectWise Explore that create, update and delete attribute rows (i.e ProjectWise "sheets"). You probably will be able to gleem out what you need from this code. Take a look at the documentation for each of the functions in the code and eventually you will figure it out, but it will probably take you a while as this topic is about 1/2 day of classroom time!
HTHs
Thank you for so informative answer.
Let me explain me more about the task. We have our working PW environment where we generating new files with wizard. Our client ask upload some our files to they PW working environment as well. My task is to automate this process.
I need create file in our PW environment and upload as new file to client PW.
When I using file creation wizard I fill out required fields, but I cant do this again when I create this file on client PW using API.
What did you think is the optimal way to do this. I already have all values, I just need have the same on other PW.
Thank you.
Darius
"Words are getting in the way" again. I don't really understand what you need/want to do. There are multiple options for importing files into ProjectWise. Files are just a "property" of a document, so have you considered creating "abstract" or "place holder" documents in your datasource, and then adding the files later in your process? You can also reserve document codes in advance of using them, perhaps that might make it easier to determine which code to use for a specific document/file.
You can also "bulk load" files into ProjectWise and their documents in various ways. You might consider the tools delivered with ProjectWise Explorer, or perhaps consider using the PowerShell module that is available.
As for "optimal way", well that "depends" upon the details, so I suspect that it would take some study to find what appears to be the most optimal way, at least until new requirements are discovered, or the requirements change over time.
A "common" solution for automatically importing files into a ProjectWise datasource is to create some sort of queue where the file and its metadata is provided by some means (file naming conventions, with metadata in an XML file, database entries, etc.) and then have a service process that queue in some organized manner.
Perhaps other ProjectWise users/admins/developers might offer some suggestions on how they have solved similar needs.
My goal is to create absolutely automated process upload files from one PW to another and from Vault to PW.
Idea is create file in one PW and do synchronization to another PW. I plan set scheduler which run some separate windows app, it finds files in one PW with specific property and upload/update file in other PW.
Best regards