Can I delete a file in Acumatica via the API? - acumatica

I'm creating a file in Acumatica by calling an action from the API, so that I can retrieve the file in my application.
Is it possible to delete the file via API after I'm done with it? I'd rather not have it cluttering up my Acumatica database.
Failing this, is there a recommended cleanup approach for these files?

Found examples of how to delete a file from within Acumatica, as well as how to save a new version of an existing file! The below implementation saves a new version but has the deletion method commented out. Because I built this into my report generation process, I'm not later deleting the report via API, but it would be easy to translate a deletion into an action callable by the API.
private IEnumerable ExportReport(PXAdapter adapter, string reportID, Dictionary<String, String> parameters)
{
//Press save if the SO is not completed
if (Base.Document.Current.Completed == false)
{
Base.Save.Press();
}
PX.SM.FileInfo file = null;
using (Report report = PXReportTools.LoadReport(reportID, null))
{
if (report == null)
{
throw new Exception("Unable to access Acumatica report writer for specified report : " + reportID);
}
PXReportTools.InitReportParameters(report, parameters, PXSettingProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(report);
IRenderFilter renderFilter = ReportProcessor.GetRenderer(ReportProcessor.FilterPdf);
//Generate the PDF
byte[] data = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterPdf).First();
file = new PX.SM.FileInfo(reportNode.ExportFileName + ".pdf", null, data);
//Save the PDF to the SO
UploadFileMaintenance graph = new UploadFileMaintenance();
//Check to see if a file with this name already exists
Guid[] files = PXNoteAttribute.GetFileNotes(Base.Document.Cache, Base.Document.Current);
foreach (Guid fileID in files)
{
FileInfo existingFile = graph.GetFileWithNoData(fileID);
if (existingFile.Name == reportNode.ExportFileName + ".pdf")
{
//If we later decide we want to delete previous versions instead of saving them, this can be changed to
//UploadFileMaintenance.DeleteFile(existingFile.UID);
//But in the meantime, for history purposes, set the UID of the new file to that of the existing file so we can save it as a new version.
file.UID = existingFile.UID;
}
}
//Save the file with the setting to create a new version if one already exists based on the UID
graph.SaveFile(file, FileExistsAction.CreateVersion);
//Save the note attribute so we can find it again.
PXNoteAttribute.AttachFile(Base.Document.Cache, Base.Document.Current, file);
}
//Return the info on the file
return adapter.Get();
}

The response from Acumatica:
S-b (Screen-base) API allows clean way of downloading report generated as file. C-b (Contract-base) simply does not have this feature added. I suggest you provided feedback here: feedback.acumatica.com (EDIT: Done! https://feedback.acumatica.com/ideas/ACU-I-1852)
I think couple of workaround are:
1) use s-b using login from c-b to generate report and get as file (see example below), or
2) create another method to delete the file once required report file is downloaded. For that, you will need to pass back FileID or something to identify for deletion.
example of #1
using (DefaultSoapClient sc = new DefaultSoapClient("DefaultSoap1"))
{
string sharedCookie;
using (new OperationContextScope(sc.InnerChannel))
{
sc.Login("admin", "123", "Company", null, null);
var responseMessageProperty = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name];
sharedCookie = responseMessageProperty.Headers.Get("Set-Cookie");
}
try
{
Screen scr = new Screen(); // add reference to report e.g. http://localhost/Demo2018R2/Soap/SO641010.asmx
scr.CookieContainer = new System.Net.CookieContainer();
scr.CookieContainer.SetCookies(new Uri(scr.Url), sharedCookie);
var schema = scr.GetSchema();
var commands = new Command[]
{
new Value { LinkedCommand = schema.Parameters.OrderType, Value = "SO" },
new Value { LinkedCommand = schema.Parameters.OrderNumber, Value = "SO004425" },
schema.ReportResults.PdfContent
};
var data = scr.Submit(commands);
if(data != null && data.Length > 0)
{
System.IO.File.WriteAllBytes(#"c:\Temp\SalesOrder.pdf",
Convert.FromBase64String(data[0].ReportResults.PdfContent.Value));
}
}
finally
{
sc.Logout();
}
}
Hope this helps. Also, it would be great if you update the stackover post based on these suggestions.
Thanks
Nayan Mansinha
Lead - Developer Support | Acumatica

Related

Uploading PDF file to Google Drive folder and overwriting existing file

