projectmargin.sql 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. SELECT
  2. -- Base identifiers
  3. c.CompanyID,
  4. c.ContractID,
  5. c.ContractCD,
  6. c.NoteID,
  7. -- Customer (single alias only; avoids duplicate joins)
  8. cust.BAccountID AS CustomerID,
  9. cust.AcctCD AS CustomerCD,
  10. cust.AcctName AS CustomerName,
  11. -- Branch details
  12. br.BranchID AS BranchKey,
  13. br.BranchCD AS BranchCD,
  14. ba.AcctName AS BranchName,
  15. -- Contract attributes (single-pass KV collapse)
  16. ckv.EstimatedComplete,
  17. ckv.CustomerPO,
  18. ckv.EventName,
  19. -- Budget / actuals (set-based rollups)
  20. COALESCE(rb.RevisedBudgetedIncome, 0) AS RevisedBudgetedIncome,
  21. COALESCE(rb.RevisedBudgetedExpense, 0) AS RevisedBudgetedExpense,
  22. COALESCE(tx.TotalExpenses, 0) AS TotalExpenses,
  23. -- Project metadata
  24. c.Status AS ProjectStatus,
  25. c.CreatedDateTime AS ProjectCreatedDateTime,
  26. c.LastModifiedDateTime AS ProjectLastModifiedDateTime,
  27. -- Project executive (resolved via KV once; no separate execNote scan)
  28. bAExec.AcctCD AS ExecutiveNumber,
  29. bAExec.AcctName AS ExecutiveName,
  30. ebranch.AcctCD AS ParentCategory,
  31. execEmp.UserID AS ExecutivePKID,
  32. -- Last modified by
  33. lmu.PKID AS LastModifiedByKey,
  34. lmu.Username AS LastModifiedUserName,
  35. lmu.FullName AS LastModifiedName,
  36. -- Period / notes
  37. gls.FinPeriodID,
  38. clna.Value AS FinanceNoteOnGMLoss,
  39. mlna.Value AS MarginLossAmount,
  40. -- Customer location & region
  41. cl.LocationCD AS CustomerServiceLocation,
  42. ca.City,
  43. ca.State,
  44. -- Billing and customer-type
  45. c.BillingID,
  46. bakv.CustomerType,
  47. lkv.Region,
  48. /* ========= NEW: Per-account GL revenue/expense sums (pre-aggregated, then joined) =========
  49. - Single scan over GLTran grouped by (CompanyID, ProjectID, FinPeriodID).
  50. - SUM(CASE...) produces wide, analytic columns without repeating logic in the outer SELECT.
  51. - Keeps the outer query at the same grain; avoids adding GROUP BY to the main SELECT.
  52. ============================================================================================ */
  53. COALESCE(gls.BilledRevenue4001, 0) AS BilledRevenue4001, -- 4001
  54. COALESCE(gls.TMUnbilledRevenue4002, 0) AS TMUnbilledRevenue4002, -- 4002
  55. COALESCE(gls.LumpSumWIPRevenue4000, 0) AS LumpSumWIPRevenue4000, -- 4000
  56. COALESCE(gls.BillingsLumpSum4003, 0) AS BillingsLumpSum4003, -- 4003
  57. COALESCE(gls.TotalExpensesCOGS, 0) AS TotalExpensesCOGS, -- 5000-5999 (net debit)
  58. COALESCE(gls.ClientDiscounts4007, 0) AS ClientDiscounts4007, -- 4007
  59. COALESCE(gls.OtherIncome4500, 0) AS OtherIncome4500, -- 4500
  60. COALESCE(gls.Refunds4502, 0) AS Refunds4502, -- 4502
  61. COALESCE(gls.JCLabor5000, 0) AS JCLabor5000, -- 5000
  62. COALESCE(gls.JCLaborBurden5001, 0) AS JCLaborBurden5001, -- 5001
  63. COALESCE(gls.JCSubcontractor5010, 0) AS JCSubcontractor5010, -- 5010
  64. COALESCE(gls.JCMaterials5020, 0) AS JCMaterials5020, -- 5020
  65. COALESCE(gls.JCEquipmentInternal5030,0) AS JCEquipmentInternal5030, -- 5030
  66. COALESCE(gls.JCEquipmentExternal5040,0) AS JCEquipmentExternal5040, -- 5040
  67. COALESCE(gls.JCConsultant5041, 0) AS JCConsultant5041, -- 5041
  68. COALESCE(gls.JCTravel5050, 0) AS JCTravel5050, -- 5050
  69. COALESCE(gls.JCOther5055, 0) AS JCOther5055, -- 5055
  70. COALESCE(gls.JCNonAllocating5056, 0) AS JCNonAllocating5056, -- 5056
  71. COALESCE(gls.WIPCOGS5099, 0) AS WIPCOGS5099, -- 5099
  72. COALESCE(gls.ShopSuppliesIndirect5100,0) AS ShopSuppliesIndirect5100, -- 5100
  73. COALESCE(gls.ShopExpensesSmallTools5101,0) AS ShopExpensesSmallTools5101, -- 5101
  74. COALESCE(gls.ShopExpenseUniforms5102,0) AS ShopExpenseUniforms5102, -- 5102
  75. COALESCE(gls.ShopIndirectLabor5103, 0) AS ShopIndirectLabor5103, -- 5103
  76. COALESCE(gls.DirectLabor5104, 0) AS DirectLabor5104, -- 5104
  77. COALESCE(gls.IndirectQuotingCosts5105,0) AS IndirectQuotingCosts5105, -- 5105
  78. COALESCE(gls.PassThroughCost5108, 0) AS PassThroughCost5108, -- 5108
  79. COALESCE(gls.PurchasePriceVariance5109,0) AS PurchasePriceVariance5109, -- 5109
  80. COALESCE(gls.WarrantyWork5110, 0) AS WarrantyWork5110, -- 5110
  81. COALESCE(gls.EstSubcontractorCosts5111,0) AS EstSubcontractorCosts5111, -- 5111
  82. COALESCE(gls.CatTravelExpenses5115, 0) AS CatTravelExpenses5115, -- 5115
  83. COALESCE(gls.DiscountsGiven5201, 0) AS DiscountsGiven5201, -- 5201
  84. COALESCE(gls.DiscountsTaken5202, 0) AS DiscountsTaken5202, -- 5202
  85. COALESCE(gls.ClientBillingExpense5210,0) AS ClientBillingExpense5210, -- 5210
  86. COALESCE(gls.BadDebt5300, 0) AS BadDebt5300, -- 5300
  87. COALESCE(gls.TruckStock5057, 0) AS TruckStock5057, -- 5057
  88. COALESCE(gls.IndirectCOGSFleetExpenses5107,0) AS IndirectCOGSFleetExpenses5107,-- 5107
  89. COALESCE(gls.IndirectLaborBurden5106,0) AS IndirectLaborBurden5106, -- 5106
  90. ( COALESCE(gls.JCLabor5000,0)
  91. + COALESCE(gls.JCLaborBurden5001,0)
  92. + COALESCE(gls.JCSubcontractor5010,0)
  93. + COALESCE(gls.JCMaterials5020,0)
  94. + COALESCE(gls.JCEquipmentExternal5040,0)
  95. + COALESCE(gls.JCOther5055,0)
  96. + COALESCE(gls.DirectLabor5104,0)
  97. + COALESCE(gls.DiscountsTaken5202,0)
  98. + COALESCE(gls.JCEquipmentInternal5030,0)
  99. + COALESCE(gls.WIPCOGS5099,0)
  100. ) AS DirectCOGSSelected,
  101. ( COALESCE(gls.ShopSuppliesIndirect5100,0)
  102. + COALESCE(gls.ShopExpensesSmallTools5101,0)
  103. + COALESCE(gls.ShopExpenseUniforms5102,0)
  104. + COALESCE(gls.ShopIndirectLabor5103,0)
  105. + COALESCE(gls.IndirectQuotingCosts5105,0)
  106. + COALESCE(gls.PassThroughCost5108,0)
  107. + COALESCE(gls.PurchasePriceVariance5109,0)
  108. + COALESCE(gls.EstSubcontractorCosts5111,0)
  109. + COALESCE(gls.ClientBillingExpense5210,0)
  110. + COALESCE(gls.IndirectLaborBurden5106,0)
  111. + COALESCE(gls.IndirectCOGSFleetExpenses5107,0)
  112. + COALESCE(gls.TruckStock5057,0)
  113. + COALESCE(gls.WarrantyWork5110,0)
  114. ) AS IndirectCOGSSelected
  115. FROM Contract c
  116. LEFT JOIN BAccount cust
  117. ON cust.CompanyID = c.CompanyID
  118. AND cust.BAccountID = c.CustomerID
  119. LEFT JOIN Branch br
  120. ON br.CompanyID = c.CompanyID
  121. AND br.BranchID = c.DefaultBranchID
  122. LEFT JOIN BAccount ba
  123. ON ba.CompanyID = br.CompanyID
  124. AND ba.BAccountID = br.BAccountID -- corrected from BACCountID
  125. /* ===== Contract attributes (one scan, many columns) =======================
  126. Single pass over ContractKvExt; MAX(CASE...) safely collapses duplicates.
  127. Eliminates multiple separate KV joins and improves SARGability.
  128. =========================================================================== */
  129. LEFT JOIN (
  130. SELECT
  131. CompanyID,
  132. RecordID,
  133. MAX(CASE WHEN FieldName = 'AttributeESTCOMPL' THEN ValueDate END) AS EstimatedComplete,
  134. MAX(CASE WHEN FieldName = 'AttributePO' THEN ValueString END) AS CustomerPO,
  135. MAX(CASE WHEN FieldName = 'AttributeEVENT' THEN ValueString END) AS EventName,
  136. MAX(CASE WHEN FieldName = 'AttributePROJECTEXE' THEN ValueString END) AS ProjectExecutiveAcctCD
  137. FROM ContractKvExt
  138. WHERE FieldName IN ('AttributeESTCOMPL','AttributePO','AttributeEVENT','AttributePROJECTEXE')
  139. GROUP BY CompanyID, RecordID
  140. ) AS ckv
  141. ON ckv.CompanyID = c.CompanyID
  142. AND ckv.RecordID = c.NoteID
  143. /* Executive resolution via KV value (AcctCD) */
  144. LEFT JOIN BAccount bAExec
  145. ON bAExec.CompanyID = c.CompanyID
  146. AND bAExec.AcctCD = ckv.ProjectExecutiveAcctCD
  147. LEFT JOIN EPEmployee execEmp
  148. ON execEmp.CompanyID = bAExec.CompanyID
  149. AND execEmp.BAccountID = bAExec.BAccountID
  150. LEFT JOIN BAccount ebranch
  151. ON ebranch.CompanyID = bAExec.CompanyID
  152. AND ebranch.BAccountID = bAExec.ParentBAccountID
  153. /* Last modified user (dimension) */
  154. LEFT JOIN Users lmu
  155. ON lmu.CompanyID = c.CompanyID
  156. AND lmu.PKID = c.LastModifiedByID
  157. /* GM loss notes */
  158. LEFT JOIN CSAnswers clna
  159. ON clna.CompanyID = c.CompanyID
  160. AND clna.RefNoteID = c.NoteID
  161. AND clna.AttributeID = 'GMLOSSNOTE'
  162. LEFT JOIN CSAnswers mlna
  163. ON mlna.CompanyID = c.CompanyID
  164. AND mlna.RefNoteID = c.NoteID
  165. AND mlna.AttributeID = 'GMLOSS'
  166. /* Customer service location & address */
  167. LEFT JOIN Location cl
  168. ON cl.CompanyID = c.CompanyID
  169. AND cl.LocationID = c.LocationID
  170. LEFT JOIN Address ca
  171. ON ca.CompanyID = cl.CompanyID
  172. AND ca.AddressID = cl.DefAddressID
  173. /* ===== Location attributes (Region) in one pass ========================= */
  174. LEFT JOIN (
  175. SELECT
  176. CompanyID, RecordID,
  177. MAX(CASE WHEN FieldName = 'AttributeREGION' THEN ValueString END) AS Region
  178. FROM LocationKvExt
  179. WHERE FieldName IN ('AttributeREGION')
  180. GROUP BY CompanyID, RecordID
  181. ) AS lkv
  182. ON lkv.CompanyID = cl.CompanyID
  183. AND lkv.RecordID = cl.NoteID
  184. /* ===== Customer attributes (Type) in one pass =========================== */
  185. LEFT JOIN (
  186. SELECT
  187. CompanyID, RecordID,
  188. MAX(CASE WHEN FieldName = 'AttributeCTYPE' THEN ValueString END) AS CustomerType
  189. FROM BAccountKvExt
  190. WHERE FieldName IN ('AttributeCTYPE')
  191. GROUP BY CompanyID, RecordID
  192. ) AS bakv
  193. ON bakv.CompanyID = cust.CompanyID
  194. AND bakv.RecordID = cust.NoteID
  195. /* ===== Budget rollup (set-based) ======================================= */
  196. LEFT JOIN (
  197. SELECT
  198. CompanyID,
  199. ProjectID,
  200. SUM(CASE WHEN [Type] = 'I' THEN RevisedAmount ELSE 0 END) AS RevisedBudgetedIncome,
  201. SUM(CASE WHEN [Type] = 'E' THEN RevisedAmount ELSE 0 END) AS RevisedBudgetedExpense
  202. FROM PMBudget
  203. GROUP BY CompanyID, ProjectID
  204. ) AS rb
  205. ON rb.CompanyID = c.CompanyID
  206. AND rb.ProjectID = c.ContractID
  207. /* ===== Expense rollup (set-based) ====================================== */
  208. LEFT JOIN (
  209. SELECT
  210. p.CompanyID,
  211. p.ProjectID,
  212. SUM(p.Amount) AS TotalExpenses
  213. FROM PMTran p
  214. JOIN PMAccountGroup pg
  215. ON pg.CompanyID = p.CompanyID
  216. AND pg.GroupID = p.AccountGroupID
  217. WHERE pg.IsExpense = 1
  218. AND p.Released = 1
  219. AND p.Allocated = 0
  220. GROUP BY p.CompanyID, p.ProjectID
  221. ) AS tx
  222. ON tx.CompanyID = c.CompanyID
  223. AND tx.ProjectID = c.ContractID
  224. /* ===== NEW: GL per-account sums (set-based pre-aggregation) =============
  225. - Grouped by (CompanyID, ProjectID, FinPeriodID) so numbers align with the period in the SELECT.
  226. - Applies the same account filter as the outer WHERE for consistency.
  227. - Keeps outer query ungrouped; avoids row explosion and repeated CASE expressions.
  228. =========================================================================== */
  229. LEFT JOIN (
  230. SELECT
  231. gt.CompanyID,
  232. gt.ProjectID,
  233. bb.FinPeriodID,
  234. SUM(CASE WHEN a.AccountCD = '4001' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS BilledRevenue4001,
  235. SUM(CASE WHEN a.AccountCD = '4002' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS TMUnbilledRevenue4002,
  236. SUM(CASE WHEN a.AccountCD = '4000' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS LumpSumWIPRevenue4000,
  237. SUM(CASE WHEN a.AccountCD = '4003' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS BillingsLumpSum4003,
  238. SUM(CASE WHEN TRY_CAST(a.AccountCD AS INT) BETWEEN 5000 AND 5999
  239. THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS TotalExpensesCOGS,
  240. SUM(CASE WHEN a.AccountCD = '4007' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS ClientDiscounts4007,
  241. SUM(CASE WHEN a.AccountCD = '4500' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS OtherIncome4500,
  242. SUM(CASE WHEN a.AccountCD = '4502' THEN COALESCE(gt.CreditAmt,0) - COALESCE(gt.DebitAmt,0) ELSE 0 END) AS Refunds4502,
  243. SUM(CASE WHEN a.AccountCD = '5000' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCLabor5000,
  244. SUM(CASE WHEN a.AccountCD = '5001' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCLaborBurden5001,
  245. SUM(CASE WHEN a.AccountCD = '5010' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCSubcontractor5010,
  246. SUM(CASE WHEN a.AccountCD = '5020' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCMaterials5020,
  247. SUM(CASE WHEN a.AccountCD = '5030' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCEquipmentInternal5030,
  248. SUM(CASE WHEN a.AccountCD = '5040' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCEquipmentExternal5040,
  249. SUM(CASE WHEN a.AccountCD = '5041' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCConsultant5041,
  250. SUM(CASE WHEN a.AccountCD = '5050' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCTravel5050,
  251. SUM(CASE WHEN a.AccountCD = '5055' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCOther5055,
  252. SUM(CASE WHEN a.AccountCD = '5056' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS JCNonAllocating5056,
  253. SUM(CASE WHEN a.AccountCD = '5099' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS WIPCOGS5099,
  254. SUM(CASE WHEN a.AccountCD = '5100' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS ShopSuppliesIndirect5100,
  255. SUM(CASE WHEN a.AccountCD = '5101' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS ShopExpensesSmallTools5101,
  256. SUM(CASE WHEN a.AccountCD = '5102' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS ShopExpenseUniforms5102,
  257. SUM(CASE WHEN a.AccountCD = '5103' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS ShopIndirectLabor5103,
  258. SUM(CASE WHEN a.AccountCD = '5104' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS DirectLabor5104,
  259. SUM(CASE WHEN a.AccountCD = '5105' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS IndirectQuotingCosts5105,
  260. SUM(CASE WHEN a.AccountCD = '5108' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS PassThroughCost5108,
  261. SUM(CASE WHEN a.AccountCD = '5109' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS PurchasePriceVariance5109,
  262. SUM(CASE WHEN a.AccountCD = '5110' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS WarrantyWork5110,
  263. SUM(CASE WHEN a.AccountCD = '5111' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS EstSubcontractorCosts5111,
  264. SUM(CASE WHEN a.AccountCD = '5115' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS CatTravelExpenses5115,
  265. SUM(CASE WHEN a.AccountCD = '5201' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS DiscountsGiven5201,
  266. SUM(CASE WHEN a.AccountCD = '5202' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS DiscountsTaken5202,
  267. SUM(CASE WHEN a.AccountCD = '5210' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS ClientBillingExpense5210,
  268. SUM(CASE WHEN a.AccountCD = '5300' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS BadDebt5300,
  269. SUM(CASE WHEN a.AccountCD = '5057' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS TruckStock5057,
  270. SUM(CASE WHEN a.AccountCD = '5107' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS IndirectCOGSFleetExpenses5107,
  271. SUM(CASE WHEN a.AccountCD = '5106' THEN COALESCE(gt.DebitAmt,0) - COALESCE(gt.CreditAmt,0) ELSE 0 END) AS IndirectLaborBurden5106
  272. FROM GLTran gt
  273. JOIN Account a
  274. ON a.CompanyID = gt.CompanyID
  275. AND a.AccountID = gt.AccountID
  276. JOIN Batch bb
  277. ON bb.CompanyID = gt.CompanyID
  278. AND bb.BatchNbr = gt.BatchNbr
  279. AND bb.Module = gt.Module
  280. WHERE (a.AccountCD IN ('4001','4002','4000','4003','4007','4500','4502')
  281. OR TRY_CAST(a.AccountCD AS INT) BETWEEN 5000 AND 5999)
  282. GROUP BY gt.CompanyID, gt.ProjectID, bb.FinPeriodID
  283. ) AS gls
  284. ON gls.CompanyID = c.CompanyID
  285. AND gls.ProjectID = c.ContractID