Procházet zdrojové kódy

Initial commit from local directory

Kitt Parker před 11 měsíci
revize
d64c330679
2 změnil soubory, kde provedl 389 přidání a 0 odebrání
  1. 332 0
      ProjectEntryExt.cs
  2. 57 0
      ProjectEntryLaborAdjust.csproj

+ 332 - 0
ProjectEntryExt.cs

@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using PX.Common;
+using PX.Data;
+using PX.Objects.CM;
+using PX.Objects.GL;
+using PX.Objects.IN;
+using PX.Objects.PM;
+
+namespace ProjectEntryLaborAdjust
+{
+
+   
+    public class ProjectEntryExt : PXGraphExtension<ProjectEntry>
+    {
+        private const string SlotLaborUpdate = "RevBudSetLabor";   // <-- new flag
+        private const string LaborInvCD = "L023";
+        private const string ExcludedBranchCD = "301";
+        private const string ExcludedBillRule = "PROGRESS";
+        private const decimal PMRate = 90m;
+        private int? _laborInvID;
+        private int? LaborInvID
+        {
+            get
+            {
+                if (_laborInvID == null)
+                    _laborInvID = InventoryItem.UK.Find(Base, LaborInvCD)?.InventoryID;
+                return _laborInvID;
+            }
+        }
+
+        protected bool Skip => Base.IsImport || Base.IsContractBasedAPI || Base.IsCopyPasteContext;
+
+        protected void _(Events.RowUpdated<PMRevenueBudget> e)
+        {
+            if (IsExcludedProject())      // ← add this line
+                return;
+
+            var row = (PMRevenueBudget)e.Row;
+            var old = (PMRevenueBudget)e.OldRow;
+            if (row == null || old == null || Skip) return;
+
+            // Prevent recursion
+            if (PXContext.GetSlot<bool?>(SlotLaborUpdate) == true) return;
+
+
+
+            // Only react when Original Budgeted Amount changed
+            if (e.Cache.ObjectsEqual<PMRevenueBudget.curyAmount>(row, old))
+            {
+                return;
+            }
+            // Ignore if user is editing the labor line itself
+
+            if (LaborInvID != null && row.InventoryID == LaborInvID) return;
+
+            // ---- Calculate totals/hours (exclude labor line) -----------------
+            decimal totalRevised = GetRevisedRevenueTotalUI(LaborInvCD);
+            decimal pmHours = GetPMHours(totalRevised);
+
+            PXContext.SetSlot<bool?>(SlotLaborUpdate, true);
+            try
+            {
+                var cache = Base.RevenueBudget.Cache;
+                PMRevenueBudget laborLine = FindRevenueLineByInventoryCd(LaborInvCD);
+               
+                if (laborLine != null)
+                {
+                    var c = Base.RevenueBudget.Cache;
+
+                    if (laborLine.Qty != pmHours)
+                        c.SetValueExt<PMRevenueBudget.qty>(laborLine, pmHours);
+
+                    if (laborLine.CuryUnitRate != PMRate)
+                        c.SetValueExt<PMRevenueBudget.curyUnitRate>(laborLine, PMRate);
+
+                    // DO NOT set curyAmount – let the formula do it.
+                    c.Update(laborLine);
+                }
+                else
+                {
+                    CreateRevenueBudgetLine(
+                        inventoryCD: LaborInvCD,
+                        taskID: row.ProjectTaskID,
+                        accountGroupID: row.AccountGroupID,
+                        qty: pmHours,
+                        rate: PMRate,
+                        amount: pmHours * PMRate,
+                        uom: row.UOM,
+                        description: "Auto-added PM Labor"
+                    );
+                }
+            }
+            finally
+            {
+                PXContext.SetSlot<bool?>(SlotLaborUpdate, false);
+            }
+
+            // ---- Popup & save -----------------------------------
+            /*ShowPopupOnce(totalOrig);*/
+            // Base.Actions.PressSave();
+        }
+
+        protected void _(Events.RowPersisting<PMRevenueBudget> e)
+        {
+            if (IsExcludedProject())      // ← add this line
+                return;
+
+            var row = (PMRevenueBudget)e.Row;
+            if (row == null || e.Operation == PXDBOperation.Delete) return;
+            if (Skip || PXContext.GetSlot<bool?>(SlotLaborUpdate) == true) return;
+
+            if (LaborInvID != null && row.InventoryID == LaborInvID)
+            {
+                // ensure both currency and base rates are correct right before save
+                decimal baseRate;
+                PXCurrencyAttribute.CuryConvBase<PMRevenueBudget.curyUnitRate>(e.Cache, row, PMRate, out baseRate);
+
+                if (row.CuryUnitRate != PMRate)
+                    e.Cache.SetValue<PMRevenueBudget.curyUnitRate>(row, PMRate);
+            }
+        }
+
+
+        private bool IsExcludedProject()
+        {
+            PMProject proj = Base.Project.Current;
+            if (proj == null) return false;
+
+            // Branch check (BranchCD = 301) --------------------------------
+            if (proj.DefaultBranchID != null)
+            {
+                Branch br = PXSelect<Branch,
+                                Where<Branch.branchID, Equal<Required<Branch.branchID>>>>
+                            .Select(Base, proj.DefaultBranchID);
+                if (br?.BranchCD?.Trim() == ExcludedBranchCD)
+                    return true;
+            }
+
+            // Billing‑rule check (BillingRule = PROGRESS) ------------------
+            // Field name differs by Acumatica build; common ones shown.
+            string billRule = proj.BillingID;   // e.g., 2021 R1+
+
+            if (!string.IsNullOrEmpty(billRule) &&
+                string.Equals(billRule.Trim(), ExcludedBillRule,
+                              StringComparison.OrdinalIgnoreCase))
+                return true;
+
+            return false;
+        }
+
+
+        public static bool IsActive()
+        {
+            return true;
+        }
+
+
+        protected void _(Events.FieldUpdated<PMRevenueBudget, PMRevenueBudget.curyUnitRate> e)
+        {
+            var r = (PMRevenueBudget)e.Row;
+            if (r == null) return;
+            PXTrace.WriteInformation($"curyUnitRate UPDATED -> {r.CuryUnitRate} (Inv={r.InventoryID})");
+        }
+
+
+        private PMRevenueBudget FindRevenueLineByInventoryCd(string inventoryCD)
+        {
+            if (string.IsNullOrEmpty(inventoryCD))
+                return null;
+
+            // Resolve InventoryID from the CD
+            InventoryItem inv = InventoryItem.UK.Find(Base, inventoryCD);
+            if (inv == null) return null;
+
+            int? projID = Base.Project.Current?.ContractID;
+            if (projID == null) return null;
+
+            // 1) Cache (includes unsaved rows already on screen)
+            var cached = Base.RevenueBudget.Cache.Cached
+                            .Cast<PMRevenueBudget>()
+                            .FirstOrDefault(r => r.ProjectID == projID
+                                              && r.InventoryID == inv.InventoryID
+                                              && r.Type == AccountType.Income);
+            if (cached != null) return cached;
+
+            // 2) DB (readonly, excludes unsaved edits)
+            var db = PXSelectReadonly<PMRevenueBudget,
+                        Where<PMRevenueBudget.projectID, Equal<Required<PMRevenueBudget.projectID>>,
+                          And<PMRevenueBudget.inventoryID, Equal<Required<PMRevenueBudget.inventoryID>>,
+                          And<PMRevenueBudget.type, Equal<AccountType.income>>>>>
+                     .Select(Base, projID, inv.InventoryID)
+                     .RowCast<PMRevenueBudget>()
+                     .FirstOrDefault();
+
+            return db;
+        }
+
+        public PMRevenueBudget CreateRevenueBudgetLine(
+           int? inventoryID = null, string inventoryCD = null,
+           int? taskID = null, string taskCD = null,
+           int? accountGroupID = null, string accountGroupCD = null,
+           decimal? qty = null,
+           decimal? rate = null,
+           decimal? amount = null,  // if null, amount = qty * rate (when both provided)
+           string uom = null,
+           string description = null,
+           int? costCodeID = null   // optional
+       )
+        {
+            // Resolve keys ----------------------------------------------------
+            int? projectID = Base.Project.Current?.ContractID;
+            if (projectID == null)
+                throw new PXException();
+
+            if (inventoryID == null && !string.IsNullOrEmpty(inventoryCD))
+                inventoryID = InventoryItem.UK.Find(Base, inventoryCD)?.InventoryID;
+
+            if (taskID == null && !string.IsNullOrEmpty(taskCD))
+                taskID = PXSelect<PMTask,
+                            Where<PMTask.projectID, Equal<Required<PMTask.projectID>>,
+                              And<PMTask.taskCD, Equal<Required<PMTask.taskCD>>>>>
+                         .Select(Base, projectID, taskCD)
+                         ?.FirstOrDefault()?.GetItem<PMTask>()?.TaskID;
+
+            if (accountGroupID == null && !string.IsNullOrEmpty(accountGroupCD))
+                accountGroupID = PMAccountGroup.UK.Find(Base, accountGroupCD)?.GroupID;
+
+            if (qty == null) qty = 0m;
+            if (rate == null) rate = 0m;
+            if (amount == null) amount = qty * rate;
+
+            // Insert skeleton row --------------------------------------------
+            var cache = Base.RevenueBudget.Cache;
+            var line = new PMRevenueBudget
+            {
+                ProjectID = projectID,
+                Type = AccountType.Income  // revenue
+            };
+
+            line = (PMRevenueBudget)cache.Insert(line);
+
+            // Set fields via SetValueExt so defaults/validations run ----------
+            if (taskID != null)
+                cache.SetValueExt<PMRevenueBudget.projectTaskID>(line, taskID);
+
+            if (accountGroupID != null)
+                cache.SetValueExt<PMRevenueBudget.accountGroupID>(line, accountGroupID);
+
+            if (inventoryID != null)
+                cache.SetValueExt<PMRevenueBudget.inventoryID>(line, inventoryID);
+
+            if (costCodeID != null)
+                cache.SetValueExt<PMRevenueBudget.costCodeID>(line, costCodeID);
+
+            if (!string.IsNullOrEmpty(uom))
+                cache.SetValueExt<PMRevenueBudget.uOM>(line, uom);
+
+            if (qty != null)
+                cache.SetValueExt<PMRevenueBudget.qty>(line, qty);
+
+            if (rate != null)
+                cache.SetValueExt<PMRevenueBudget.curyUnitRate>(line, rate);
+
+            if (amount != null)
+                cache.SetValueExt<PMRevenueBudget.curyAmount>(line, amount);
+
+            if (!string.IsNullOrEmpty(description))
+                cache.SetValueExt<PMRevenueBudget.description>(line, description);
+
+            // Final update so cache/state is consistent
+            line = (PMRevenueBudget)cache.Update(line);
+
+
+
+            return line;
+        }
+
+
+        private decimal GetPMHours(decimal jobSize)
+        {
+            if (jobSize < 0m) return 0m; // or throw
+
+            switch (jobSize)
+            {
+                case var n when n <= 1000m: return 1.0m;
+                case var n when n <= 2500m: return 1.5m;
+                case var n when n <= 5000m: return 3.0m;
+                case var n when n <= 7500m: return 3.5m;
+                case var n when n <= 10000m: return 5.0m;
+                case var n when n <= 25000m: return 6.0m;
+                case var n when n <= 50000m: return 9.0m;
+                default: return 10.5m;
+            }
+        }
+
+
+
+
+        private decimal GetRevisedRevenueTotalUI(params string[] excludeCDs)
+        {
+            // Resolve the IDs to skip
+            var skip = new HashSet<int?>(
+                (excludeCDs ?? new string[0])
+                .Select(cd => InventoryItem.UK.Find(Base, cd)?.InventoryID)
+                .Where(id => id != null)
+                .Cast<int?>()
+            );
+
+            decimal total = 0m;
+
+            // Select() returns rows currently in cache + remaining DB rows for the view
+            foreach (PMRevenueBudget line in Base.RevenueBudget.Select().RowCast<PMRevenueBudget>())
+            {
+                if (line.Type == AccountType.Income && !skip.Contains(line.InventoryID))
+                    total += line.CuryRevisedAmount ?? 0m;   // or CuryAmount if you want "original"
+            }
+
+            return total;
+        }
+
+
+
+
+    }
+
+
+
+
+}

+ 57 - 0
ProjectEntryLaborAdjust.csproj

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{6FA79278-5D42-40F2-BFB9-58BAC87CE814}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ProjectEntryLaborAdjust</RootNamespace>
+    <AssemblyName>ProjectEntryLaborAdjust</AssemblyName>
+    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="PX.Common">
+      <HintPath>..\..\..\..\..\Program Files\Acumatica ERP\AcumaticaERP\Bin\PX.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="PX.Data">
+      <HintPath>..\..\..\..\..\Program Files\Acumatica ERP\AcumaticaERP\Bin\PX.Data.dll</HintPath>
+    </Reference>
+    <Reference Include="PX.Objects">
+      <HintPath>..\..\..\..\..\Program Files\Acumatica ERP\AcumaticaERP\Bin\PX.Objects.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ProjectEntryExt.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>