I am developing an app that uploads PDF files to a specific Google Drive folder. The file name includes the current date. The following code is for my DriveServiceHelper.class that is used to create a folder in Google Drive and then upload the PDF files into that folder using its folderID:
public class DriveServiceHelper {
Calendar c = Calendar.getInstance();
Date d = c.getTime();
SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy");
String currentDate = df.format(d);
String ps_FolderKey;
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private Drive mDriveService;
public DriveServiceHelper(Drive mDriveService) {
this.mDriveService = mDriveService;
}
public Task<String> createFolder() {
return Tasks.call(mExecutor, () -> {
File folderMetadata = new File();
folderMetadata.setName("Covid Assessment Sheets");
folderMetadata.setMimeType("application/vnd.google-apps.folder");
File myFolder = null;
try {
myFolder = mDriveService.files().create(folderMetadata)
.setFields("id")
.execute();
System.out.println("Folder ID: " + myFolder.getId());
} catch (Exception e) {
e.printStackTrace();
}
if (myFolder == null) {
throw new IOException("Null result when requesting file creation");
}
ps_FolderKey = myFolder.getId();
return ps_FolderKey;
});
}
public Task<String> createFilePDF(String filePath, String folderId) {
return Tasks.call(mExecutor, () -> {
File fileMetaData = new File();
fileMetaData.setName("Covid Assessment # " + currentDate);
fileMetaData.setParents(Collections.singletonList(folderId));
java.io.File file = new java.io.File(filePath);
FileContent mediaContent = new FileContent("application/pdf", file);
File myFile = null;
try {
myFile = mDriveService.files().create(fileMetaData, mediaContent).execute();
} catch (Exception e) {
e.printStackTrace();
}
if (myFile == null) {
throw new IOException("Null result when requesting file creation");
}
return myFile.getId();
});
}
}
When uploading the same PDF to a Google Drive folder, I want to overwrite files with the same name, but instead duplicate files are created in the folder as the fileID assigned is different even if file name is the same.
Please help me understand how I should go about this, to automatically overwrite/replace files that already exist with the same name (each file is differentiated by date), and a new PDF file is created if the PDF file does not exist in the folder.
I understand that I might be using the deprecated Drive API, but I was unable to find other solutions online to help me implement what I need. I also came across solutions that include queries to search for existing Google Drive files, but I am not sure I understand how to use it to make it work for me.
Thank you
Google Drive supports multiple files with the same name
Thus, by creating a file with an already existing name, you will not automatically overwrite the old file.
Instead you should do the following:
Use the method Files:list with the query name = 'Covid Assessment Sheets' to find the already existing file(s) with the same name
If desired, you can narrow down the results by also specifying the mimeType and the parent folder (parents)
Retrieve the id of the list result(s)
Use the method Files:delete to delete the existing file
Proceed to create a new file as you are already doing
In Java this would look as following:
FileList result = DriveService.files().list()
.setQ("name = 'Covid Assessment Sheets'");
.setFields("files(id)")
.execute();
List<File> files = result.getFiles();
for (File file : files) {
DriveService.files().delete(file.getId()).execute();
}
An alternative approach would be to update the contents of the already existing file instead of creating a new one.

C# SharePoint CSOM change file properties after upload

