using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Text.Json; using System.Text.Json.Serialization; using System.Drawing.Text; using System.IO; using static ddq.Utility; using static ddq.DataAccess; using System.Diagnostics.Eventing.Reader; using System.Diagnostics.Metrics; using Org.BouncyCastle.Bcpg.OpenPgp; namespace ddq { public partial class FundQ : Form { private string fundId; private string companyId; private int userId; private DataTable fundInfoTable; public FundQ(string fundId, string companyId, int userId) { InitializeComponent(); this.fundId = fundId; this.companyId = companyId; this.userId = userId; InitializeData(); } private void InitializeData() { DataTable dt = DataAccess.Get_fund_info(fundId, null); if (dt == null || dt.Rows.Count <= 0) return; this.lblFundName.Text = dt.Rows[0].Field("fund_short_name"); //this.lblMainCode.Text = "Code: " + dt.Rows[0].Field("register_number"); this.lblMainCode.Text = "Code: " + fundId; this.lblCategory.Text = "Category: " + dt.Rows[0].Field("strategy"); this.lblInceptionDate.Text = "Launched: " + dt.Rows[0].Field("inception_date")?.ToString("yyyy-MM-dd"); this.lblDomicile.Text = "Domicile: "; fundInfoTable = DataAccess.Get_dd_fund_info(fundId, null, 1); if (fundInfoTable != null && fundInfoTable.Rows.Count > 0) { string jsonString = fundInfoTable.Rows[0].Field("info").Trim(); JsonDocument document = JsonDocument.Parse(jsonString); JsonElement root = document.RootElement; if (root.ValueKind != JsonValueKind.Undefined) { // // General Info // LoadTextDataFromJson(this.txtInvestmentObjective, root, "investmentObjective", this.tspInvestmentObjective); LoadTextDataFromJson(this.txtBenchmark, root, "benchmark", this.tspBenchmark); LoadTextDataFromJson(this.txtInvestmentPhilosophy, root, "investmentPhilosophy", this.tspInvestmentPhilosophy); LoadCurrentFees(root); LoadTextDataFromJson(this.txtPolicyOfClosingFund, root, "policyOfClosingFund", this.tspPolicyOfClosingFund); LoadPortfolioManager(root); LoadTER(root); // // Process // LoadDerivates(root); LoadPriceTarget(root); } } } private void LoadPortfolioManager(JsonElement jsonElement) { bool hasData = jsonElement.TryGetProperty("portfolioManagers", out JsonElement element); string strUpdateTime = ""; DataTable dt = new DataTable(); if (hasData == true && element.ValueKind == JsonValueKind.Object) { hasData &= element.TryGetProperty("v", out JsonElement elm); Utility.DDData dddata = Utility.Json2Table2(element); if (dddata != null) { dt = (DataTable)dddata.Value; strUpdateTime = dddata.UpdateTime; } } else { // 没有基金经理数据时,初始化表 dt.Columns.Add("personnel_id", typeof(string)); dt.Columns.Add("name", typeof(string)); dt.Columns.Add("startDate", typeof(DateTime)); dt.Columns.Add("endDate", typeof(DateTime)); } InitPortfolioManagerGrid(dt, strUpdateTime); } private void InitPortfolioManagerGrid(DataTable dt, string updateTime) { if (dt == null && updateTime == null) return; this.grdPortfolioManager.DataSource = dt; this.grdPortfolioManager.Columns["personnel_id"].Visible = false; this.grdPortfolioManager.Columns["name"].HeaderText = "Manager Name"; this.grdPortfolioManager.Columns["name"].DisplayIndex = 0; this.grdPortfolioManager.Columns["startDate"].HeaderText = "Start Date"; this.grdPortfolioManager.Columns["startDate"].DefaultCellStyle.Format = "yyyy-MM-dd"; this.grdPortfolioManager.Columns["startDate"].DisplayIndex = 1; this.grdPortfolioManager.Columns["endDate"].HeaderText = "End Date"; this.grdPortfolioManager.Columns["endDate"].DefaultCellStyle.Format = "yyyy-MM-dd"; this.grdPortfolioManager.Columns["endDate"].DisplayIndex = 2; this.tspPortfolioManager.Text = updateTime; this.tspPortfolioManager.ForeColor = IsChangedRecently(this.tspPortfolioManager.Text) ? COLOR_MODIFIED : COLOR_NORMAL; } private void LoadCurrentFees(JsonElement jsonElement) { if (jsonElement.ValueKind != JsonValueKind.Object) return; bool hasData = jsonElement.TryGetProperty("currentFees", out JsonElement element); if (hasData) { if (element.ValueKind == JsonValueKind.Object) { hasData = element.TryGetProperty("v", out JsonElement elm); if (hasData) { this.txtManagementFee.Text = Json2Text(elm, "managementFee"); this.txtSubscriptionFee.Text = Json2Text(elm, "subscriptionFee"); this.txtRedemptionFee.Text = Json2Text(elm, "redemptionFee"); this.txtAdministrationFee.Text = Json2Text(elm, "administrationFee"); this.txtSwitchingFee.Text = Json2Text(elm, "switchingFee"); this.txtTrusteeFee.Text = Json2Text(elm, "trusteeFee"); this.txtPerformanceFee.Text = Json2Text(elm, "performanceFee"); this.tspCurrentFee.Text = element.GetProperty("t").ToString(); this.tspCurrentFee.ForeColor = IsChangedRecently(this.tspCurrentFee.Text) ? COLOR_MODIFIED : COLOR_NORMAL; } } } } private void LoadTER(JsonElement jsonElement) { bool hasData = jsonElement.TryGetProperty("totalExpenseRatio", out JsonElement element); string strUpdateTime = ""; DataTable dt = new DataTable(); if (hasData == true && element.ValueKind == JsonValueKind.Object) { hasData &= element.TryGetProperty("v", out JsonElement elm); Utility.DDData dddata = Utility.Json2Table2(element); if (dddata != null) { dt = (DataTable)dddata.Value; strUpdateTime = dddata.UpdateTime; } } else { dt.Columns.Add("year", typeof(string)); dt.Columns.Add("ter", typeof(string)); int year = DateTime.Today.Year - 1; for (int i = 0; i < 5; i++) { DataRow row = dt.NewRow(); row["year"] = year - i; dt.Rows.Add(row); } } InitTERGrid(dt, strUpdateTime); } private void InitTERGrid(DataTable dt, string updateTime) { if (dt == null || updateTime == null) return; this.grdTER.DataSource = dt; this.grdTER.Columns["year"].HeaderText = "Year"; this.grdTER.Columns["year"].ReadOnly = true; this.grdTER.Columns["year"].DisplayIndex = 0; this.grdTER.Columns["ter"].HeaderText = "Total Expense Ratio %"; this.grdTER.Columns["ter"].ValueType = typeof(decimal); this.grdTER.Columns["ter"].DefaultCellStyle.Format = "N3"; this.grdTER.Columns["ter"].DisplayIndex = 1; this.tspTER.Text = updateTime; this.tspTER.ForeColor = Utility.IsChangedRecently(updateTime) ? COLOR_MODIFIED : COLOR_NORMAL; } private void LoadDerivates(JsonElement jsonElement) { if (jsonElement.ValueKind != JsonValueKind.Object) return; bool hasData = jsonElement.TryGetProperty("derivatives", out JsonElement element); if (hasData) { if (element.ValueKind == JsonValueKind.Object) { hasData = element.TryGetProperty("v", out JsonElement elm); if (hasData) { this.chkAllowDerivatives.Checked = Json2Boolean(elm, "allowed"); this.chkUseDerivatives.Checked = Json2Boolean(elm, "currentUse"); this.chkDerivativesForEfficent.Checked = Json2Boolean(elm, "effectiveManagement"); this.chkDerivatesForHedging.Checked = Json2Boolean(elm, "hedging"); this.chkDerivativesForMarket.Checked = Json2Boolean(elm, "marketAccess"); this.chkDerivatesForLeverage.Checked = Json2Boolean(elm, "leverage"); this.tspDerivatives.Text = element.GetProperty("t").ToString(); this.tspDerivatives.ForeColor = IsChangedRecently(this.tspDerivatives.Text) ? COLOR_MODIFIED : COLOR_NORMAL; } } } } private void LoadPriceTarget(JsonElement jsonElement) { if (jsonElement.ValueKind != JsonValueKind.Object) return; bool hasData = jsonElement.TryGetProperty("priceTarget", out JsonElement element); if (hasData) { if (element.ValueKind == JsonValueKind.Object) { hasData = element.TryGetProperty("v", out JsonElement elm); if (hasData) { this.chkPriceTarget.Checked = Json2Boolean(elm, "assigned"); this.txtOverrunPriceTarget.Text = Json2Text(elm, "overrun"); this.tspPriceTarget.Text = element.GetProperty("t").ToString(); this.tspPriceTarget.ForeColor = IsChangedRecently(this.tspPriceTarget.Text) ? COLOR_MODIFIED : COLOR_NORMAL; } } } } private void FundQ_Load(object sender, EventArgs e) { } private void btnSaveGeneralInfo_Click(object sender, EventArgs e) { try { // 数据对象 var textData = new { // -- General Info -- investmentObjective = AddInfo(this.txtInvestmentObjective.Text, userId), benchmark = AddInfo(this.txtBenchmark.Text, userId), investmentPhilosophy = AddInfo(this.txtInvestmentPhilosophy.Text, userId), portfolioManagers = AddInfo(Utility.DataTable2List((DataTable)this.grdPortfolioManager.DataSource), userId), totalExpenseRatio = AddInfo(Utility.DataTable2List((DataTable)this.grdTER.DataSource), userId), currentFees = AddInfo(new { managementFee = this.txtManagementFee.Text, subscriptionFee = this.txtSubscriptionFee.Text, redemptionFee = this.txtRedemptionFee.Text, administrationFee = this.txtAdministrationFee.Text, switchingFee = this.txtSwitchingFee.Text, trusteeFee = this.txtTrusteeFee.Text, performanceFee = this.txtPerformanceFee.Text }, userId), policyOfClosingFund = AddInfo(this.txtPolicyOfClosingFund.Text, userId), // -- Process -- derivatives = AddInfo( new { allowed = this.chkAllowDerivatives.Checked, currentUse = this.chkUseDerivatives.Checked, effectiveManagement = this.chkDerivativesForEfficent.Checked, hedging = this.chkDerivatesForHedging.Checked, marketAccess = this.chkDerivativesForMarket.Checked, leverage = this.chkDerivatesForLeverage.Checked }, userId), priceTarget = AddInfo( new { assigned = this.chkPriceTarget.Checked, overrun = this.txtOverrunPriceTarget.Text }, userId) }; // 序列化选项 var options = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; string jsonString = JsonSerializer.Serialize(textData, options); //MessageBox.Show($"Save JSON: \n{jsonString}", "Json Result"); int ret = DataAccess.Set_dd_fund_info(this.fundId, DateTime.Today, jsonString, 1, 1, userId); if (ret == 1) { this.Close(); } } catch (Exception ex) { MessageBox.Show($"Error: {ex.Message}", "Error"); } } private void grdTER_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { string errMsg = "Please enter a number between 0 and 100, with up to 3 decimal digits"; if (grdTER.Columns[e.ColumnIndex].Name == "ter" && e.RowIndex >= 0) { // 先清除可能存在的旧错误提示 grdTER.Rows[e.RowIndex].ErrorText = ""; if (e.FormattedValue.ToString().Trim() == "") return; // 尝试将输入的值转换为小数 if (!decimal.TryParse(e.FormattedValue.ToString(), out decimal newValue)) { // 如果转换失败,说明输入的不是有效数字 e.Cancel = true; grdTER.Rows[e.RowIndex].ErrorText = errMsg; return; } // 验证数值范围(例如限制在0到100之间) if (newValue < 0 || newValue > 100) { e.Cancel = true; grdTER.Rows[e.RowIndex].ErrorText = errMsg; return; } // (可选) 自定义验证小数位数 string[] parts = e.FormattedValue.ToString().Split('.'); if (parts.Length > 1 && parts[1].Length > 3) // 检查小数点后的位数 { e.Cancel = true; grdTER.Rows[e.RowIndex].ErrorText = errMsg; } } } private void btnSaveFundProcess_Click(object sender, EventArgs e) { } private void chkAllowDerivatives_CheckedChanged(object sender, EventArgs e) { bool isChecked = chkAllowDerivatives.Checked; this.chkUseDerivatives.Enabled = isChecked; if (isChecked == false) { this.chkUseDerivatives.Checked = false; } } private void chkUseDerivatives_CheckedChanged(object sender, EventArgs e) { bool isChecked = chkUseDerivatives.Checked; this.chkDerivativesForEfficent.Enabled = isChecked; this.chkDerivatesForHedging.Enabled = isChecked; this.chkDerivativesForMarket.Enabled = isChecked; this.chkDerivatesForLeverage.Enabled = isChecked; if (isChecked == false) { this.chkDerivativesForEfficent.Checked = false; this.chkDerivatesForHedging.Checked = false; this.chkDerivativesForMarket.Checked = false; this.chkDerivatesForLeverage.Checked = false; } } private void chkPriceTarget_CheckedChanged(object sender, EventArgs e) { bool isChecked = chkPriceTarget.Checked; if (!isChecked) { this.txtOverrunPriceTarget.Text = ""; } } private void btnUploadProcessDiagram_Click(object sender, EventArgs e) { this.ofdProcessDiagram.Title = "Upload Diagram File -- FAKE"; if (this.ofdProcessDiagram.ShowDialog() == DialogResult.OK) { // TODO: upload files to server. let's fake it for now string filePath = ofdProcessDiagram.FileName; string fileName = Path.GetFileName(filePath); try { } catch (Exception ex) { MessageBox.Show("File was not able to be uploaded:" + ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void btnAddManager_Click(object sender, EventArgs e) { using (PersonSelectionDialog dialog = new PersonSelectionDialog(companyId)) { dialog.StartPosition = FormStartPosition.CenterParent; DialogResult result = dialog.ShowDialog(this); if (result == DialogResult.OK && dialog.PersonnelId != "") { DataTable dt = (DataTable)this.grdPortfolioManager.DataSource; DataRow row = dt.NewRow(); row["personnel_id"] = dialog.PersonnelId; row["name"] = dialog.PersonnelName; row["startDate"] = dialog.StartDate.Date; dt.Rows.Add(row); } } } private void grdPortfolioManager_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { int rowIndex = e.RowIndex; int columnIndex = e.ColumnIndex; if (rowIndex < 0 || columnIndex < 0) { return; } // 先清除可能存在的旧错误提示 grdPortfolioManager.Rows[rowIndex].ErrorText = ""; // 如果转换失败,说明输入的不是有效日期 bool isPass = DateTime.TryParse(grdPortfolioManager.Rows[rowIndex].Cells["startDate"].Value.ToString(), out DateTime newDate); if (!isPass || newDate < new DateTime(1900, 1, 1) || newDate > DateTime.Today) { e.Cancel = true; grdPortfolioManager.Rows[columnIndex].ErrorText = "Please enter a proper start date"; return; } if (grdPortfolioManager.Rows[rowIndex].Cells["endDate"].Value.ToString() == "") return; isPass = DateTime.TryParse(grdPortfolioManager.Rows[rowIndex].Cells["endDate"].Value.ToString(), out newDate); if (!isPass || newDate < new DateTime(1900, 1, 1) || newDate > DateTime.Today) { e.Cancel = true; grdPortfolioManager.Rows[columnIndex].ErrorText = "Please enter a proper end date"; ; return; } } private void grdPortfolioManager_Validating(object sender, CancelEventArgs e) { } } }