Hi,
I want to split timesheet based on the project id before submitting timesheet to workflow in d365 f&o
Suppose I have timesheet with 3 lines out of those 2 lines with same project id and 3rd line with different project then based on the different project id it will create new timesheet for line 3 and existing timesheet will same with 2 lines which is having same project id. 3rd line Data should copy from existing timesheet and both timesheet needs to automatically submitted to workflow.
Can you please suggest where to write the code before submitting timesheet to workflow.
Hi ,
Splitting is fine & its creating two new timesheet with proper lines only posting done together for splitted lines. 3rd line never get posted as posting for 3rd line is already done in Line 2
/// [ExtensionOf(classStr(TsWorkflowActionManager))] final class ADNTsWorkflowActionManager_Extension { ////// submitToWorkflow /// /// _args /// returns boolean value public boolean submitToWorkflow(Args _args) { next submitToWorkflow (_args); TSTimesheetTrans tsTimeSheetTrans; TSTimesheetLineWeek tsTimesheetLineWeek; TSTimesheetTable tsTimesheetTable,newTimesheetTable; ProjPeriodLine projPeriodLine; ProjPeriodTimesheetWeek projPeriodTimesheetWeek; TsTimesheetLog tstimeSheetLog; TSTimesheetNbr tsTimesheetNbr; ProjId firstProjId,currentProjId; TSTimesheetLine tsTimesheetLine,tsTimesheetLineBuff,tsTimesheetLinelocal,origtimesheetLineDelete; //boolean result = true; if(projparameters::find().ADNSplitTimesheetOnProjId && !canceledAction) { Common callerRecord = _args.record(); tsTimesheetTable = callerRecord as TSTimesheetTable; select firstonly projId from tsTimesheetLine where tsTimesheetLine.TimesheetNbr == tsTimesheetTable.TimesheetNbr; firstProjId = tsTimesheetLine.ProjId; while select tsTimesheetLineBuff group by tsTimesheetLineBuff.ProjId where tsTimesheetLineBuff.ProjId != firstProjId && tsTimesheetLineBuff.TimesheetNbr == tsTimesheetTable.TimesheetNbr { projPeriodLine = TSTimesheetTable::getValidResourcePeriod(tsTimesheetTable.Resource); if (this && TSTimesheetTable::checkMaxTimesheets(projPeriodLine.PeriodFrom, tsTimesheetTable.Resource, true)) { if (projPeriodLine.RecId != 0) { newTimesheetTable.TimesheetNbr =""; newTimesheetTable.initValue(); newTimesheetTable.Resource = tsTimesheetTable.Resource; newTimesheetTable.ProjPeriodId = projPeriodLine.PeriodId; newTimesheetTable.PeriodFrom = projPeriodLine.PeriodFrom; newTimesheetTable.PeriodTo = projPeriodLine.PeriodTo; projPeriodTimesheetWeek = ProjPeriodTimesheetWeek::findFromPeriod(projPeriodLine.PeriodId); if (projPeriodTimesheetWeek.RecId == 0) { warning("@SYS338882"); } else { newTimesheetTable.ProjPeriodTimesheetWeek = projPeriodTimesheetWeek.RecId; } //ttsBegin; newTimesheetTable.insert(); //ttscommit; tsTimesheetNbr = newTimesheetTable.TimesheetNbr; if (newTimesheetTable.RecId != 0) { if (ProjParameters::find().TimesheetAuditTrail) { TSTimesheetTableLog::createTableLog(newTimesheetTable, '', TsTimesheetChangeType::Create); } this.preLineInserts(newTimesheetTable); currentProjId = tsTimesheetLineBuff.ProjId; while select * from tsTimesheetLinelocal where tsTimesheetLinelocal.ProjId == currentProjId && tsTimesheetLinelocal.TimesheetNbr == tsTimesheetTable.TimesheetNbr && tsTimesheetLinelocal.ProjId != firstProjId { this.createTimesheetLine(tsTimesheetNbr,tsTimesheetLinelocal); delete_from origtimesheetLineDelete where origtimesheetLineDelete.RecId == tsTimesheetLinelocal.RecId; } this.postLineInserts(newTimesheetTable, true); //ttscommit; ttsbegin; newTimesheetTable.selectForUpdate(true); if (newTimesheetTable.RecId && newTimesheetTable.ApprovalStatus == TSAppStatus::Create) { // submitting to workflow; Workflow::activateFromWorkflowType(workFlowTypeStr(TSDocumentTemplate), newTimesheetTable.RecId, "@ADN:AutoSubmitToWF", false, curUserid()); } this.updateStatusToInReview(newTimesheetTable, false); ttscommit; } } else { throw error("@Timesheet:Timesheet_Not_Created"); } } } } return true; //return next submitToWorkflow (_args); } ////// Create TimesheetLine /// /// _timesheetNbr /// _timesheetLine public void createTimesheetLine(TSTimesheetNbr _timesheetNbr, TSTimesheetLine _timesheetLine) { TSTimesheetLine newTimesheetLine = this.initFromTsTimesheetLine(_timesheetNbr, _timesheetLine); if (newTimesheetLine.validateWrite() && newTimesheetLine.ProjPeriodTimesheetWeek != 0) { newTimesheetLine.insert(); this.loadFromLine(newTimesheetLine, _timesheetLine); } } ////// Create TSTimesheetLineWeek record /// /// _tsTimesheetLineNew /// _tsTimesheetLineOld public void loadFromLine(TSTimesheetLine _tsTimesheetLineNew, TSTimesheetLine _tsTimesheetLineOld) { TSTimesheetLineWeek lineWeek, lineWeekOld; while select lineWeekOld where lineWeekOld.TimesheetNbr == _tsTimesheetLineOld.TimesheetNbr && lineWeekOld.linenum == _tsTimesheetLineOld.LineNum && lineWeekOld.tstimesheetLine == _tsTimesheetLineOld.RecId { lineWeek.clear(); buf2Buf(lineWeekOld, lineWeek); lineWeek.TimesheetNbr = _tsTimesheetLineNew.TimesheetNbr; lineWeek.LineNum = _tsTimesheetLineNew.LineNum; lineWeek.tsTimesheetLine = _tsTimesheetLineNew.RecId; lineWeek.insert(); } } ////// Init from timesheetLine Value /// /// _timesheetNbr /// _timesheetLine /// TSTimesheetLine public TSTimesheetLine initFromTsTimesheetLine(TSTimesheetNbr _timesheetNbr, TSTimesheetLine _timesheetLine) { TSTimesheetLine newTimesheetLine; TSTimesheetTable targetTimesheet = TSTimesheetTable::find(_timesheetNbr); RefRecId getTimesheetWeekRecId(RefRecId sourceWeekId) { ProjPeriodTimesheetWeek sourcePeriodTimesheetWeek; ProjPeriodTimesheetWeek targetPeriodTimesheetWeek; TSTimesheetTable sourceTimesheet = TSTimesheetTable::find(_timesheetLine.TimesheetNbr); int counter = 0, counter2 = 0; RefRecId retval = 0; while select RecId from sourcePeriodTimesheetWeek order by sourcePeriodTimesheetWeek.PeriodFrom where sourcePeriodTimesheetWeek.PeriodId == sourceTimesheet.ProjPeriodId && sourcePeriodTimesheetWeek.PeriodFrom >= sourceTimesheet.PeriodFrom && sourcePeriodTimesheetWeek.PeriodTo <= sourceTimesheet.PeriodTo { if (sourcePeriodTimesheetWeek.RecId == sourceWeekId) { break; } counter ; } while select RecId from targetPeriodTimesheetWeek order by targetPeriodTimesheetWeek.PeriodFrom where targetPeriodTimesheetWeek.PeriodId == targetTimesheet.ProjPeriodId && targetPeriodTimesheetWeek.PeriodFrom >= targetTimesheet.PeriodFrom && targetPeriodTimesheetWeek.PeriodTo <= targetTimesheet.PeriodTo { // always set the last PeriodWeek // if the source Period has more weeks than target, then the timesheet lines will be set // to whatever is the last week of the target period retval = targetPeriodTimesheetWeek.RecId; if (counter == counter2) { retval = targetPeriodTimesheetWeek.RecId; break; } counter2 ; } return retval; } newTimesheetLine.TimesheetNbr = _timesheetNbr; newTimesheetLine.ApprovalStatus = TSAppStatus::Create; newTimesheetLine.LineNum = _timesheetLine.LineNum; newTimesheetLine.Resource = targetTimesheet.Resource; newTimesheetLine.ProjId = _timesheetLine.ProjId; newTimesheetLine.ActivityNumber = _timesheetLine.ActivityNumber; newTimesheetLine.CategoryId = _timesheetLine.CategoryId; newTimesheetLine.LinePropertyId = _timesheetLine.LinePropertyId; newTimesheetLine.WrkCtrId = _timesheetLine.WrkCtrId; newTimesheetLine.CurrencyCode = _timesheetLine.CurrencyCode; newTimesheetLine.DefaultDimension = _timesheetLine.DefaultDimension; newTimesheetLine.TaxGroupId = _timesheetLine.TaxGroupId; newTimesheetLine.TaxItemGroup = _timesheetLine.TaxItemGroup; newTimesheetLine.ProjectDataAreaId = _timesheetLine.ProjectDataAreaId; newTimesheetLine.ProjCompanySalesCurrency = _timesheetLine.ProjCompanySalesCurrency; newTimesheetLine.setNextLineNum(_timesheetNbr); newTimesheetLine.ProjPeriodTimesheetWeek = getTimesheetWeekRecId(_timesheetLine.ProjPeriodTimesheetWeek); return newTimesheetLine; } ////// Init from timesheetLine Value /// /// _isNew public void postLineInserts( TSTimesheetTable _tsTimesheetTable, boolean _isNew) { TsTimesheetTableLog tsTimesheetTableLog; if (ProjParameters::find().TimesheetAuditTrail) { TsTimesheetLog tsTimesheetLog; // if the timesheet is new we need to use the existing table log created in insert if (_isNew) { select firstOnly tsTimesheetTableLog where tsTimesheetTableLog.TimesheetNbr == _tsTimesheetTable.TimesheetNbr; } else { tsTimesheetTableLog = TsTimesheetTableLog::createTableLog(_tsTimesheetTable, '', TsTimesheetChangeType::Update); } tsTimesheetLog.logPostLineInserts(tsTimesheetTableLog); } } ////// update Status To InReview when submitted to workflow /// /// tsTimesheetTable /// isLineItemApprovalConfigured public void updateStatusToInReview(TSTimesheetTable tsTimesheetTable, boolean isLineItemApprovalConfigured) { TSTimesheetLine tsTimesheetLine; TSTimesheetTrans tsTrans; if (tsTimesheetTable.ApprovalStatus != TSAppStatus::Pending) { if (!TSStateChangeManager::validateStatusChange(tsTimesheetTable.ApprovalStatus, TSAppStatus::Pending)) { throw error(strFmt("@SYS108515",tsTimesheetTable.ApprovalStatus, TSAppStatus::Pending)); } tsTimesheetTable.ApprovalStatus = TSAppStatus::Pending; ttsbegin; tsTimesheetTable.doUpdate(); ttscommit; } if (!isLineItemApprovalConfigured) { // The status change is unrelated to other data changes and should not require any row-by-row processing. tsTimesheetLine.skipDataMethods(true); tsTrans.skipDataMethods(true); tsTimesheetLine.skipDatabaseLog(true); tsTrans.skipDatabaseLog(true); tsTimesheetLine.skipEvents(true); tsTrans.skipEvents(true); update_recordset tsTimesheetLine setting ApprovalStatus = TSAppStatus::Pending where tsTimesheetLine.TimesheetNbr == tsTimesheetTable.TimesheetNbr; update_recordset tsTrans setting ApprovalStatus = TSAppStatus::Pending where tsTrans.TimesheetNbr == tsTimesheetTable.TimesheetNbr; } } ////// preLineInserts /// /// _timesheetTable Public void preLineInserts(TSTimesheetTable _timesheetTable) { TsTimesheetLog tsTimesheetLog; if (ProjParameters::find().TimesheetAuditTrail) { tsTimesheetLog = new TsTimesheetLog(); tsTimesheetLog.preLineInserts(_timesheetTable); } } }
Hi Sonali,
From what I can see your code is creating only one extra timesheet and not a new timesheet for the timesheet lines for every project.
I suggest to create a separate function to create a timesheet record and the timesheet lines for each project.
Can you insert your code as formatted text? It makes it a lot easier to read.
Hi,
I have created extension of TsWorkflowActionManager to split timesheet. below is my code.
I have created Timesheet with 3 lines.
Line 1 with project id: SE03-000033_001
Line 2 with project id: SE03-000075_002
Line 3 with project id: SE03-000004_003
As per my code I am splitting timesheet based on project Id. Line 1 remains same in Original Timesheet. For Line 2 & Line 3 I am creating new timesheet with lines using x++ code & submitting automatically to workflow.
But when I am creating 3 lines with different Project id so posting of original line i.e. 1st line is fine as per standard. But 2nd & 3rd line which submitted automatically to workflow by my custom code comes under single Voucher transaction.
For 2nd & 3rd line transaction posted in line no 2. For line 3 there is no transaction. May I know the reason Why transaction comes under in same voucher transaction and not per project wise or timesheet wise.
Below is my code.
[ExtensionOf(classStr(TsWorkflowActionManager))]
final class ADNTsWorkflowActionManager_Extension
{
currentProjId = tsTimesheetLineBuff.ProjId;
public boolean submitToWorkflow(Args _args)
{
//next submitToWorkflow (_args);
TSTimesheetTable tsTimesheetTable,newTimesheetTable;
ProjPeriodLine projPeriodLine;
ProjPeriodTimesheetWeek projPeriodTimesheetWeek;
TsTimesheetLog tstimeSheetLog;
TSTimesheetNbr tsTimesheetNbr;
ProjId firstProjId,currentProjId;
TSTimesheetLine tsTimesheetLine,tsTimesheetLineBuff,tsTimesheetLinelocal,origtimesheetLineDelete;
//boolean result = true;
if(projparameters::find().ADNSplitTimesheetOnProjId && !canceledAction)
{
Common callerRecord = _args.record();
tsTimesheetTable = callerRecord as TSTimesheetTable;
select firstonly projId from tsTimesheetLine
where tsTimesheetLine.TimesheetNbr == tsTimesheetTable.TimesheetNbr;
firstProjId = tsTimesheetLine.ProjId;
while select tsTimesheetLineBuff group by tsTimesheetLineBuff.ProjId
where tsTimesheetLineBuff.ProjId != firstProjId &&
tsTimesheetLineBuff.TimesheetNbr == tsTimesheetTable.TimesheetNbr
{
projPeriodLine = TSTimesheetTable::getValidResourcePeriod(tsTimesheetTable.Resource);
if (this &&
TSTimesheetTable::checkMaxTimesheets(projPeriodLine.PeriodFrom, tsTimesheetTable.Resource, true))
{
if (projPeriodLine.RecId != 0)
{
newTimesheetTable.TimesheetNbr ="";
newTimesheetTable.initValue();
newTimesheetTable.Resource = tsTimesheetTable.Resource;
newTimesheetTable.ProjPeriodId = projPeriodLine.PeriodId;
newTimesheetTable.PeriodFrom = projPeriodLine.PeriodFrom;
newTimesheetTable.PeriodTo = projPeriodLine.PeriodTo;
projPeriodTimesheetWeek = ProjPeriodTimesheetWeek::findFromPeriod(projPeriodLine.PeriodId);
if (projPeriodTimesheetWeek.RecId == 0)
{
warning("@SYS338882");
}
else
{
newTimesheetTable.ProjPeriodTimesheetWeek = projPeriodTimesheetWeek.RecId;
}
ttsBegin;
newTimesheetTable.insert();
//ttscommit;
tsTimesheetNbr = newTimesheetTable.TimesheetNbr;
if (newTimesheetTable.RecId != 0)
{
if (ProjParameters::find().TimesheetAuditTrail)
{
TSTimesheetTableLog::createTableLog(newTimesheetTable, '', TsTimesheetChangeType::Create);
}
if (ProjParameters::find().TimesheetAuditTrail)
{
tsTimesheetLog = new TsTimesheetLog();
tsTimesheetLog.preLineInserts(newTimesheetTable);
}
while select * from tsTimesheetLinelocal
where tsTimesheetLinelocal.ProjId == currentProjId
&& tsTimesheetLinelocal.TimesheetNbr == tsTimesheetTable.TimesheetNbr
&& tsTimesheetLinelocal.ProjId != firstProjId
{
this.createTimesheetLine(tsTimesheetNbr,tsTimesheetLinelocal);
delete_from origtimesheetLineDelete
where origtimesheetLineDelete.RecId == tsTimesheetLinelocal.RecId;
}
this.postLineInserts(newTimesheetTable, true);
if (newTimesheetTable.RecId
&& newTimesheetTable.ApprovalStatus == TSAppStatus::Create)
{
// submitting to workflow;
Workflow::activateFromWorkflowType(
workFlowTypeStr(TSDocumentTemplate),
newTimesheetTable.RecId,
"@ADN:AutoSubmitToWF",
false,
curUserid());
}
ttscommit;
}
}
else
{
throw error("@Timesheet:Timesheet_Not_Created");
}
}
}
}
return next submitToWorkflow (_args);;
}
/// <summary>
/// Create TimesheetLine
/// </summary>
/// <param name = "_timesheetNbr">_timesheetNbr</param>
/// <param name = "_timesheetLine">_timesheetLine</param>
public void createTimesheetLine(TSTimesheetNbr _timesheetNbr, TSTimesheetLine _timesheetLine)
{
TSTimesheetLine newTimesheetLine = this.initFromTsTimesheetLine(_timesheetNbr, _timesheetLine);
if (newTimesheetLine.validateWrite() && newTimesheetLine.ProjPeriodTimesheetWeek != 0)
{
newTimesheetLine.insert();
this.adnloadFromLine(newTimesheetLine, _timesheetLine);
}
}
/// <summary>
/// Create TSTimesheetLineWeek record
/// </summary>
/// <param name = "_tsTimesheetLineNew">_tsTimesheetLineNew</param>
/// <param name = "_tsTimesheetLineOld">_tsTimesheetLineOld</param>
public void adnloadFromLine(TSTimesheetLine _tsTimesheetLineNew, TSTimesheetLine _tsTimesheetLineOld)
{
TSTimesheetLineWeek lineWeek, lineWeekOld;
while select lineWeekOld
where lineWeekOld.TimesheetNbr == _tsTimesheetLineOld.TimesheetNbr
&& lineWeekOld.linenum == _tsTimesheetLineOld.LineNum
&& lineWeekOld.tstimesheetLine == _tsTimesheetLineOld.RecId
{
lineWeek.clear();
buf2Buf(lineWeekOld, lineWeek);
lineWeek.TimesheetNbr = _tsTimesheetLineNew.TimesheetNbr;
lineWeek.LineNum = _tsTimesheetLineNew.LineNum;
lineWeek.tsTimesheetLine = _tsTimesheetLineNew.RecId;
lineWeek.insert();
}
}
/// <summary>
/// Init from timesheetLine Value
/// </summary>
/// <param name = "_timesheetNbr">_timesheetNbr</param>
/// <param name = "_timesheetLine">_timesheetLine</param>
/// <returns>TSTimesheetLine</returns>
public TSTimesheetLine initFromTsTimesheetLine(TSTimesheetNbr _timesheetNbr, TSTimesheetLine _timesheetLine)
{
TSTimesheetLine newTimesheetLine;
TSTimesheetTable targetTimesheet = TSTimesheetTable::find(_timesheetNbr);
RefRecId getTimesheetWeekRecId(RefRecId sourceWeekId)
{
ProjPeriodTimesheetWeek sourcePeriodTimesheetWeek;
ProjPeriodTimesheetWeek targetPeriodTimesheetWeek;
TSTimesheetTable sourceTimesheet = TSTimesheetTable::find(_timesheetLine.TimesheetNbr);
int counter = 0, counter2 = 0;
RefRecId retval = 0;
while select RecId from sourcePeriodTimesheetWeek
order by sourcePeriodTimesheetWeek.PeriodFrom
where sourcePeriodTimesheetWeek.PeriodId == sourceTimesheet.ProjPeriodId
&& sourcePeriodTimesheetWeek.PeriodFrom >= sourceTimesheet.PeriodFrom
&& sourcePeriodTimesheetWeek.PeriodTo <= sourceTimesheet.PeriodTo
{
if (sourcePeriodTimesheetWeek.RecId == sourceWeekId)
{
break;
}
counter++;
}
while select RecId from targetPeriodTimesheetWeek
order by targetPeriodTimesheetWeek.PeriodFrom
where targetPeriodTimesheetWeek.PeriodId == targetTimesheet.ProjPeriodId
&& targetPeriodTimesheetWeek.PeriodFrom >= targetTimesheet.PeriodFrom
&& targetPeriodTimesheetWeek.PeriodTo <= targetTimesheet.PeriodTo
{
// always set the last PeriodWeek
// if the source Period has more weeks than target, then the timesheet lines will be set
// to whatever is the last week of the target period
retval = targetPeriodTimesheetWeek.RecId;
if (counter == counter2)
{
retval = targetPeriodTimesheetWeek.RecId;
break;
}
counter2++;
}
return retval;
}
newTimesheetLine.TimesheetNbr = _timesheetNbr;
newTimesheetLine.ApprovalStatus = TSAppStatus::Create;
newTimesheetLine.LineNum = _timesheetLine.LineNum;
newTimesheetLine.Resource = targetTimesheet.Resource;
newTimesheetLine.ProjId = _timesheetLine.ProjId;
newTimesheetLine.ActivityNumber = _timesheetLine.ActivityNumber;
newTimesheetLine.CategoryId = _timesheetLine.CategoryId;
newTimesheetLine.LinePropertyId = _timesheetLine.LinePropertyId;
newTimesheetLine.WrkCtrId = _timesheetLine.WrkCtrId;
newTimesheetLine.CurrencyCode = _timesheetLine.CurrencyCode;
newTimesheetLine.DefaultDimension = _timesheetLine.DefaultDimension;
newTimesheetLine.TaxGroupId = _timesheetLine.TaxGroupId;
newTimesheetLine.TaxItemGroup = _timesheetLine.TaxItemGroup;
newTimesheetLine.ProjectDataAreaId = _timesheetLine.ProjectDataAreaId;
newTimesheetLine.ProjCompanySalesCurrency = _timesheetLine.ProjCompanySalesCurrency;
newTimesheetLine.setNextLineNum(_timesheetNbr);
newTimesheetLine.ProjPeriodTimesheetWeek = getTimesheetWeekRecId(_timesheetLine.ProjPeriodTimesheetWeek);
return newTimesheetLine;
}
/// <summary>
/// Init from timesheetLine Value
/// </summary>
/// <param name = "_tsTimesheetTable>_tsTimesheetTable</param>
/// <param name = "_isNew">_isNew</param>
public void postLineInserts(
TSTimesheetTable _tsTimesheetTable,
boolean _isNew)
{
TsTimesheetTableLog tsTimesheetTableLog;
if (ProjParameters::find().TimesheetAuditTrail)
{
TsTimesheetLog tsTimesheetLog;
// if the timesheet is new we need to use the existing table log created in insert
if (_isNew)
{
select firstOnly tsTimesheetTableLog where
tsTimesheetTableLog.TimesheetNbr == _tsTimesheetTable.TimesheetNbr;
}
else
{
tsTimesheetTableLog = TsTimesheetTableLog::createTableLog(_tsTimesheetTable, '', TsTimesheetChangeType::Update);
}
tsTimesheetLog.logPostLineInserts(tsTimesheetTableLog);
}
}
}
Hi Gaurangkumar,
We have suggested this option but customer don't want to change the current process & needs this automation of splitting timesheet based on project ids before submitting timesheet to workflow.
HI Sonali Mane
I can understand the situation and delay of dependent tasks due to non approval of other project line.
If I have to resolve this issue, Instead of doing this development, I would ask my user to created timesheet in per project instead of combining timesheet.
Please let us know if this helps.
Thanks,
Thanks for your reply Anton!
Timesheet lines are approved by the project manager or line manager Lines in the timesheet are not posted to the projects before all lines in the timesheet are approved. This makes a project dependent on other project approvals from other project managers which may delay invoicing, estimates and cost follow-up.
For that reason, timesheets will automatically be split on project id, meaning that one timesheet only contains one project id. Dependencies to other project managers approval will then disappear.
I have not tested this, but this will get you started. Have a look at the validateSubmit method of the TsTimesheetTable table. It is called by the TsTimesheetService and TsWorkflowActionManager classes. Your code can be added in this method or perhaps in those two classes.
By the way, what is the reason for splitting the timesheets per project?
André Arnaud de Cal...
291,969
Super User 2025 Season 1
Martin Dráb
230,842
Most Valuable Professional
nmaenpaa
101,156