How to modify an existing Attribute's base code - acumatica

In the timesheet code there is an attribute called PXWeekSelector2:
What I need to know is how to modify the code of this attribute in a customization project to increase the weekListCount variable to go over 1000 (ours is actually 800, from the year 2005 - which is why we've hit our limit).
Not sure how to override or modify an existing attribute. Any help would be appreciated.
Updated 4/28:
If I look at the PXWeekSelector2Attribute in the source code, this is what I see. I don't see any method like 'Prefetch':
namespace PX.Objects.EP
{
public class PXWeekSelector2Attribute : PXWeekSelectorAttribute
{
public PXWeekSelector2Attribute();
public static int GetNextWeekID(PXGraph graph, int weekID);
public static int GetNextWeekID(PXGraph graph, DateTime date);
public static DateTime GetWeekEndDate(PXGraph graph, int weekId);
public static int GetWeekID(PXGraph graph, DateTime date);
public static WeekInfo GetWeekInfo(PXGraph graph, int weekId);
public static DateTime GetWeekStartDate(PXGraph graph, int weekId);
public static bool IsCustomWeek(PXGraph graph);
protected override IEnumerable GetAllRecords();
public class FullWeekList
{
public FullWeekList();
public static List<EPWeekRaw> Weeks();
}
public class WeekInfo
{
public WeekInfo();
public DayInfo Mon { get; }
public DayInfo Tue { get; }
public DayInfo Wed { get; }
public DayInfo Thu { get; }
public DayInfo Fri { get; }
public DayInfo Sat { get; }
public DayInfo Sun { get; }
public Dictionary<DayOfWeek, DayInfo> Days { get; }
public void AddDayInfo(DateTime date);
public bool IsValid(DateTime date);
}
public class DayInfo
{
public DayInfo(DateTime? date);
public DateTime? Date { get; }
public bool Enabled { get; }
}
}
}

One way is to create your own version of the PXWeekSelector2Attribute where you will change the FullWeekList.Definition.Prefetch method to read as many records as you need like below
public void Prefetch()
{
DateTime curentDay = this._stratDay;
for (int i = 0; i < 1000; i++)
{
int year;
if (curentDay.AddDays(-3.0).Year < curentDay.Year && PXDateTimeInfo.GetWeekNumber(curentDay) > 1)
{
year = curentDay.AddYears(-1).Year;
}
else
{
year = curentDay.Year;
}
EPWeekRaw rawWeek = EPWeekRaw.ToEPWeekRaw(new PXWeekSelectorAttribute.EPWeek
{
WeekID = new int?(year * 100 + PXDateTimeInfo.GetWeekNumber(curentDay))
});
this._weeks.Add(rawWeek);
curentDay = curentDay.AddDays(7.0);
}
}
and copy almost all other methods and members of PXWeekSelector2Attribute and change them to work with your attribute's methods
protected override IEnumerable GetAllRecords()
{
if (PXWeekSelectorCustomAttribute.IsCustomWeek(this._Graph))
{
return (from _ in (PXResultset<EPCustomWeek>)PXSelectBase<EPCustomWeek, PXSelect<EPCustomWeek>.Config>.Select(this._Graph, null).AsEnumerable<PXResult<EPCustomWeek>>()
select EPWeekRaw.ToEPWeekRaw((EPCustomWeek)_)).ToList<EPWeekRaw>();
}
return PXWeekSelectorCustomAttribute.FullWeekList.Weeks();
}
And the last thing will be to overwrite the PXWeekSelector2Attribute with your PXWeekSelectorCustomAttribute using CacheAttached whereever you need.

Related

How to get the current cache/document (Sales Order/Shipment) outside the context of a graph

I'm currently implementing a new carrier method and would like to access additional information on the Shipment/Sales Order object which is not passed through in the GetRateQuote & Ship functions of the implemented ICarrierService class.
The carrier method implements the ICarrierService interface and subsequently does not have access to a Graph where one would typically be able to access the current (cached?) document, etc.
How could I, for example, access the shipment number for which the Ship function is called?
My ultimate goal is to be able to generate a label for the shipment package, and in order to do so, I need to obtain the Shipment Number.
using PX.Api;
using PX.CarrierService;
using PX.CS.Contracts.Interfaces;
using PX.Data;
using PX.Data.Reports;
using PX.Objects.Common.Extensions;
using PX.Reports;
using PX.Reports.Data;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyCarriers.CollectCarrier
{
public class CollectCarrier : ICarrierService
{
private List<CarrierMethod> methods;
private List<string> attributes;
public IList<string> Attributes => (IList<string>)this.attributes;
public string CarrierID { get; set; }
public string Method { get; set; }
public ReadOnlyCollection<CarrierMethod> AvailableMethods => this.methods.AsReadOnly();
public CollectCarrier()
{
this.methods = new List<CarrierMethod>(1);
this.methods.Add(new CarrierMethod("CLT", "Collect"));
this.attributes = new List<string>(1);
}
[...]
public CarrierResult<ShipResult> Ship(CarrierRequest cr)
{
if (cr.Packages == null || cr.Packages.Count == 0)
{
throw new InvalidOperationException("CarrierRequest.Packages must be contain atleast one Package");
}
CarrierResult<ShipResult> carrierResult;
try
{
CarrierResult<RateQuote> rateQuote = this.GetRateQuote(cr, true);
ShipResult result = new ShipResult(rateQuote.Result);
//Report Parameters
Dictionary<String, String> parameters = new Dictionary<String, String>();
// ************************************************************************************
// At this point, I would like to be able to retrieve the current SOShipment's Shipment Number
// ************************************************************************************
parameters["shipmentNbr"] = "000009"; // Hard-coded this value to get the PDF generated.
//Report Processing
PX.Reports.Controls.Report _report = PXReportTools.LoadReport("SO645000", null);
PXReportTools.InitReportParameters(_report, parameters, SettingsProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(_report);
//Generation PDF
result.Image = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterPdf).First();
result.Format = "pdf";
result.Data.Add(new PackageData(
cr.Packages.FirstOrDefault().RefNbr,
this.RandomString(6),
result.Image,
"pdf"
)
{
TrackingData = this.RandomString(6)
});
carrierResult = new CarrierResult<ShipResult>(result);
}
catch (Exception ex)
{
if (this.LogTrace)
{
this.WriteToLog(ex, this.GetType().Name + ".Ship().Exception");
}
List<Message> messageList = this.HandleException(ex);
messageList?.Insert(0, new Message("", "Failed to generate the collection label: "));
carrierResult = new CarrierResult<ShipResult>(false, null, (IList<Message>)messageList);
}
return carrierResult;
}
[...]
}
}
For reference, the CarrierRequest object that is passed to the functions contain the following information:
public class CarrierRequest
{
public string ThirdPartyAccountID
{
get;
set;
}
public string ThirdPartyPostalCode
{
get;
set;
}
public string ThirdPartyCountryCode
{
get;
set;
}
public IAddressBase Shipper
{
get;
set;
}
public IContactBase ShipperContact
{
get;
set;
}
public IAddressBase Origin
{
get;
set;
}
public IContactBase OriginContact
{
get;
set;
}
public IAddressBase Destination
{
get;
set;
}
public IContactBase DestinationContact
{
get;
set;
}
public IList<CarrierBox> Packages
{
get;
set;
}
public IList<CarrierBoxEx> PackagesEx
{
get;
set;
}
public IList<string> Methods
{
get;
set;
}
public DateTime ShipDate
{
get;
set;
}
public UnitsType Units
{
get;
private set;
}
public bool SaturdayDelivery
{
get;
set;
}
public bool Resedential
{
get;
set;
}
public bool Insurance
{
get;
set;
}
public string CuryID
{
get;
private set;
}
public IList<string> Attributes
{
get;
set;
}
public decimal InvoiceLineTotal
{
get;
set;
}
public string FreightClass
{
get;
set;
}
public bool SkipAddressVerification
{
get;
set;
}
public IList<ISETerritoriesMappingBase> TerritoriesMapping
{
get;
set;
}
public CarrierRequest(UnitsType units, string curyID)
{
if (string.IsNullOrEmpty(curyID))
{
throw new ArgumentNullException("curyID");
}
Units = units;
CuryID = curyID;
}
}
I have seen a similar question here on SO, but I'm not entirely sure that is applicable to my specific request?
Any assistance will be highly appreciated.
See below as an option to loop through your currents and search for the specific current object:
SOShipment ship = null;
for (int i = 0; i < Caches.Currents.Length; i++)
{
if (Caches.Currents[i].GetType() == typeof(SOShipment))
{
ship = (SOShipment)Caches.Currents[i];
break;
}
}

Automapper fails for objects with collections where items use ConvertUsing<>()

My example:
class BoxVM {
int BoxId {get;set;}
List<ItemVM> Items {get;set;}
}
class Box {
int BoxId {get;set;}
List<Item> Items {get;set;}
}
With mapping config:
CreateMap<BoxVM, Box>();
CreateMap<ItemVM, Item>().ConvertUsing<ItemTypeConverter>();
When converting BoxVM will Items, the ItemTypeConverter is not called. Leaving an empty Items collection in Box.
The BoxId is being mapped correctly.
Am I missing a step?
Looks like it does work.
using System.Collections.Generic;
using AutoMapper;
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<BoxVM, Box>();
cfg.CreateMap<ItemVM, Item>().ConvertUsing<ItemTypeConverter>();
});
Mapper.AssertConfigurationIsValid();
var boxVm = new BoxVM()
{
Value1 = "5",
Items = new List<ItemVM> { new ItemVM { Name = "Item1" } }
};
var result = Mapper.Map<BoxVM, Box>(boxVm);
Assert.AreEqual(1, result.Items.Count);
}
}
public class Box
{
public string Value1 { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public string Name { get; set; }
}
public class BoxVM
{
public string Value1 { get; set; }
public List<ItemVM> Items { get; set; }
}
public class ItemVM
{
public string Name { get; set; }
}
public class ItemTypeConverter : ITypeConverter<ItemVM, Item>
{
public Item Convert(ItemVM source, Item destination, ResolutionContext context)
{
return new Item { Name = source.Name };
}
}

SchemaBuilder with complex types

I want to store complex content part record but couldn't create columns with SchemaBuilder in Migrations file.
Here are my classes:
public enum BoxInheritance
{
Empty, Inherit, Enter
}
public class BoxSize
{
public string Width { get; set; }
public string Height { get; set; }
}
public class BoxSpace
{
public string Left { get; set; }
public string Right { get; set; }
public string Top { get; set; }
public string Bottom { get; set; }
}
public class BoxPartRecord : ContentPartRecord
{
public virtual BoxSize Size { get; set; }
public virtual BoxSpace Space { get; set; }
public virtual Dictionary<string, BoxInheritance> Inheritances { get; set; }
public BoxPartRecord()
{
Size = new BoxSize();
Space = new BoxSpace();
Inheritances = new Dictionary<string, BoxInheritance>();
}
}
Is it ok to use a content part record like this?
How to create a table for this content part record?
I think this won't work. My suggestion is to use simple types in the record class and complex types in the content part itself (you can do the mapping there).
public class BoxPartRecord
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
...
}
public class BoxPart : ContentPart
{
public BoxSize Size { get { return new BoxSize {record.Width, record.Height} ...
}

Mapping into differnet types with Automapper

There are two classes in my Model:
public class Operation
{
public string Name { get; set; }
public Calculation Details { get; set; }
}
public class Calculation
{
public long Value { get; set; }
public List<decimal> Points { get; set; }
}
Mapping into this DTO:
public class OperationDto
{
public string Name { get; set; }
public CalculationDto Details { get; set; }
}
public class CalculationDto
{
public long Value { get; set; }
}
public class CalculationDetailedDto: CalculationDto
{
public List<decimal> Points { get; set; }
}
And sometimes the Client can request detailed information about the calculation. For example, depending of the command-line options:
class Program
{
static void Main(string[] args)
{
Mapper.CreateMap<Operation, OperationDto>();
Mapper.CreateMap<Calculation, CalculationDto>();
Mapper.CreateMap<Calculation, CalculationDetailedDto>();
var operation = new Operation
{
Name = "Very complicated opertion.",
Details =
new Calculation
{
Value = 1002,
Points = new List<decimal> {1.2m, 2.4m, 3.7m}
}
};
var operationDto = Mapper.Map<OperationDto>(operation);
Debug.WriteLine("Operation name: '{0}' value: '{1}'", operationDto.Name, operationDto.Details.Value);
if (args.Length > 0)
{
Debug.WriteLine("Details:");
foreach (var point in ((CalculationDetailedDto) operationDto.Details).Points)
{
Debug.WriteLine("{0}", point);
}
}
}
How do i tell Automapper at runtime map the calculation into CalculationDetailedDto?
You need to create another OperationDto that uses the detailed calculationDto, and create a map from Operation to OperationDetailedDto:
public class OperationDetailedDto
{
public string Name { get; set; }
public CalculationDetailedDto Details { get; set; }
}
Mapper.CreateMap<Operation, OperationDetailedDto>();
if (detailed) operationDto = Mapper.Map<Operation, OperationDto>(operation);
else operationDto = Mapper.Map<Operation, OperationDetailedDto(operation);
For those who do not fit solution proposed #fiskeboss can use the following.
You can re-map Calculation to CalculationDetailedDto after checking the command line arguments.
if (args.Length > 0)
{
operationDto.Details = Mapper.Map<CalculationDetailedDto>(operation.Details);
Debug.WriteLine("Details:");
foreach (var point in ((CalculationDetailedDto) operationDto.Details).Points)
{
Debug.WriteLine("{0}", point);
}
}

Automapper map custom collections

Hello. I have a list that looks like this one:
public class PagedList<T> : List<T>
{
public PagedList(IEnumerable<T> collection) : base(collection)
{ }
public int TotalItems { get; set; }
public int CurrentPage { get; set; }
public int PageSize { get; set; }
//some other properties
}
and used in repository for paging
public PagedList<TEntity> GetPaged(int page)
{
var pagedEntities = some_query;
return pagedEntities.AsPagedList(totalResults, page, pageSize);
}
The same PagedList is also used in asp mvc view models for paging.
Is it possible to map this collections using Automapper with all the properties TotalItems/CurrentPage/... ?
PagedList<DbItem> dbItems = _repository.GetPages(page);
var viewItems = new PagedList<SomeItemView>();
Mapper.Map(dbItems , viewItems);
Tahnk You !
This worked for me. Are you looking for something more generic?
public class DbItem
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ViewItem
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PagedList<T>: List<T>
{
public int TotalItems { get; set; }
public int CurrentPage { get; set; }
public int PageSize { get; set; }
}
class Program
{
static void Main(string[] args)
{
MapItems();
}
public static void MapItems()
{
Mapper.CreateMap<DbItem, ViewItem>();
Mapper.CreateMap<PagedList<DbItem>, PagedList<ViewItem>>()
.AfterMap((s, d) => Mapper.Map<List<DbItem>, List<ViewItem>>(s, d));
var dbList = new PagedList<DbItem>
{
new DbItem {Id = 1, Name = "a"},
new DbItem {Id = 2, Name = "b"}
};
dbList.TotalItems = 2;
dbList.CurrentPage = 1;
dbList.PageSize = 10;
var viewList = Mapper.Map<PagedList<DbItem>, PagedList<ViewItem>>(dbList);
Console.WriteLine(viewList.TotalItems);
Console.WriteLine(viewList.CurrentPage);
Console.WriteLine(viewList.PageSize);
Console.WriteLine(viewList[0].Id + " " + viewList[0].Name);
Console.WriteLine(viewList[1].Id + " " + viewList[1].Name);
Console.ReadLine();
}
}
What you need is a custom type converter
public class PagedListConverter<TIn, TOut> : ITypeConverter<IPagedList<TIn>, IPagedList<TOut>>
{
public IPagedList<TOut> Convert(AutoMapper.ResolutionContext context)
{
var source = (IPagedList<TIn>)context.SourceValue;
var mapped = Mapper.Map<IList<TOut>>(source);
return new StaticPagedList<TOut>(mapped, source.GetMetaData());
}
}
Usage:
Mapper.CreateMap<IPagedList<Company>, IPagedList<CompanyViewModel>>().ConvertUsing<PagedListConverter<Company, CompanyViewModel>>();
For those who have faced the similar problem recently, and as an update to NoPyGod's answer, you can achieve the general mapping using ITypeConverter. According to the official documentation:
AutoMapper also supports open generic type converters with any number of generic arguments:
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap(typeof(Source<>), typeof(Destination<>)).ConvertUsing(typeof(Converter<,>)));
The closed type from Source will be the first generic argument, and the closed type of Destination will be the second argument to close Converter<,>.
So the custom type converter would be:
private class Converter<TSource, TDestination>
: ITypeConverter<PagedList<TSource>, PagedList<TDestination>>
{
public PagedList<TDestination> Convert(
PagedList<TSource> source,
PagedList<TDestination> destination,
ResolutionContext context) =>
new PagedList<TDestination>(
context.Mapper.Map<List<TSource>, List<TDestination>>(source));
/* Additional settings comes here. */
}
And then register it:
this.CreateMap(typeof(PagedList<>), typeof(PagedList<>)).ConvertUsing(typeof(Converter<,>));

Resources