Scrap Costing

We’re running into a costing issue in Epicor Kinetic related to scrap on a subcontracted part. (Admins: I didn’t know the best title for this post, so feel free to change it if needed.)

Process Overview

  • Operation 20 is a subcontract operation.
    • We send 120 pieces out to a vendor.
    • The vendor processes them and returns all 120 pieces.
    • The cost for this operation is roughly $30 per part.
    • Total cost for this operation = about $3600.
  • Operation 40 is an internal operation where we take each of these long bars and cut them into 210 smaller pieces per bar.
    • This operation regularly produces some amount of scrap.
    • This operation takes the 120 parts from op 20, and generates a total of about 25200 parts.
    • At the op scrap was generated we have only completed about 4400 parts.
    • Our scrap cost is being calculated at $3600 / 120pc = $30/pc
    • Instead of $3600 / 25200pc = $0.15/pc
    • I’m doing all this in a custom scrap costing BAQ (attached).
    • I calculate scrap cost using the Total Actual Labor Cost for the operation divided by the number of completed pieces at that operation. Then multiply that cost by the number of scrapped parts.
    • The goal of this BAQ is to see the costs that went into parts that get scrapped. We want the total cost of all operations and subassemblies that went into creating the scrapped part.

What We Need Help Understanding

  • Is Epicor capable of allocating costs when one high‑value part is transformed into many lower‑value parts at a later operation?
  • How should scrap costing work in this type of routing scenario?
  • Are there specific job configuration settings, scrap reporting options, or costing methods we should be using to get Epicor to calculate this correctly?
  • Is the problem entirely in my BAQ, or are we misusing the system somehow?

Here is a snip of my BAQ showing the relevant data for the part in question. Here you can see that the 248 pieces of scrap happend at op 40, and you can see the costs that went into these parts for each operation up through #40. I think I need to change my SCTot calculation to look at the required qty, instead of the completed qty. But I have to do that break out at op 20. Im so confused!

Notes: Although GPT helped generate the SQL that started my BAQ, I had to make a lot of edits to make it run. The BAQ seems to generate correct scrap values in most other jobs. I suspect anytime we run lengths of barstock, this issue could come up.

/*  
 * Disclaimer!!! 
 * This is not a real query being executed, but a simplified version for general vision. 
 * Executing it with any other tool may produce a different result. 
 */
with [AssemblyHierarchy] as 
(select  
	[ja].[Company] as [Company], 
	[ja].[JobNum] as [JobNum], 
	[ja].[AssemblySeq] as [AssemblySeq], 
	[ja].[Parent] as [Parent], 
	[JobHead].[PartNum] as [JobHead_PartNum], 
	[ja].[PartNum] as [ja_PartNum], 
	(0) as [LevelDepth], 
	(ja.AssemblySeq) as [RootAssembly] 

from Erp.JobAsmbl as [ja]
inner join Erp.JobHead as [JobHead] on 
	  ja.Company = JobHead.Company
	and  ja.JobNum = JobHead.JobNum
where ( ja.JobNum = @JobNum  )
union all
select  
	[child].[Company] as [child_Company], 
	[child].[JobNum] as [child_JobNum], 
	[child].[AssemblySeq] as [child_AssemblySeq], 
	[child].[Parent] as [child_Parent], 
	[parent].[JobHead_PartNum] as [JobHead_PartNum], 
	[parent].[ja_PartNum] as [ja_PartNum], 
	((parent.LevelDepth + 1)) as [Calculated_LevelDepth], 
	[parent].[RootAssembly] as [RootAssembly] 

from Erp.JobAsmbl as [child]
inner join  AssemblyHierarchy  as [parent] on 
	  child.Company = parent.Company
	and  child.JobNum = parent.JobNum
	and  child.Parent = parent.AssemblySeq
	and  child.AssemblySeq <> parent.AssemblySeq)
 ,[ConsumedAsm] as 
(select  
	[JobMtl].[Company] as [JobMtl_Company], 
	[JobMtl].[JobNum] as [JobMtl_JobNum], 
	[JobMtl].[AssemblySeq] as [JobMtl_AssemblySeq], 
	[JobAsmbl1].[AssemblySeq] as [JobAsmbl1_AssemblySeq] 

from Erp.JobMtl as [JobMtl]
inner join Erp.JobAsmbl as [JobAsmbl1] on 
	  JobMtl.Company = JobAsmbl1.Company
	and  JobMtl.JobNum = JobAsmbl1.JobNum
	and  JobMtl.PartNum = JobAsmbl1.PartNum
where (JobMtl.JobNum = @JobNum))
 ,[Scrap] as 
