I use a sheet for project planning which has a column called "Status", where I can choose "Done" or "Open" from a pull down menu. In another cell, I enter my ECD (expected completion date).
I would like to set the status of a task to "Done" and another cell called ACD (actual completion date) should show the current date and freeze the cell after that. Using the function TODAY or NOW would always change the date, every day.
Is there a formula or script that could do that for me?
I love you COM VAT!
But don't be a naughty boy and not share your code...
Keep column A empty, upon form submission or an edit on the row column A will show the current date and it WILL NOT CHANGE :-)
function onEdit() {
var dateColNum = 1 //column F
var ss1 = SpreadsheetApp.getActiveSpreadsheet();
//set date in same row as edit happens, at fixed column
var nextCell = ss1.getActiveSheet().getRange(ss1.getActiveRange().getLastRow(), dateColNum, 1, 1)
if( nextCell.getValue() === '' ) //is empty?
nextCell.setValue(new Date());
}
Assume the "done" is in cell A1 and date/time in B1
=IF(A1="Done",IF(LEN(B1)>0,B1,NOW()),"")
however, you need to turn on iterations
also if you erase the "Done" the cell will be empty again
What you're looking for is a macro, not a formula. This solution assumes your ECD column is 'A' (column 1), then your STATUS column is column 'B' (column 2)
Step 1) Press Alt+F11, this will open up the code editor in VBA.
Step 2) Copy and paste the code below into your 'sheet' that your formula resides in.
Step 3) Save your workbook as a macro-enabled spreadsheet. (xlsm).
Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
If (Target.Columns.Count = 1 And Target.Column = 2 And Target.Rows.Count = 1) Then
'Check every row, currently this will get executed once
For Each cell In Target.Cells
'Make sure the value is set to 'done'
If (UCASE(cell.Value) = "DONE") Then
'Using the current modified row, set column 1 to Today
'Note: Now() is the same as Today in VBA
Target.Worksheet.Cells(cell.row, 1).Value = Now()
End If
Next cell
End If
End Sub
Note: This macro only gets executed if only one cell gets set to 'done'. You can change it to work on multiple cells by removing the 'Target.Rows.Count = 1' part of the FIRST 'if' statement. This scenario may happen if you're 'extending' via clicking and dragging the 'done' value to multiple cells.
Note2: If you want to assign the date once, and prevent the date from ever changing, even if you modify the field to 'done' again. Change the last 'if' statement to the following:
If (UCASE(cell.Value) = "DONE" And Target.Worksheet.Cells(cell.row, 1).Value = "") Then
Related
Goal: When a user pastes a value to a cell, I want the script to check whether or not the copied value is from the same column or not.
Problem: Oftentimes, the user copies a cell from a different column and pastes in a cell that's under another column. This causes some problems for this particular sheet, so I want to be able to have the script check if the clipboard’s copied value's column number (or letter) is the same with the column of where the user is trying to paste it.
Example:
Scenario A: User copied a cell from A1 and attempts to paste it to B2 (either by mistake or intentionally)
If statement check: Check if the user’s clipboard copied value’s column number (or letter) is equal to 2 (if by letter: B).
Result: Since the copied column number is 1 and editing column number is 2, the script will show an error message to refuse the editing.
Scenario B: User copied a cell from A1 and attempts to paste it to A2
If statement check: Check if the user’s clipboard copied value’s column number (or letter) is equal to 1 (if by letter: A).
Result: Since the copied column number is 1 and editing column number is also 1, the script will allow the user to pates the value to A2.
Here's my attempt: I thought it would make sense to use the onEdit function to compare
function onEdit(e) {
const range = e.range;
const source = e.source;
const sheetName = source.getActiveSheet().getName();
const row = range.getRow();
const column = range.getColumn();
const editedRangeValue = range.getValue();
// Call the copyPrevention function
copyPrevention(sheetName, range, row, column, editedRangeValue);
}
function copyPrevention(sheetName, range, row, column, editedRangeValue) {
const headerRowNum = 12;
if (sheetName == 'Status' && row > headerRowNum && editedRangeValue != '') {
// row variable: This is to ensure the editing will only apply if the user tries to edit after row 12 (headerRow).
if (column == "") { // The "" is just a placeholder. If this is even possible, I would like to compare user's editing column number & their clipboard column number. And if the column numbers are the same, then I'd like the script to continue the execution without throwing any errors.
range.setValue(editedRangeValue);
} else if (column != "") { // The "" is just a placeholder. I would like to compare user's editing column number & their clipboard column number. And if the column numbers are not the same, then I'd like the script to throw an error and not let the user edit the cell.
const statusSheetUi = SpreadsheetApp.getUi();
statusSheetUi.alert("You're pasting in the wrong column!");
}
}
}
I have a feeling that this isn't possible, however wanted to check.
Thank you!
archiving / copying all the values in column A to column C based on date. Also, if the date is change, a new set of values will be copied and the previous value won't be deleted.I was working on a daily schedule of every employee at the same time it will be recorded as their attendance based on the date. Can someone help me?
Example 1
Example 2 : if I change the date, the previous record won't get deleted and will copy a new set of values based on date
If I press the (save button) I want all the the values from column B2:B4 will be copied to column F2:F4 based on the date on column C1
The 2nd screenshot shows if I change the date and press again the (save button) the previous value won't get deleted and a new set of values will be copied based on the actual date on column C1
In order to solve your issue I suggest you to use Apps Script and write a script and use this function:
function dateFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var currDate = sheet.getRange("C1").getValue();
var originRange = sheet.getRange("B2:B4");
var headers = sheet.getDataRange().getValues()[0];
var dateFound = false;
for (var i=5; i<headers.length && !dateFound; i++) {
if (currDate.toString() == headers[i].toString()) {
originRange.copyTo(sheet.getRange(2, i + 1, 3));
dateFound = true;
}
}
if (!dateFound)
SpreadsheetApp.getUi().alert("Date not found!");
}
The script works by gathering all the dates from your headers and will try to see if there's a match with the date from the C1 cell. If a match has been found, the values will be copied correspondingly, otherwise, an alert prompt will show up specifying that the date has not been found.
Now, you will need to link this function to a button so it will only be executed when you click it.
You can easily do that by inserting a drawing and shape/draw it so it will resemble a button of your liking. You will have to Save and Close it.
Then, when you right click on it the option of Assign a script will appear and you have to put the name of the function from above in there, as shown below:
So now, every time you press the Save button, the script will be executed.
Furthermore, I suggest you read the following links since they might help you:
Apps Script Spreadsheet Service;
Sheet Class .getDataRange();
Range Class .copyTo().
Currently I am using the following script:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange('Sheet1!C16:C16');
source.copyTo(ss.getRange('Sheet2!A1'), {contentsOnly: true});
source.clear();
}
However I want to append the pasted data in sheet2 with new data that is generated weekly.
So once a week sheet1 is updated and the value in 'Sheet1!C16' will change. I want to append this in 'Sheet2!A2'. and so forth.
So how can I edit this script to make sure the new value is copied in the next empty cell (so when 'Sheet2!A1' has a value get the new value from 'Sheet1!C16' and copy it to 'Sheet2!A2'. And when 'Sheet2!A1'is has a value, copy the new value from 'Sheet1!C16' to 'Sheet2!A3'
I need to to this for all weeks of the year (so just to be safe 53 weeks)
Thnx in advance!
It depends, you can do a few things.
The first one is the easiest, however there is a condition. If each week you copy to cell A1 → A2 → A3 and just keep going down, then each week, that data that you copy over is always the lowest. So for example if you wish to copy data to cell A3 that means that the last row to have any data in any column is row 2. That way all you need to do is get the range like this: ss.getRange(ss.getLastRow() + 1, 1)
The other method is to use PropertiesService to store which row is the last one in use. On script run, get that value
var nextRow = PropertiesService.getScriptProperties().getProperty('property we added before')
and then when you get the target range you use
ss.getRange(nextRow, 1)
PropertiesService.getScriptProperties().setProperty('property we added before', nextRow + 1)
that way you get the next row each time and you update the property for the next time you wish to run. With this you also need to keep in mind that you might want to reset the counter after the year.
I am using a Google Sheet to track my portfolio and I have a cell that calculates the total current market value of my portfolio from various parts of the sheet.
I am trying to see if it is possible to create a script that will record the date as well as the market value (the value only, not the formula) automatically from this cell every day. I know it is possible to set a trigger to run the code weekly/daily, but am having trouble with the code itself.
I can think of 2 approaches. I wasn't sure how to code the second implementation, and what I have so far for the first implementation is below.
1) (First, use Cell A1 as a counter. Set initial value to, say, 3.)
function recordValue() {
//read the counter in Cell A1
var counter = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("A1")
//record current date in cell B&(contents of cell A1)
var dateCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("B"&counter)
outputRange.setValues(Utilities.formatDate(new Date(), Session.getTimeZone(), 'MM-dd-yyyy'));
//read the current market value in 'Summary'!A1 and record it in cell C&(contents of cell A1)
var marketValue = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Summary").getRange("A1:A1");
var outputCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("C"&counter);
outputCell.setValues(marketValue);
//add 1 to the counter in cell A1
counter.setValues(counter + 1);
};
2) Record the current date in the cell directly below, then read the current market value in 'SheetA'!A1 and record it in the cell to the right of the date. Then create a new, empty row directly below (ie pushing the recently written data down by one row)
I'm new to Google Sheets and don't have much programming knowledge. Will either of the above approaches work (and work well with a time-driven trigger)? What's wrong with the current code I have for the first implemetation?
Thanks!
I made some changes in your code because you don't use the getValue() .
I don't really understand exactly what you want to do but the code below works :
function recordValue() {
//read the counter in Cell A1
var counter = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("A1");
//record current date in cell B&(contents of cell A1)
var dateCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("B"+counter.getValue());
dateCell.setValue(new Date());
//read the current market value in 'Summary'!A1 and record it in cell C&(contents of cell A1)
var marketValue = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Summary").getRange("A1");
var outputCell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ThisSheet").getRange("C"+counter.getValue());
outputCell.setValue(marketValue.getValue());
//add 1 to the counter in cell A1
counter.setValue(counter.getValue() + 1);
}
I'm having some trouble trying to make a function that acts like vlookup but returns a range rather than a cell
The data to search through looks like this
Sometimes there is a space separating sometimes not
What I would like to do is to look up 16 from my main page and return all the values in that range.
the code I currently am using will only return the first line in a messagebox
Public Function findrulepos(target) As Range
Dim ruleStart, ruleEnd, ruleEnd2 As String
Dim RuleRange As Range
'Dim ruleEnd As Range
MaxRule = 100000
MaxRow = 100000
Set target = Sheets("main").Range("E2")
Sheets("ResRules").Select
For i = 3 To MaxRow
If CStr(ThisWorkbook.Sheets("ResRules").Range("A" & i).Value) = _
CStr(target.Value) Then
ruleStart = _
ThisWorkbook.Sheets("ResRules").Range("A" & i).Offset(0, 1).Text
Exit For
Else
End If
Next i
End Function
If we can assume that the numeric group labels in col A are sequential, then I think this will achieve what you need:
Enter the numeric label you are wanting to extract (16 in your example) into cell E1
Note that =MATCH(E1,A:A,0) gives us the row number where group 16
starts, which is the first row we want to copy. Similarly,
=MATCH(E1+1,A:A,0) gives us the row number where group 17 starts,
which is one row below the last row we want to copy. (Unfortunately
that's not true for the very last group of code, but to rectify that
you just need to add a dummy number at the very bottom of the data
in col A.)
Enter the formula =IF(ROW()+MATCH(E$1,A:A,0)-1<MATCH(E$1+1,A:A,0),INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1),"") in F1. That should copy the first value of the selected code block -- [DO] ADD TO QUEUE P in your example.
Copy F1 down as many rows as the largest code block is likely to be.
The one problem with that is that it will put 0 whenever it copies a blank row. So you have to explicitly check for that case, e.g. by changing the formula in F1 to =IF(ROW()+MATCH(E$1,A:A,0)-1<MATCH(E$1+1,A:A,0),IF(ISBLANK(INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1)),"",INDIRECT("B"&MATCH(E$1,A:A,0)+ROW()-1)),"")