I have searched and found several examples of how to do this, but I can't make them work - well part of it doesn't work.
I can perform the file upload, but the following attempt to change properties fail.
I'm attempting to upload a file from a base64 payload - this part works - but when I afterwards attempt to edit the properties (custom column) associated with the file, the code fails.
Here is the code (simplified for readability):
(note that props is a collection of custom objects (FileProperty) with a name and a value attribute).
using (ClientContext context = new ClientContext("<sharepoint_server_url>"))
{
context.Credentials = new SharePointOnlineCredentials(<usr>,<secure_pwd>);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(Convert.FromBase64String(<base64_content>)))
{
File.SaveBinaryDirect(context, <relative_path>, ms, true);
}
// file is uploaded - so far so good!
// attempt to edit properties of the file.
if (props != null)
{
if (props.Count > 0)
{
File newFile = context.Web.GetFileByServerRelativeUrl(<relative_path>);
context.Load(newFile);
context.ExecuteQuery();
newFile.CheckOut();
ListItem item = newFile.ListItemAllFields;
foreach (FileProperty fp in props)
{
item[fp.name] = fp.value;
}
item.Update();
newFile.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
}
}
}
This code throws an exception in the part where I try to update the properties.
Message: The file was not found.
Can anyone tell me what is wrong with this example or provide another example on how to do this?
Also, a question - is there a way to address a file by a unique ID which is the same regardless of where in the SharePoint server the file is located or moved to?
I hope someone can help me out - thanks :)
Ok, I found a solution to my problem. I don't know why this works better, it just does.
For all I know, I'm doing the exact same thing, just in another way - maybe someone else who knows more about SharePoint than me (which isn't much) can explain why this works while the first example I posted doesn't.
Previous to the code shown, I ensure that <site_url> doesn't end with "/" and that <library_name> doesn't start or end with "/" and that <file_name> doesn't start or end with "/".
With the code below I can uplaod a file and update properties, in my case i changed "Title" and a custom column "CustCulomnA" and it workes.
using (ClientContext context = new ClientContext(<site_url>))
{
context.Credentials = new SharePointOnlineCredentials(<usr>, <secure_pwd>);
FileCreationInformation fci = new FileCreationInformation()
{
Url = <file_name>,
Content = Convert.FromBase64String(<base64_content>),
Overwrite = true
};
Web web = context.Web;
List lib = web.Lists.GetByTitle(<library_name>);
lib.RootFolder.Files.Add(fci);
context.ExecuteQuery();
response.message = "uploaded";
if (props != null)
{
if (props.Count > 0)
{
File newFile = context.Web.GetFileByUrl(<site_url> +"/"+ <library_name> + "/" + <file_name>);
context.Load(newFile);
context.ExecuteQuery();
newFile.CheckOut();
ListItem item = newFile.ListItemAllFields;
foreach (FileProperty fp in props)
{
item[fp.name] = fp.value;
}
item.Update();
newFile.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
context.ExecuteQuery();
Make sure the file server relative url is valid in this case.
For example if the complete url is:
https://zheguo.sharepoint.com/sites/test/Shared%20Documents/test.jpg
Then relative url should be
/sites/test/Shared%20Documents/test.jpg
And you can also use GetFileByUrl method, passing the complete file url like this:
clientContext.Credentials = new SharePointOnlineCredentials(userName, securePassword);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
File file = web.GetFileByUrl("https://zheguo.sharepoint.com/sites/test/Shared%20Documents/test.jpg");
clientContext.Load(file);
clientContext.ExecuteQuery();
file.CheckOut();
ListItem item = file.ListItemAllFields;
item["Title"] = "Test";
item.Update();
file.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
clientContext.ExecuteQuery();
}

GENERATE MULTIPLE TEXT FILES IN ACUMATICA LOCALLY

How can I generate several text files at the same time locally?
I am using the method:
throw new PXRedirectToFileException (file, true);
![enter image description here][1]
However, this method only generates 1 text file. I need more than 1 text file to be generated at a time.
List<object> data1099Misc = new List<object> { };
ARInvoice ari = Base.Document.Current;
foreach (xvrFSCab diot in PXSelect<xvrFSCab,
Where<xvrFSCab.invoiceNbr,
In<Required<xvrFSCab.invoiceNbr>>>>.Select(Base, ari.InvoiceNbr))
{
data1099Misc.Add(CreatePayerARecord(diot));
}
FixedLengthFile flatFile = new FixedLengthFile();
flatFile.WriteToFile(data1099Misc, sw);
sw.Flush();
sw.FlushAsync();
int cont = 0;
while ( cont<3)
{
cont = cont + 1;
string path = "DIOTJOSE" + ".txt";
PX.SM.FileInfo file = new PX.SM.FileInfo(path, null, stream.ToArray());
throw new PXRedirectToFileException(file, true);
}
Acumatica had the same issue when they had to open multiple reports at one click (with RedirectException).
For this reason Acumatica supports multiple RequiredException only for Reports.
They have a method called "CombineReport" that works with multiple PXReportRequiredException (PXReportsRedirectList)
Sad part is that they did not make something for other RequiredException or RedirectException
I tried to make my own "Combine" method but I was not able to create it just because the RedirectHelper.TryRedirect method use hardcoded types of the RedirectException inside body instead to use an generic or base object :(

Custom index of Kentico searching in media library is not adding the index to the search

I'm trying to add a custom index for searching but the file is not added to the results.
This is my code for rebuild method, is finding docs in the right media library but those are not being retrieved in results.
try
{
MediaLibraryInfo library = MediaLibraryInfoProvider.GetMediaLibraryInfo("Documents", SiteContext.CurrentSiteName);
if (library != null)
{
var mediaFiles = MediaFileInfoProvider.GetMediaFiles().WhereEquals("FileLibraryID", library.LibraryID);// all files extension
List<string> files = new List<string>();
foreach (MediaFileInfo mediafile in mediaFiles)
{
SearchDocumentParameters documentParameters = new SearchDocumentParameters()
{
Index = srchInfo,
Type = SearchHelper.CUSTOM_SEARCH_INDEX,
Id = Guid.NewGuid().ToString(),
Created = mediafile.FileCreatedWhen
};
ILuceneSearchDocument doc = LuceneSearchDocumentHelper.ToLuceneSearchDocument(SearchHelper.CreateDocument(documentParameters));
doc.AddGeneralField(SearchFieldsConstants.CUSTOM_TITLE, mediafile.FileTitle, true, true);
doc.AddGeneralField("NAME", mediafile.FileName, true, true);
doc.AddGeneralField("DESCRIPTION", mediafile.FileDescription, true, true);
iw.AddDocument(doc);
}
iw.Flush();
iw.Optimize();
}
}
catch (Exception ex)
{
EventLogProvider.LogException("CustomTextFileIndex", "Rebuild", ex);
}
// Always close the index writer
finally
{
iw.Close();
}
The lines for doc.AddGeneralField with custom NAME and DESCRIPTION won't work with Kentico's default naming convention for columns, you should use the following instead:
doc.AddGeneralField(SearchFieldsConstants.CUSTOM_TITLE, mediafile.FileName, true, true);
doc.AddGeneralField(SearchFieldsConstants.CUSTOM_CONTENT, mediafile.FileDescription, true, true);
The default search mechanisms in Kentico won't use any custom column names for their queries. You'd have to create custom Lucene queries for that functionality. If you're already using CUSTOM_TITLE or CUSTOM_CONTENT fields for something else, then simply add the content to that same field, for example:
doc.AddGeneralField(SearchFieldsConstants.CUSTOM_TITLE, $"{mediafile.FileTitle} {mediafile.FileName}", true, true);
You can verify the Lucene index has included the records using a tool called Luke, I use version 3.5 (4+ won't work on the Kentico indexes)
https://code.google.com/archive/p/luke/downloads

How to insert an empty folder with desired name under an item in Sitecore programmatically?

I need to create empty folders in each sections (content, Layout, renderings, MediaLibrary, Templates) under Sitecore node programmatically.
Please advise.
A folder in Sitecore is a Item, with for example Template: /sitecore/templates/Common/Folder {A87A00B1-E6DB-45AB-8B54-636FEC3B5523}
So you need code to add a item:
See: How to programmatically populate Sitecore items (Add item and fields)?
https://briancaos.wordpress.com/2011/01/14/create-and-publish-items-in-sitecore/
http://learnsitecore.cmsuniverse.net/en/Developers/Articles/2009/06/ProgramaticallyItems2.aspx
For Example Under the Layout folder you can use a other Template, Template: /sitecore/templates/System/Layout/Renderings/Sublayout Folder
So there are more folder templates, and of course you can create your own, and adding the insert options you need or set a nice icoon in the standard values.
Summarized:
You need privileges to create an Sitecore item, You can use the SecurityDisabler or User Switcher.
Get the parent item.
Create the item with the template you want.
//Get the master database first
Sitecore.Data.Database masterDB = Sitecore.Configuration.Factory.GetDatabase("master");
//Creating a folder under "Renderings". Change path as per requirement
Sitecore.Data.Items.Item parentNode= masterDB.GetItem("/sitecore/layout/Renderings");
//Always get the folder template from this location
Sitecore.Data.Items.Item folder = masterDB.GetItem("/sitecore/templates/Common/Folder");
//Add the folder at desired location
parentNode.Add("Folder Name", new TemplateItem(folder));
As Jan Bluemink said:
public static Item AddFolder(String name, Item Parrent = null)
{
Database myDatabase = Sitecore.Context.Database;
if (Parrent == null)
{
return null;
}
Item kiddo = null;
try
{
Sitecore.Data.Items.TemplateItem FolderTemplate = myDatabase.GetTemplate("{EB395152-CC2F-4ECB-8FDD-DE6822517BC8}");
using (new Sitecore.SecurityModel.SecurityDisabler())
{
kiddo = Parrent.Add(name, FolderTemplate);
//Insert values in fileds
// posibly you need to change language and add version to update;
// let say a template "article with some id {00101010101010-100110-1010100-12323}" with two fiels single line text, multiline text or rich text editor
//kiddo.Editing.BeginEdit();
//try
//{
// kiddo.Fields["Title"].Value = "Title 1";
// kiddo.Fields["Description"].Value = "description 1";
// kiddo.Editing.EndEdit();
//}
//catch
//{
// kiddo.Editing.CancelEdit();
//}
}
}
catch (Exception ex) {
return null;
}
return kiddo;
}
and the call:
Item content = Sitecore.Context.Database.GetItem("/sitecore/content");
Item contentFolder = AddFolder("folder", content);
Item medialib = Sitecore.Context.Database.GetItem("/sitecore/media library/medialib");
Item medialibFolder = AddFolder("folder", medialib);

Resources