(select  
	[ld].[Company] as [ld_Company], 
	[ld].[JobNum] as [ld_JobNum], 
	[ld].[AssemblySeq] as [ld_AssemblySeq], 
	[ld].[OprSeq] as [ld_OprSeq], 
	(SUM(ld.ScrapQty)) as [ScrapQty] 

from Erp.LaborDtl as [ld]
where ( ld.JobNum = @JobNum  
and ld.AssemblySeq = @AssemblySeq  
and ld.ScrapQty > 0  
and ld.OprSeq <= @ThruOp  )
group by 
	[ld].[Company], 
	[ld].[JobNum], 
	[ld].[AssemblySeq], 
	[ld].[OprSeq])
 ,[AccumulatedCost] as 
(select  
	[s].[ld_Company] as [ld_Company], 
	[s].[ld_JobNum] as [ld_JobNum], 
	[s].[ld_AssemblySeq] as [ld_AssemblySeq], 
	[s].[ld_OprSeq] as [ld_OprSeq], 
	[s].[ScrapQty] as [ScrapQty], 
	[h].[JobHead_PartNum] as [JobHead_PartNum], 
	[h].[ja_PartNum] as [ja_PartNum], 
	[jo].[AssemblySeq] as [jo_AssemblySeq], 
	[jo].[OprSeq] as [jo_OprSeq], 
	(ISNULL(jo.ActLabCost, 0) + ISNULL(jo.ActBurCost, 0) + ISNULL(jm.TotalCost, 0)) as [TotalAccumulatedCost], 
	(ISNULL(jo.QtyCompleted, 0)) as [QtyCompleted], 
	[jo].[SubContract] as [jo_SubContract], 
	[jo].[ActLabCost] as [jo_ActLabCost], 
	[jo].[ActBurCost] as [jo_ActBurCost], 
	[jm].[TotalCost] as [jm_TotalCost] 

from  Scrap  as [s]
inner join  AssemblyHierarchy  as [h] on 
	  s.ld_Company = h.Company
	and  s.ld_JobNum = h.JobNum
	and  s.ld_AssemblySeq = h.AssemblySeq
left outer join  ConsumedAsm  as [ConsumedAsm] on 
	  h.Company = ConsumedAsm.JobMtl_Company
	and  h.JobNum = ConsumedAsm.JobMtl_JobNum
	and  h.AssemblySeq = ConsumedAsm.JobAsmbl1_AssemblySeq
left outer join Erp.JobOper as [jo] on 
	  h.Company = jo.Company
	and  h.JobNum = jo.JobNum
	and  (h.AssemblySeq = jo.AssemblySeq
	or  ConsumedAsm.JobMtl_AssemblySeq = jo.AssemblySeq)
	and ( jo.OprSeq <= s.ld_OprSeq  )
left outer join Erp.JobMtl as [jm] on 
	  jo.Company = jm.Company
	and  jo.JobNum = jm.JobNum
	and  jo.AssemblySeq = jm.AssemblySeq
	and  jo.OprSeq = jm.RelatedOperation
left outer join Erp.JobAsmbl as [JobAsmbl] on 
	  jm.Company = JobAsmbl.Company
	and  jm.JobNum = JobAsmbl.JobNum
	and  jm.AssemblySeq = JobAsmbl.AssemblySeq)

select distinct 
	[AccumulatedCost].[ld_Company] as [ld_Company], 
	[AccumulatedCost].[ld_JobNum] as [ld_JobNum], 
	[AccumulatedCost].[ld_AssemblySeq] as [ld_AssemblySeq], 
	[AccumulatedCost].[ld_OprSeq] as [ld_OprSeq], 
	[AccumulatedCost].[ScrapQty] as [ScrapQty], 
	[AccumulatedCost].[TotalAccumulatedCost] as [TotalAccumulatedCost], 
	(iif(AccumulatedCost.QtyCompleted=0,0, iif(AccumulatedCost.TotalAccumulatedCost=0,0,AccumulatedCost.TotalAccumulatedCost / AccumulatedCost.QtyCompleted))) as [UnitCostAtScrap], 
	(iif(AccumulatedCost.QtyCompleted=0,0,iif(AccumulatedCost.TotalAccumulatedCost=0,0,iif(AccumulatedCost.ScrapQty=0,0,(((AccumulatedCost.TotalAccumulatedCost / AccumulatedCost.QtyCompleted)) * AccumulatedCost.ScrapQty) )))) as [ScrapCost], 
	[AccumulatedCost].[jo_AssemblySeq] as [jo_AssemblySeq], 
	[AccumulatedCost].[jo_OprSeq] as [jo_OprSeq], 
	[AccumulatedCost].[QtyCompleted] as [QtyCompleted], 
	(iif(iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted)=0,0,iif(AccumulatedCost.jo_SubContract=0, AccumulatedCost.jo_ActLabCost/ iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted), 0))) as [Calculated_LabTot], 
	(iif(iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted)=0,0,AccumulatedCost.jm_TotalCost / iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted))) as [Calculated_MatTot], 
	(iif(iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted)=0,0,iif(AccumulatedCost.jo_SubContract=1, AccumulatedCost.jo_ActLabCost/ iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted), 0))) as [Calculated_SCTot], 
	(iif(iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted)=0,0,AccumulatedCost.jo_ActBurCost / iif(AccumulatedCost.QtyCompleted=0,0,AccumulatedCost.QtyCompleted))) as [Calculated_BurTot] 

