I am trying to set the HTML output from the XMLWorkerHelper into a PdfPCell instead of the Document. What am I doing wrong?
string html = "<h1>Test h1 Heading</h1><ul><li>html 1</li><li>html 2</li><li>html 3</li></ul>";
XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, new StringReader(html));
PdfPCell P4Type1 = new PdfPCell();
P4Tabel.AddCell(P4Type1);
Instead of working with an individual Chunk work with the parent IElement which is where most of the formatting is applied. Not all IElements can be added so you'll need to check the IsContent() boolean property:
PdfPCell P4Type1 = new PdfPCell();
foreach (var element in mh.elements) {
if (element.IsContent()) {
P4Type1.AddElement(element);
}
}
This code is tested ok, that giving html string into PdfPCell to write in pdf document
string htmlFolioNotice = " <P align=center ><B> Thank you for your stay with us.Please visit us again.</B></P> " + "<P align=justify>NOTICE TO GUESTS: This property is privately owned and the management reserves the right to refuse service to anyone. Management will not be responsible for accidents or injury to guests or for loss of money, jewelry or valuables of any kind. Management will not be responsible for any item left in the room.<BR> <BR>CHECKOUT TIME: 11:00 AM SELF REGISTRATION ONLY I AGREE that my liability for this bill is not waived and agree to be held personally liable in the event that the indicated person or company failed to pay for any part or full amount of these charges including any missing/damaged items, etc.. I agreee that if an attorney is retained to collect these charges, I will pay all reasonable attorney's fees and costs incurred. If payment is by credit card you are authorized to charge my account for all charges incurred, including any and all damages/missing items, etc.. I agree that the sole purpose of renting this room is for my own residency only. Damge to property,Disturbance to guest or employees or Law Enforcement comes to your room. Immediate check out with no refund</P>";`enter code here`
PdfPTable tblNotise = new PdfPTable(1) { WidthPercentage = 100, HorizontalAlignment = 1, SpacingBefore = 20f };
int[] tblNotisewidth = { 100 };
tblNotise.SetWidths(tblNotisewidth);
PdfPCell contentCell = new PdfPCell();
contentCell.Border = 0;
var htmlarraylist = HTMLWorker.ParseToList(new StringReader(htmlFolioNotice), null);
for (int k = 0; k < htmlarraylist.Count; k++)
{
var ele = (IElement)htmlarraylist[k];
contentCell.AddElement(ele);
}
tblNotise.AddCell(contentCell);
docPDF.Add(tblNotise);
Related
I am using Google sheets and the Google script editor to create a script to automatically send myself an e-mail every time a product quantity goes below the minimum inventory level. Since I have multiple products with different minimum inventory levels I expect to get a series of e-mails, one for each row.
I use one sheet for the actual Inventory data and another sheet that contains information for the script to refer to, such as my email and what message to include in the e-mail.
I succeeded having an e-mail sent collecting data from the first row of the Inventory sheet but I am not being able to apply that for all the following rows.
I tried changing the .getRange("F2") to .getRange("F2:F"), then whenever one of the products goes under the minimum inventory level I get one single e-mail containing the information about all products, regardless of whether their quantity is under the minimum level or not.
The ideal solution would be ONE single e-mail containing all the information about all products that are under the minimum quantity .
Here is a link to my spreadsheet: https://docs.google.com/spreadsheets/d/1ZHmBvi8ZeaDRYq6Qigaw08NUiOwZumPrLnvnka_mgmA/edit?usp=sharing
Current script:
function CheckInventory() {
// Fetch inventory quantity
var InventoryRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Inventory").getRange("F2");
var Inventory = InventoryRange.getValue();
// Fetch minimum quantity
var MinimumQuantityRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Inventory").getRange("D2");
var MinimumQuantity = MinimumQuantityRange.getValue();
// Check Inventory
if (Inventory < MinimumQuantity){
// Fetch email address
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("E2");
var emailAddress = emailRange.getValues();
// Fetch email message details.
var detailsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("G2");
var details = detailsRange.getValues();
var subjectdetailsRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Notification Rules").getRange("H2");
var subjectdetails = subjectdetailsRange.getValues();
// Send Alert Email.
var message = details;
var subject = subjectdetails;
MailApp.sendEmail(emailAddress, subject, message);
}
}
Update:
As you said in a comment, you want the email to be sent when you edit an inventory cell, and only if this edited inventory quantity is below the corresponding minimum. So here I update my answer accordingly.
First, I guess you have done this already, but because the function uses sendEmail, you will have to grant authorization for it to work. I created a trigger for that. Run this once:
function createEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("CheckInventory")
.forSpreadsheet(ss)
.onEdit()
.create();
}
Then, this is the function that will run every time the spreadsheet is edited. To avoid the email to be sent every time the file is edited, we need a condition that checks whether the edited cell is an inventory and that this inventory is below the minimum:
function CheckInventory(e) {
var ss = e.source;
var inventorySheet = ss.getSheetByName("Inventory");
var rowIndex = e.range.getRow();
var columnIndex = e.range.getColumn();
var numCols = 6;
var row = inventorySheet.getRange(rowIndex, 1, 1, numCols).getValues()[0];
var editedInventory = row[5];
var editedMinimum = row[3];
var sheetName = ss.getActiveSheet().getName();
// Checking that: (1) edited cell is an inventory quantity, and (2) Inventory is below minimum
if(editedInventory <= editedMinimum && sheetName == "Inventory" && columnIndex == 6 && rowIndex > 1) {
var inventoryValues = inventorySheet.getDataRange().getValues();
var emailBody = "";
for(var i = 1; i < inventoryValues.length; i++) {
var inventory = inventoryValues[i][5];
var minimum = inventoryValues[i][3];
if(inventory <= minimum) {
var productName = inventoryValues[i][0] + " " + inventoryValues[i][1];
var productUnits = minimum + " " + inventoryValues[i][4];
var messagePart1 = "Inventory for " + productName + " has gone under " + productUnits + ". ";
var messagePart2 = "Organise purchase order. Inventory as of today is: " + inventory + " " + inventoryValues[i][4];
var message = messagePart1.concat(messagePart2);
var newItem = "<p>".concat(message, "</p>");
emailBody += newItem;
}
}
var emailSubject = "Low inventory alert";
var emailAddress = "your-email#your-domain.com";
// Send Alert Email
if(emailBody != "") {
MailApp.sendEmail({
to: emailAddress,
subject: emailSubject,
htmlBody: emailBody
});
}
}
}
As I said in a comment, if you just want to send an email, it doesn't make sense to have many email subjects, so I hardcoded the subject.
Regarding the emails, I assumed you just want an email address to receive the email, so I hardcoded it too. If you want all the different email addresses found in Notifications tab to receive emails regarding all products, a small fix would be needed to this code. Tell me if that's needed for you.
Also, I didn't use your notifications tab at all, I created the message directly using the script. I'm not sure it is that useful to have the sheet "Notification Rules". Much of its info is the same as the one in inventory, and the rest of data (email address, basically) could be easily included there. But whatever suits you.
I hope this is of any help.
I currently trying to create for stock inventory of some products that are frequently used in my workplace using google spreadsheet. Moreover, I'm trying to come up with a script that would send me an email when a certain product reaches a value below 2 so that I would know that a certain product needs to be restock. I'm do not know the basics of coding, but here's what I got so far:
function readCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var ProductA = sheet.getRange("B2").getValue();
var Product B = sheet.getRange("B3").getValue();
var min = 2
if (ProductA<min) MailApp.sendEmail('n********#googlegroups.com', 'LOW REAGENT STOCK', 'Attention! Your stock of ProductA is running low. Please proceed to restock.');
if (ProductB<min) MailApp.sendEmail('n********#googlegroups.com', 'LOW REAGENT STOCK', 'Attention! Your stock of ProductB is running low. Please proceed to restock.');
}
I put the trigger on onEdit to run the script and I intent to expand the list with more products. The thing is that if one product as already reached a value below 2 and if a change another one, the script will send email for both of them. With more products, this becomes a nuisance, because I would received a bunch of emails if other values remain below 2. Can someone help me out with this? I couldn't find any solution to this so far and I would truly appreciate some help.
Thank you!
When the "onEdit" trigger fires, it receives the event object as parameter containing some useful information about the context, in which the edit action occurred.
For example,
function onEdit(e) {
// range that was edited
var range = e.range;
//value prior to the edit action
var oldValue = e.oldValue;
//new value
var value = e.value;
//sheet the action came from
var sheet = range.getSheet();
//cell coordinates (if edited range is a single cell)
//or the upper left boundary of the edited range
var row = range.getRow();
var col = range.getColumn();
}
You can inspect the event object to get the cell that was edited and see if it's in column B.
var productsColIndex = 1; //column A index;
var inventoryColIndex = 2; //column B index
var range = e.range;
var value = e.value;
var sheet = range.getSheet();
var editedRow = range.getRow();
var editedCol = range.getColumn();
var productName = sheet.getRange(editedRow, productsColIndex).getValue();
//checking if
//1) column B was edited
//2) the product exists in column A
//3) new value is less than 2
if ((editedCol == inventoryColIndex) && productName && value < 2) {
//code for sending notification email.
}
Finally, because simple triggers like onEdit() can't call services that require authorization, it's better to create a function with a different name and then set up the installable trigger manually. In your Script Editor, go to "Edit" -> "Current project's triggers" -> "Add a new trigger" , select your function name from the dropdown list, and pick the following options: "From spreadsheet", "On edit".
Here are my spreadsheet responses from a form: https://docs.google.com/spreadsheets/d/1a9H2HqAwl29IY6-aCvCKs12Xb3vDcZHCOoNugx81PTA/edit#gid=1939572907
The form data generates in the "raw data" tab of the above spreadsheet. However, I'd like to automatically rearrange the form responses in a different format on the "teacher list" tab of the spreadsheet on form submissions. We are trying to keep track of how often we visit a teacher's room and so want all of the timestamps to appear next to the teacher's name.
I do not know if I should be using formulas or a script to get the job done.
To show you our end goal, I have two form submissions that I have typed into the cells where'd we like them to appear on the "teacher list" tab.
Any suggestions or resources to help me accomplish this would be very much appreciated!
This should give you a good start. And, I have removed the merging of the cells in G column in teacher list tab.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
var data = sheet.getDataRange().getValues();
var formatSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Teacher List');
var formatData = formatSheet.getDataRange().getValues();
var name = data[sheet.getLastRow()-1][2];
var flag = 0, index;
for(var i=1; i<formatData.length; i++)
{
if(name == formatData[i][0])
{
flag = 1;
index = i;
break;
}
}
if(flag == 1)
{
for(var i=1; i<=5; i++)
{
if(formatData[index][i] == "")
{
formatSheet.getRange(index+1, i+1).setValue(data[sheet.getLastRow()-1][0]);
formatSheet.getRange(index+1, 7).setValue(formatData[index][6].concat('; '+data[sheet.getLastRow()-1][3]));
break;
}
}
}
}
But is there more than 5 visits possible? Is first column of teacher list tab is going to remian same throughout? Do you want to add new row if no match is found for 'Teacher or PLC Observed' from Raw Data with first column of Teacher List tab?
If answer to these questions is positive, you need to tweak a code little bit, try it. I'll help if you're stuck.
Edit: Please set the appscript trigger as: From form -> onSubmit.
please help me in converting my after trigger to batch apex.
This trigger fires when opportunity stage changes to won.
It runs through line items and checks if forecast(custom objet) exists with that acunt.if yes,iit links to them..if no,itt will create a new forecat.
my trigger works fine forr some records.but to mass update i am getting timed out error.So opting batch apex but i had never written it.pls help me.
trigger Accountforecast on Opportunity (after insert,after update) {
List<Acc_c> AccproductList =new List<Acc_c>();
List<Opportunitylineitem> opplinitemlist =new List<Opportunitylineitem>();
list<opportunitylineitem > oppdate= new list<opportunitylineitem >();
List<Acc__c> accquery =new List<Acc__c>();
List<date> dt =new List<date>();
Set<Id> sProductIds = new Set<Id>();
Set<Id> sAccountIds = new Set<Id>();
Set<id> saccprodfcstids =new set<Id>();
Acc__c accpro =new Acc__c();
string aname;
Integer i;
Integer myIntMonth;
Integer myIntyear;
Integer myIntdate;
opplinitemlist=[select Id,PricebookEntry.Product2.Name,opp_account__c,Opp_account_name__c,PricebookEntry.Product2.id, quantity,ServiceDate,Acc_Product_Fcst__c from Opportunitylineitem WHERE Opportunityid IN :Trigger.newMap.keySet() AND Acc__c=''];
for(OpportunityLineItem oli:opplinitemlist) {
sProductIds.add(oli.PricebookEntry.Product2.id);
sAccountIds.add(oli.opp_account__c);
}
accquery=[select id,Total_Qty_Ordered__c,Last_Order_Qty__c,Last_Order_Date__c,Fcst_Days_Period__c from Acc__c where Acc__c.product__c In :sproductids and Acc__c.Account__c in :saccountids];
for(Acc__c apf1 :accquery){
saccprodfcstids.add(apf1.id);
}
if(saccprodfcstids!=null){
oppdate=[select servicedate from opportunitylineitem where Acc__c IN :saccprodfcstids ];
i =[select count() from Opportunitylineitem where acc_product_fcst__c in :saccprodfcstids];
}
for(Opportunity opp :trigger.new)
{
if(opp.Stagename=='Closed Won')
{
for(opportunitylineitem opplist:opplinitemlist)
{
if(!accquery.isempty())
{
for(opportunitylineitem opldt :oppdate)
{
string myDate = String.valueOf(opldt);
myDate = myDate.substring(myDate.indexof('ServiceDate=')+12);
myDate = myDate.substring(0,10);
String[] strDate = myDate.split('-');
myIntMonth = integer.valueOf(strDate[1]);
myIntYear = integer.valueOf(strDate[0]);
myIntDate = integer.valueOf(strDate[2]);
Date d = Date.newInstance(myIntYear, myIntMonth, myIntDate);
dt.add(d);
}
dt.add(opp.closedate);
dt.sort();
integer TDays=0;
system.debug('*************dt:'+dt.size());
for(integer c=0;c<dt.size()-1;c++)
{
TDays=TDays+dt[c].daysBetween(dt[c+1]);
}
for(Acc_product_fcst__c apf:accquery)
{
apf.Fcst_Days_Period__c = TDays/i;
apf.Total_Qty_Ordered__c =apf.Total_Qty_Ordered__c +opplist.quantity;
apf.Last_Order_Qty__c=opplist.quantity;
apf.Last_Order_Date__c=opp.CloseDate ;
apf.Fcst_Qty_Avg__c=apf.Total_Qty_Ordered__c/(i+1);
Opplist.Acc__c =apf.Id;
}
}
else{
accpro.Account__c=opplist.opp_account__c;
accpro.product__c=opplist.PricebookEntry.Product2.Id;
accpro.opplineitemid__c=opplist.id;
accpro.Total_Qty_Ordered__c =opplist.quantity;
accpro.Last_Order_Qty__c=opplist.quantity;
accpro.Last_Order_Date__c=opp.CloseDate;
accpro.Fcst_Qty_Avg__c=opplist.quantity;
accpro.Fcst_Days_Period__c=7;
accproductList.add(accpro);
}
}
}
}
if(!accproductlist.isempty()){
insert accproductlist;
}
update opplinitemlist;
update accquery;
}
First of all, you should take a look at this: Apex Batch Processing
Once you get a better idea on how batches work, we need to take into account the following points:
Identify the object that requires more processing. Account? Opportunity?
Should the data be maintained across batch calls? Stateful?
Use correct data structure in terms of performance. Map, List?
From your code, we can see you have three objects: OpportunityLineItems, Accounts, and Opportunities. It seems that your account object is using the most processing here.
It seems you're just keeping track of dates and not doing any aggregations. Thus, you don't need to maintain state across batch calls.
Your code has a potential of hitting governor limits, especially memory limits on the heap. You have a four-nested loop. Our suggestion would be to maintain opportunity line items related to Opportunities in a Map rather than in a List. Plus, we can get rid of those unnecessary for loops by refactoring the code as follows:
Note: This is just a template for the batch you will need to construct.
globalglobal Database.QueryLocator start(Database.BatchableContext BC) class AccountforecastBatch implements Database.Batchable<sObject>
{
global Database.QueryLocator start(Database.BatchableContext BC)
{
// 1. Do some initialization here: (i.e. for(OpportunityLineItem oli:opplinitemlist) {sProductIds.add(oli.PricebookEntry.Product2.id)..}
// 2. return Opportunity object here: return Database.getQueryLocator([select id,Total_Qty_Ordered__c,Last_Order_Qty ....]);
}
global void execute(Database.BatchableContext BC, List<sObject> scope)
{
// 1. Traverse your scope which at this point will be a list of Accounts
// 2. You're adding dates inside the process for Opportunity Line Items. See if you can isolate this process outside the for loops with a Map data structure.
// 3. You have 3 potential database transactions here (insert accproductlist;update opplinitemlist; update accquery; ). Ideally, you will only need one DB transaction per batch.If you can complete step 2 above, you might only need to update your opportunity line items. Otherwise, you're trying to do more than one thing in a method and you will need to redesign your solution
}
global void finish(Database.BatchableContext BC)
{
// send email or do some other tasks here
}
}
I've seen elsewhere how to set the UnitPrice on an Item, using the wiley and elusive Item1 field as Intuit.Ipp.Data.Qbd.Money. But how do I READ the unit price from the Item1 field? I can't cast it. The new operator doesn't work ("new ...Money(myItem.Item1)"). So how do I get the price?
I realize the DevKit will probably never be changed so this makes sense. But can we at least get some doc explaining all those strange "xxxItemxxx" fields?
ServiceContext context = new ServiceContext(oauthValidator, realmId, intuitServiceType);
DataServices commonService = new DataServices(context);
Intuit.Ipp.Data.Qbd.Item qbdItem = new Intuit.Ipp.Data.Qbd.Item();
Intuit.Ipp.Data.Qbd.Money unitPrice = new Intuit.Ipp.Data.Qbd.Money();
unitPrice.Amount = 22;
unitPrice.AmountSpecified = true;
qbdItem.Item1 = unitPrice;
IEnumerable<Intuit.Ipp.Data.Qbd.Item> qbdItemsResult = commonService.FindAll(qbdItem, 1, 10) as IEnumerable<Intuit.Ipp.Data.Qbd.Item>;
foreach (var itemResult in qbdItemsResult)
{
Intuit.Ipp.Data.Qbd.Money test1UnitPrice = itemResult.Item1 as Intuit.Ipp.Data.Qbd.Money;
}
You can use the above code for .Net.
Response XML of Item entity suggests that 'UntiPrice' is a top level tag.
I tried this usecase using java. PFB code.
QBItemService itemService = QBServiceFactory.getService(context,QBItemService.class);
items = itemService.findAll(context,1, 100);
for (QBItem item : items) {
System.out.println("Name - " + item.getName() + " UnitPrice - " + item.getUnitPrice().getAmount());
Can you please try the same in .Net and let me know if it works in the same way.
Intuit.Ipp.Data.Qbd.Money [ getAmount() ]
Thanks