The task of the code that is presented for review is to prepare all the data for the export of the selected contract and create a file on this basis.
This is one of the first experiences with a large (for me) program. So I put the tag begginer. In this regard, please use the terms from my code and avoid general description of some concepts without an example (it is difficult for me to understand it).
Context
There are two documents. Contracts
is the main document. ActCompletion
is derived from Contracts
. It contains some already known data from Contracts
and adds its own. Contracts
describes a table in the database. ActCompletion
takes existing Contracts
and runtime receives their individual data from the user. They are not stored anywhere in the database. In both cases, the user selects some Contracts
from table. Then clicks the export button on either Contracts
or ActCompletion
.
Contracts
and ActCompletion
have a template
MS Word with MergeFields
. After receiving all the necessary data, the dictionary creation is called. The dictionary contains a key
as the MergeField name
and a value
as the text
to be inserted instead of MergeField
.
In this way, has two variants:
Event from the "export contract" button, loading the selected contract from the database, request a path to save, create a dictionary, create a file.
Event of the button "export act", loading the selected contract from the database, request additional data for
ActCompletion
, request a path to save, create dictionary, create a file.
Elements
Exporter
is a class that combines the two implementations of theDocumentContract
andDocumentActCompletion
DocumentContract
andDocumentActCompletion
are encapsulate own model:ModelContract
andModelActCompletion
to hide public fields and methods, leaving only one available method.Inherit interfaceIDocument
to force theExport()
method to be implemented.ModelContract
andModelActCompletion
extend the base class implementationExporterModel
. The model contains theExport()
method, which performs the steps described by me after the word "has two variables".ConverterContract
andConverterActCompletion
are encapsulate own implementation ofConverterBase
. Both overrideConvert()
method in its own way. Engaged in the creation of a dictionary.ShaperField
is auxiliary static class. Contains methods for formatting data into string.WordProvider
is сreates a file based on the dictionary, template and output path.
File structure
Example
int id = 22; // some id of selected contract from user
Exporter exporter = new Exporter();
exporter.Contract.Export(id);
// or
// exporter.ActCompletion.Export(id);
UPDATE
I created a database in MS Access
(with the extension .accdb
). The provider to work with this EntityFrameworkCore.Jet.
Code
Contracts and ActCompletion
public partial class Contracts
{
public Contracts(int id = 0)
{
this.Id = id;
ListKindWorks = new HashSet<ListKindWorks>();
ListSubjects = new HashSet<ListSubjects>();
}
public int Id { get; set; }
public string Num { get; set; }
public DateTime DateConclusion { get; set; }
public int Worker { get; set; }
public DateTime DateStartWork { get; set; }
public DateTime DateEndWork { get; set; }
public double Salary { get; set; }
public virtual Workers WorkerNavigation { get; set; }
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
public virtual ICollection<ListSubjects> ListSubjects { get; set; }
}
public class ActCompletion
{
public Contracts Contract { get; set; }
public double Salary { get; set; }
public Dates Dates { get; private set; }
public ActCompletion()
{
Dates = new Dates();
}
}
public class Dates
{
public DateTime DateConclusion { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
}
Exporter
class Exporter
{
public DocumentContract Contract { get; private set; }
public DocumentActCompletion ActCompletion { get; private set; }
public Exporter()
{
Contract = new DocumentContract();
ActCompletion = new DocumentActCompletion();
}
}
IDocument, DocumentContract and DocumentActCompletion
interface IDocument
{
void Export(int id);
}
class DocumentContract : IDocument
{
private readonly ModelContract model;
public DocumentContract()
{
model = new ModelContract();
}
public void Export(int id)
{
model.Export(id);
}
}
class DocumentActCompletion : IDocument
{
private readonly ModelActCompletion model;
public DocumentActCompletion()
{
model = new ModelActCompletion();
}
public void Export(int id)
{
model.Export(id);
}
}
ModelContract
class ModelContract : ExporterModel
{
private readonly ConverterContract converter;
public ModelContract()
{
converter = new ConverterContract();
SelectTemplate();
}
public void Export(int id)
{
LoadContract(id);
if (!RequestDocumentPath(FormatDocumentName()))
{
return;
}
CreateDictionary();
CreateDocument();
}
private string FormatDocumentName()
{
return Properties.Settings.Default.WordTemplate_Contract_DefaultName
.Replace(
Properties.Settings.Default.WordTemplate_PatternName,
ShaperField.ShapeShortName(Contract.WorkerNavigation.FullName));
}
private void SelectTemplate()
{
PathTemplate = Properties.Settings.Default.WordTemplate_Contract_Path;
}
private void CreateDictionary()
{
Dict = converter.Convert(Contract);
}
}
ModelActCompletion
class ModelActCompletion : ExporterModel
{
private readonly ConverterActCompletion converter;
private ActCompletion ActCompletion;
public ModelActCompletion()
{
converter = new ConverterActCompletion();
ActCompletion = new ActCompletion();
SelectTemplate();
}
public void Export(int id)
{
LoadContract(id);
if (!RequestActCompletion()
|| !RequestDocumentPath(FormatDocumentName()))
{
return;
}
CreateDictionary();
CreateDocument();
}
private bool RequestActCompletion()
{
using (ActCompletionForm form = new ActCompletionForm(Contract))
{
if (form.ShowDialog() == DialogResult.OK)
{
ActCompletion = form.Act;
return true;
}
else
{
ActCompletion = new ActCompletion();
return false;
}
}
}
private string FormatDocumentName()
{
return Properties.Settings.Default.WordTempleate_ActCompletion_DefaultName
.Replace(
Properties.Settings.Default.WordTemplate_PatternName,
ShaperField.ShapeShortName(Contract.WorkerNavigation.FullName));
}
private void SelectTemplate()
{
PathTemplate = Properties.Settings.Default.WordTemplate_ActCompletion_Path;
}
private void CreateDictionary()
{
Dict = converter.Convert(ActCompletion);
}
}
ExporterModel
class ExporterModel
{
private Logger logger = NLog.LogManager.GetCurrentClassLogger();
private WordProvider wordProvider;
private string pathDocument;
private string pathTemplate;
public string PathTemplate
{
get
{
return pathTemplate;
}
set
{
pathTemplate = Environment.CurrentDirectory + value;
}
}
public Contracts Contract { get; private set; }
public Dictionary<string, string> Dict { get; set; }
public ExporterModel()
{
Contract = new Contracts();
wordProvider = new WordProvider();
}
public void LoadContract(int id)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (ModelContext model = new ModelContext())
{
Contract = model.Contracts
.Where(c => c.Id == id)
.Include(w => w.WorkerNavigation)
.ThenInclude(w => w.IssuedNavigation)
.Include(w => w.WorkerNavigation)
.ThenInclude(w => w.BankNavigation)
.Include(lkw => lkw.ListKindWorks)
.ThenInclude(lkw => ((ListKindWorks)lkw).IdKindWorkNavigation)
.Include(ls => ls.ListSubjects)
.ThenInclude(ls => ((ListSubjects)ls).IdSubjectNavigation)
.Single();
}
stopwatch.Stop();
logger.Debug("Загрузка договора для экспорта: {0}", stopwatch.Elapsed);
}
public bool RequestDocumentPath(string documentName)
{
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.Filter = "Документы Word (*.docx)|*.docx";
sfd.AddExtension = true;
sfd.RestoreDirectory = true;
sfd.FileName = documentName;
if (sfd.ShowDialog() == DialogResult.OK)
{
pathDocument = sfd.FileName;
return true;
}
else
{
pathDocument = string.Empty;
return false;
}
}
}
public void CreateDocument()
{
wordProvider.CreateDocument(Dict, PathTemplate, pathDocument);
}
}
ConverterContract
class ConverterContract
{
private readonly Converter converter;
public ConverterContract()
{
converter = new Converter();
}
public Dictionary<string, string> Convert(Contracts contract)
{
return converter.Convert(contract);
}
private class Converter : ConverterBase
{
public override Dictionary<string, string> Convert(object obj)
{
Contracts c = obj as Contracts;
InitializeDict(c, c.Salary);
AddKindWork(c.ListKindWorks);
AddSubject(c.ListSubjects);
AddDates(c.DateStartWork, c.DateEndWork);
AddPassport(c.WorkerNavigation);
AddBank(c.WorkerNavigation);
return Dict;
}
private void AddKindWork(ICollection<ListKindWorks> list)
{
Dict.Add(Fields["KIND_WORK"], ShaperField.ShapeKindWork(list));
}
private void AddSubject(ICollection<ListSubjects> list)
{
Dict.Add(Fields["SUBJECT"], ShaperField.ShapeSubject(list));
}
private void AddDates(DateTime start, DateTime end)
{
Dict.Add(Fields["DATE_START_CONTRACT"], ShaperField.ShapeDate(start));
Dict.Add(Fields["DATE_END_CONTRACT"], ShaperField.ShapeDate(end));
}
private void AddPassport(Workers w)
{
Dict.Add(Fields["ADDRESS"], ShaperField.ShapeAddress(w.Address));
Dict.Add(Fields["PASSPORT"], ShaperField.ShapePassport(w));
Dict.Add(Fields["PASSPORT_NUMBER"], ShaperField.ShapePassportNumber(w.PassportNumber));
}
private void AddBank(Workers w)
{
Dict.Add(Fields["BANK"], ShaperField.ShapeBank(w));
}
}
}
ConverterActCompletion
class ConverterActCompletion
{
private readonly Converter converter;
public ConverterActCompletion()
{
converter = new Converter();
}
public Dictionary<string, string> Convert(ActCompletion act)
{
return converter.Convert(act);
}
private class Converter : ConverterBase
{
public override Dictionary<string, string> Convert(object obj)
{
ActCompletion act = obj as ActCompletion;
InitializeDict(act.Contract, act.Salary);
AddDates(act.Dates);
return Dict;
}
private void AddDates(Dates dates)
{
Dict.Add(Fields["DATE_FILL_ACT_COMPLETION"], ShaperField.ShapeDate(dates.DateConclusion));
Dict.Add(Fields["DATE_START_ACT_COMPLETION"], ShaperField.ShapeDate(dates.DateStart));
Dict.Add(Fields["DATE_END_ACT_COMPLETION"], ShaperField.ShapeDate(dates.DateEnd));
}
}
}
ConverterBase
using Newtonsoft.Json;
abstract class ConverterBase
{
public Dictionary<string, string> Fields { get; private set; }
public Dictionary<string, string> Dict { get; private set; }
public ConverterBase()
{
Dict = new Dictionary<string, string>();
Fields = JsonConvert.DeserializeObject<Dictionary<string, string>>
(Properties.Settings.Default.MergeFieldDictionaryAsString);
}
public void InitializeDict(Contracts c, double salary)
{
Dict.Clear();
AddGeneralField(c);
AddSalary(salary);
}
public abstract Dictionary<string, string> Convert(object obj);
private void AddGeneralField(Contracts c)
{
Dict.Add(Fields["NUM_CONTRACT"], ShaperField.ShapeNum(c.Num));
Dict.Add(Fields["DATE_FILL_CONTRACT"], ShaperField.ShapeDate(c.DateConclusion));
Dict.Add(Fields["FULL_NAME"], ShaperField.ShapeFullName(c.WorkerNavigation.FullName));
Dict.Add(Fields["SHORT_NAME"], ShaperField.ShapeShortName(c.WorkerNavigation.FullName));
}
private void AddSalary(double salary)
{
ConvertSalary cs = new ConvertSalary();
cs.setSalaryWithTax(salary);
Dict.Add(Fields["SALARY_GROSS"], cs.GetSalaryWithTax());
Dict.Add(Fields["INCOME_TAX_PROCENT"], Properties.Settings.Default.ConvertSalary_IncomeTaxProcent.ToString());
Dict.Add(Fields["INCOME_TAX_SUM"], cs.GetIncomeTaxValue());
Dict.Add(Fields["INSURANCE_TAX_PROCENT"], Properties.Settings.Default.ConvertSalary_InsuranceTaxProcent.ToString());
Dict.Add(Fields["INSURANCE_TAX_SUM"], cs.GetInsuranceTaxValue());
}
}
ShaperField
static class ShaperField
{
public static string ShapeNum(string value)
{
return value;
}
public static string ShapeDate(DateTime value)
{
return value.ToLongDateString();
}
public static string ShapeFullName(string value)
{
return value;
}
// TODO: зачем в KindWork и Subject - ToArray()
public static string ShapeKindWork(ICollection<ListKindWorks> list)
{
return string.Join(", ", list.Select(x => "«" + x.IdKindWorkNavigation.Title + "»").ToArray());
}
public static string ShapeSubject(ICollection<ListSubjects> list)
{
return string.Join(", ", list.Select(x => "«" + x.IdSubjectNavigation.Title + "»").ToArray());
}
public static string ShapeAddress(string value)
{
return value;
}
public static string ShapePassport(Workers w)
{
return string.Format("{0} выдан {1} {2} г.", w.PassportSeries, w.IssuedNavigation.Title, w.DateIssued.ToString("dd.MM.yyyy"));
}
public static string ShapePassportNumber(string value)
{
return value;
}
public static string ShapeBank(Workers w)
{
return string.Format("{0}\nв {1}", w.BankAccount, w.BankNavigation.Title);
}
public static string ShapeShortName(string fullName)
{
string[] name = fullName.Split(' ');
if (TryFormatShortName(name))
{
return FormatShortName(name);
}
else
{
ShowMessage.Error(string.Format("Во время получения инициалов от полного ФИО произошла ошибка. " +
"Возможно ФИО не содержит трех слов.\nВ случае продолжения, сокращенное имя получит значение \"{0}\"",
fullName));
return fullName;
}
}
private static bool TryFormatShortName(string[] name)
{
try
{
string shortName = FormatShortName(name);
}
catch (Exception ex)
{
return false;
}
return true;
}
private static string FormatShortName(string[] name, string pattern = "{0}.{1}. {2}")
{
return string.Format(pattern, name[1][0], name[2][0], name[0]);
}
}
WordProvider
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
class WordProvider
{
private WordprocessingDocument document;
private Dictionary<string, string> _dict;
private string _templatePath;
private string _outputPath;
private string _tempPath;
public void CreateDocument(Dictionary<string, string> dict, string templatePath, string outputPath)
{
_dict = dict;
_templatePath = templatePath;
_outputPath = outputPath;
_tempPath = GetTempPath();
Create();
}
private string GetTempPath()
{
return Path.GetTempPath() + Path.GetFileName(_outputPath);
}
private void Create()
{
File.Copy(_templatePath, _tempPath, true);
document = WordprocessingDocument.Open(_tempPath, true);
document.ChangeDocumentType(WordprocessingDocumentType.Document);
FillMergeFields();
document.MainDocumentPart.Document.Save();
document.Close();
File.Copy(_tempPath, _outputPath, true);
File.Delete(_tempPath);
}
private void FillMergeFields()
{
string fieldName;
foreach (FieldCode field in document.MainDocumentPart.RootElement.Descendants<FieldCode>())
{
fieldName = GetFieldName(field);
// TODO: пропуски в лог
if (_dict.ContainsKey(fieldName))
{
ReplaceMergeFieldWithText(field, _dict[fieldName]);
}
}
}
private string GetFieldName(FieldCode field)
{
return field.Text.Substring(11, field.Text.IndexOf("\\") - 11).Trim();
}
private void ReplaceMergeFieldWithText(FieldCode field, string replacementText)
{
// Разбор алгоритма кода
// https://stackoverflow.com/a/57180496/11563179
if (field == null || replacementText == string.Empty)
{
return;
}
Run rFldParent = (Run)field.Parent;
List<Run> runs = new List<Run>();
runs.Add(rFldParent.PreviousSibling<Run>()); // begin
runs.Add(rFldParent.NextSibling<Run>()); // separate
runs.Add(runs.Last().NextSibling<Run>()); // text
runs.Add(runs.Last().NextSibling<Run>()); // end
runs.ForEach(r => r.Remove());
field.Remove(); // instrText
rFldParent.Append(new Text(replacementText));
}
}
Contracts
ispartial
but the other part is missing. \$\endgroup\$Contracts
generated by the database provider. As well as the rest of the code concerning work with database. \$\endgroup\$