from  AccumulatedCost  as [AccumulatedCost]

ScrapCostingKinetic (1).baq (22.0 KB)

The observant among you may notice that op 10 is in the same boat. Lets stay focused on op 20, as that should resolve the issue for both.
Thank you for your time!
Nate

For context, an AI generated this “solution”.

The issue is not Epicor incorrectly costing the job — it is the scaling of qty between operations.
Epicor allocates Op 20 subcontract cost to the 120 bars, not the 25,200 cut pieces.
The BAQ is dividing Op 20 cost by CompletedQty at Op 40, which mixes two incompatible units of measure.
The fix is to calculate cost per bar at the operation where the cost occurred, then prorate that cost across the full expected yield of the cutting operation.
Formula correction:
ScrapUnitCost = TotalActualCost(Op) / FinalYield(Op40)

This sounds like the solution that we figured out too, but it seems awfully manual. I am not sure how I could get this kind of formula into a BAQ. I can manually calculate it outside Epicor. I am really curious if anyone else even looks at scrap at this level.
Thanks again!

Sorry we do use scrap, so I may be off-base but I wonder if your cut piece should be an assembly consuming the 120ea bars @ 0.00476 (1/210) quantity per parent. At least then mtl cost reporting 248/4471 scrap may be proportional.

For example, this Job ProdQty: 25,200 having FORMBAR SubAsm @ 0.00478 per parent shows RequiredQty = 120ea. BAR raw mtl is 1 per parent Asm FORMBAR. This Asm is what you get back from your SubCon.

CUT Opr20 consumes FORMBAR and now reporting qty and scrap is proportional?

I suspect it may still include labor cost from your op40 though. Sounds like you want to show only ‘prior mtl cost’ for scrap cost.

2 Likes

No, I do want the costs for all ops all the way up through scrap. If we did labor on op40, then scrapped parts, I want those labor costs included. That is the most realistic scenario.

Our setup is not that different from what you posted, the op has a qty/parent of 0.004.

The next op 40, has qty/parent of 1. You think if we switch op20 to an assembly it might record the costs correctly? Isn’t consuming the material on op40, the same as consuming it in an assembly? I admit, I’m not an engineer and don’t know this as well as I should.

Thanks!

1 Like

I’m not strong on this either but as long as you have a qty/parent factor and scrap costs are working in on your jobs, you should be able to work it out to a lower level in your query. So no you probably don’t need an asm. I don’t see that factor in your query.

Do you mean the 0.004/parent factor? I don’t want to hard code that in to the BAQ. Am I missing something?

Not hard code but in your calcs. I would think that’d be needed. Along with Job ProdQty to make it work. :man_shrugging:

2 Likes

multiply by jo.QtyPer everywhere you have jo.QtyCompleted and see what happens. :man_shrugging:

2 Likes

BAM!
bam yes GIF

This did the trick! Thank you!!!

2 Likes

For anyone wanting to see what their scrap cost looks like, try this out!
ScrapCostingKinetic2.baq (20.6 KB)
I would love to hear if you find any issue with it.

:fire:

^ off-topic: im gonna keep posting this unitl we get fire emoji back plus party parrot makes me dizzy. :wink:

2 Likes

I may have introduced some errors into the last version. I am fixing them and will repost.

I had some weird nested iifs that I didn’t need. I cleaned up those calculated fields. I also made sure the material cost pulls through even if the qty completed is blank. I did this by using the run qty instead of qty completed if the qty is 0.

ScrapCostingKinetic2 (1).baq (20.8 KB)