Another fixation.

This commit is contained in:
Programmist73 2023-05-10 14:00:34 +04:00
parent b475dac5d5
commit 3ad12b46fd
10 changed files with 346 additions and 29 deletions

View File

@ -44,8 +44,9 @@ namespace TransportCompany
{ {
dataGridView.DataSource = list; dataGridView.DataSource = list;
dataGridView.Columns["Id"].Visible = false; dataGridView.Columns["Id"].Visible = false;
dataGridView.Columns[4].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; dataGridView.Columns["MongoId"].Visible = false;
dataGridView.Columns[5].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; dataGridView.Columns[5].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView.Columns[6].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
} }
_logger.LogInformation("Загрузка клиентов"); _logger.LogInformation("Загрузка клиентов");
@ -81,6 +82,8 @@ namespace TransportCompany
{ {
form.Id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value); form.Id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value);
form.MongoId = Convert.ToString(dataGridView.SelectedRows[0].Cells["MongoId"].Value);
if (form.ShowDialog() == DialogResult.OK) if (form.ShowDialog() == DialogResult.OK)
{ {
LoadData(); LoadData();
@ -98,13 +101,16 @@ namespace TransportCompany
{ {
int id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value); int id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value);
string mongoId = Convert.ToString(dataGridView.SelectedRows[0].Cells["MongoId"].Value);
_logger.LogInformation("Удаление клиента"); _logger.LogInformation("Удаление клиента");
try try
{ {
if (!_logicC.Delete(new ClientBindingModel if (!_logicC.Delete(new ClientBindingModel
{ {
Id = id Id = id,
MongoId = mongoId
})) }))
{ {
throw new Exception("Ошибка при удалении. Дополнительная информация в логах."); throw new Exception("Ошибка при удалении. Дополнительная информация в логах.");

View File

@ -22,8 +22,12 @@ namespace TransportCompany
private int? _id; private int? _id;
private string? _mongoId;
public int Id { set { _id = value; } } public int Id { set { _id = value; } }
public string MongoId { set { _mongoId = value; } }
public FormCreateClient(ILogger<FormCreateClient> logger, IClientLogic logicC) public FormCreateClient(ILogger<FormCreateClient> logger, IClientLogic logicC)
{ {
InitializeComponent(); InitializeComponent();
@ -36,7 +40,31 @@ namespace TransportCompany
private void FormCreateClient_Load(object sender, EventArgs e) private void FormCreateClient_Load(object sender, EventArgs e)
{ {
//проверка на заполнение поля id. Если оно заполнено, то пробуем получить запись и выести её на экран //проверка на заполнение поля id. Если оно заполнено, то пробуем получить запись и выести её на экран
if (_id.HasValue) if (!string.IsNullOrEmpty(_mongoId))
{
try
{
_logger.LogInformation("Получение клиента");
var view = _logicC.ReadElement(new ClientSearchModel { MongoId = _mongoId });
if (view != null)
{
textBoxName.Text = view.Name;
textBoxSurname.Text = view.Surname;
textBoxPatronymic.Text = view.Patronymic;
textBoxTelephone.Text = view.Telephone;
textBoxEmail.Text = view.Email;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения компонента");
MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else if (_id.HasValue)
{ {
try try
{ {
@ -101,6 +129,7 @@ namespace TransportCompany
var model = new ClientBindingModel var model = new ClientBindingModel
{ {
Id = 0, Id = 0,
MongoId = _mongoId,
Name = textBoxName.Text, Name = textBoxName.Text,
Surname = textBoxSurname.Text, Surname = textBoxSurname.Text,
Patronymic = textBoxPatronymic.Text, Patronymic = textBoxPatronymic.Text,

View File

@ -22,8 +22,12 @@ namespace TransportCompany
private int? _id; private int? _id;
private string? _mongoId;
public int Id { set { _id = value; } } public int Id { set { _id = value; } }
public string MongoId { set { _mongoId = value; } }
public FormCreateTransport(ILogger<FormCreateTransport> logger, ITransportLogic logicT) public FormCreateTransport(ILogger<FormCreateTransport> logger, ITransportLogic logicT)
{ {
InitializeComponent(); InitializeComponent();
@ -35,7 +39,27 @@ namespace TransportCompany
private void FormCreateTransport_Load(object sender, EventArgs e) private void FormCreateTransport_Load(object sender, EventArgs e)
{ {
//проверка на заполнение поля id. Если оно заполнено, то пробуем получить запись и выести её на экран //проверка на заполнение поля id. Если оно заполнено, то пробуем получить запись и выести её на экран
if (_id.HasValue) if (!string.IsNullOrEmpty(_mongoId))
{
try
{
_logger.LogInformation("Получение транспорта");
var view = _logicT.ReadElement(new TransportSearchModel { MongoId = _mongoId });
if (view != null)
{
textBoxTransport.Text = view.Tranport;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка получения транспорта");
MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else if (_id.HasValue)
{ {
try try
{ {
@ -65,14 +89,22 @@ namespace TransportCompany
return; return;
} }
/*if (string.IsNullOrEmpty(textBoxTypeTransportation.Text))
{
MessageBox.Show("Введите тип транспортировки", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}*/
_logger.LogInformation("Добавление транспорта"); _logger.LogInformation("Добавление транспорта");
try try
{ {
var model = new TransportBindingModel var model = new TransportBindingModel
{ {
Id = 0, Id = _id ?? 0,
MongoId = _mongoId,
Tranport = textBoxTransport.Text, Tranport = textBoxTransport.Text,
TransportationType = textBoxTypeTransportation.Text
}; };
var operationResult = _id.HasValue ? _logicT.Update(model) : _logicT.Create(model); var operationResult = _id.HasValue ? _logicT.Update(model) : _logicT.Create(model);

View File

@ -0,0 +1,39 @@
namespace TransportCompany
{
partial class FormTransferData
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "FormTransferData";
}
#endregion
}
}

View File

@ -0,0 +1,20 @@
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;
namespace TransportCompany
{
public partial class FormTransferData : Form
{
public FormTransferData()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -42,7 +42,9 @@ namespace TransportCompany
if (list != null) if (list != null)
{ {
dataGridView.DataSource = list; dataGridView.DataSource = list;
dataGridView.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; dataGridView.Columns["MongoId"].Visible = false;
dataGridView.Columns["TransportationType"].Visible = false;
dataGridView.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
} }
_logger.LogInformation("Загрузка транспортных средств"); _logger.LogInformation("Загрузка транспортных средств");
@ -83,6 +85,8 @@ namespace TransportCompany
{ {
form.Id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value); form.Id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value);
form.MongoId = Convert.ToString(dataGridView.SelectedRows[0].Cells["MongoId"].Value);
if (form.ShowDialog() == DialogResult.OK) if (form.ShowDialog() == DialogResult.OK)
{ {
LoadData(); LoadData();
@ -100,13 +104,16 @@ namespace TransportCompany
{ {
int id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value); int id = Convert.ToInt32(dataGridView.SelectedRows[0].Cells["Id"].Value);
string mongoId = Convert.ToString(dataGridView.SelectedRows[0].Cells["MongoId"].Value);
_logger.LogInformation("Удаление транспорта"); _logger.LogInformation("Удаление транспорта");
try try
{ {
if (!_logic.Delete(new TransportBindingModel if (!_logic.Delete(new TransportBindingModel
{ {
Id = id Id = id,
MongoId = mongoId
})) }))
{ {
throw new Exception("Ошибка при удалении. Дополнительная информация в логах."); throw new Exception("Ошибка при удалении. Дополнительная информация в логах.");

View File

@ -46,7 +46,10 @@
label1 = new Label(); label1 = new Label();
checkBoxSorted = new CheckBox(); checkBoxSorted = new CheckBox();
checkBoxForFilterMode = new CheckBox(); checkBoxForFilterMode = new CheckBox();
createJsonFilesToolStripMenuItem = new ToolStripMenuItem(); chooiceDBToolStripMenuItem = new ToolStripMenuItem();
transferDataToolStripMenuItem = new ToolStripMenuItem();
startPostgresqlToolStripMenuItem = new ToolStripMenuItem();
startMongoDBToolStripMenuItem = new ToolStripMenuItem();
((System.ComponentModel.ISupportInitialize)dataGridView).BeginInit(); ((System.ComponentModel.ISupportInitialize)dataGridView).BeginInit();
menuStrip.SuspendLayout(); menuStrip.SuspendLayout();
SuspendLayout(); SuspendLayout();
@ -74,7 +77,7 @@
// menuStrip // menuStrip
// //
menuStrip.ImageScalingSize = new Size(20, 20); menuStrip.ImageScalingSize = new Size(20, 20);
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem, rndGenerationToolStripMenuItem, testTimeGetDataToolStripMenuItem, testComplexQueriesToolStripMenuItem, createJsonFilesToolStripMenuItem }); menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem, rndGenerationToolStripMenuItem, testTimeGetDataToolStripMenuItem, testComplexQueriesToolStripMenuItem, chooiceDBToolStripMenuItem });
menuStrip.Location = new Point(0, 0); menuStrip.Location = new Point(0, 0);
menuStrip.Name = "menuStrip"; menuStrip.Name = "menuStrip";
menuStrip.Padding = new Padding(6, 3, 0, 3); menuStrip.Padding = new Padding(6, 3, 0, 3);
@ -201,12 +204,33 @@
checkBoxForFilterMode.Text = "Включить режим фильтра"; checkBoxForFilterMode.Text = "Включить режим фильтра";
checkBoxForFilterMode.UseVisualStyleBackColor = true; checkBoxForFilterMode.UseVisualStyleBackColor = true;
// //
// createJsonFilesToolStripMenuItem // chooiceDBToolStripMenuItem
// //
createJsonFilesToolStripMenuItem.Name = "createJsonFilesToolStripMenuItem"; chooiceDBToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { transferDataToolStripMenuItem, startPostgresqlToolStripMenuItem, startMongoDBToolStripMenuItem });
createJsonFilesToolStripMenuItem.Size = new Size(209, 24); chooiceDBToolStripMenuItem.Name = "chooiceDBToolStripMenuItem";
createJsonFilesToolStripMenuItem.Text = "Сформировать json файлы"; chooiceDBToolStripMenuItem.Size = new Size(93, 24);
createJsonFilesToolStripMenuItem.Click += CreateJsonFilesToolStripMenuItem_Click; chooiceDBToolStripMenuItem.Text = "Выбор БД";
//
// transferDataToolStripMenuItem
//
transferDataToolStripMenuItem.Name = "transferDataToolStripMenuItem";
transferDataToolStripMenuItem.Size = new Size(224, 26);
transferDataToolStripMenuItem.Text = "Передача данных";
transferDataToolStripMenuItem.Click += TransferDataToolStripMenuItem_Click;
//
// startPostgresqlToolStripMenuItem
//
startPostgresqlToolStripMenuItem.Name = "startPostgresqlToolStripMenuItem";
startPostgresqlToolStripMenuItem.Size = new Size(224, 26);
startPostgresqlToolStripMenuItem.Text = "Запуск Postgresql";
startPostgresqlToolStripMenuItem.Click += StartPostgresqlToolStripMenuItem_Click;
//
// startMongoDBToolStripMenuItem
//
startMongoDBToolStripMenuItem.Name = "startMongoDBToolStripMenuItem";
startMongoDBToolStripMenuItem.Size = new Size(224, 26);
startMongoDBToolStripMenuItem.Text = "Запуск MongoDB";
startMongoDBToolStripMenuItem.Click += StartMongoDBToolStripMenuItem_Click;
// //
// FormTrucking // FormTrucking
// //
@ -265,6 +289,9 @@
private CheckBox checkBoxForFilterMode; private CheckBox checkBoxForFilterMode;
private ToolStripMenuItem testTimeGetDataToolStripMenuItem; private ToolStripMenuItem testTimeGetDataToolStripMenuItem;
private ToolStripMenuItem testComplexQueriesToolStripMenuItem; private ToolStripMenuItem testComplexQueriesToolStripMenuItem;
private ToolStripMenuItem createJsonFilesToolStripMenuItem; private ToolStripMenuItem chooiceDBToolStripMenuItem;
private ToolStripMenuItem transferDataToolStripMenuItem;
private ToolStripMenuItem startPostgresqlToolStripMenuItem;
private ToolStripMenuItem startMongoDBToolStripMenuItem;
} }
} }

View File

@ -4,6 +4,7 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Windows.Forms; using System.Windows.Forms;
using TransportCompanyBusinessLogic.BusinessLogic;
using TransportCompanyContracts.BusinessLogicsContracts; using TransportCompanyContracts.BusinessLogicsContracts;
using TransportCompanyContracts.SearchModels; using TransportCompanyContracts.SearchModels;
using static System.Windows.Forms.VisualStyles.VisualStyleElement; using static System.Windows.Forms.VisualStyles.VisualStyleElement;
@ -14,15 +15,11 @@ namespace TransportCompany
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITruckingLogic _truckingLogic; private bool _mongo;
private readonly IClientLogic _clientLogic; private ITruckingLogic _truckingLogic;
private readonly ICargoLogic _cargoLogic; private IClientLogic _clientLogic;
private readonly ITransportLogic _transportLogic;
private readonly ITransportationLogic _transportationLogic;
public FormTrucking(ILogger<FormTrucking> logger, ITruckingLogic truckingLogic, IClientLogic clientLogic, public FormTrucking(ILogger<FormTrucking> logger, ITruckingLogic truckingLogic, IClientLogic clientLogic,
ICargoLogic cargoLogic, ITransportLogic transportLogic, ITransportationLogic transportationLogic) ICargoLogic cargoLogic, ITransportLogic transportLogic, ITransportationLogic transportationLogic)
@ -32,9 +29,6 @@ namespace TransportCompany
_logger = logger; _logger = logger;
_truckingLogic = truckingLogic; _truckingLogic = truckingLogic;
_clientLogic = clientLogic; _clientLogic = clientLogic;
_cargoLogic = cargoLogic;
_transportLogic = transportLogic;
_transportationLogic = transportationLogic;
} }
private void FormMain_Load(object sender, EventArgs e) private void FormMain_Load(object sender, EventArgs e)
@ -55,6 +49,18 @@ namespace TransportCompany
if (list != null) if (list != null)
{ {
dataGridView.DataSource = list; dataGridView.DataSource = list;
if (list.Any(x => !string.IsNullOrEmpty(x.MongoId)))
{
dataGridView.Columns["Id"].Visible = false;
dataGridView.Columns["MongoId"].Visible = true;
}
else
{
dataGridView.Columns["Id"].Visible = true;
dataGridView.Columns["MongoId"].Visible = false;
}
dataGridView.Columns["ClientId"].Visible = false; dataGridView.Columns["ClientId"].Visible = false;
dataGridView.Columns["CargoId"].Visible = false; dataGridView.Columns["CargoId"].Visible = false;
dataGridView.Columns["TransportId"].Visible = false; dataGridView.Columns["TransportId"].Visible = false;
@ -200,7 +206,7 @@ namespace TransportCompany
} }
} }
private void CreateJsonFilesToolStripMenuItem_Click(object sender, EventArgs e) /*private void CreateJsonFilesToolStripMenuItem_Click(object sender, EventArgs e)
{ {
var listTrucking = _truckingLogic.ReadList(null); var listTrucking = _truckingLogic.ReadList(null);
var listClients = _clientLogic.ReadList(null); var listClients = _clientLogic.ReadList(null);
@ -226,6 +232,36 @@ namespace TransportCompany
writetext.Close(); writetext.Close();
} }
}*/
private void TransferDataToolStripMenuItem_Click(object sender, EventArgs e)
{
var service = Program.ServiceProvider?.GetService(typeof(FormTransferData));
if (service is FormTransferData form)
{
form.ShowDialog();
}
}
private void StartPostgresqlToolStripMenuItem_Click(object sender, EventArgs e)
{
_mongo = false;
Program.ConnectPostgres();
_truckingLogic = Program.ServiceProvider.GetService(typeof(ITruckingLogic)) as TruckingLogic;
_clientLogic = Program.ServiceProvider.GetService(typeof(IClientLogic)) as ClientLogic;
}
private void StartMongoDBToolStripMenuItem_Click(object sender, EventArgs e)
{
_mongo = true;
Program.ConnectMongo();
_truckingLogic = Program.ServiceProvider.GetService(typeof(ITruckingLogic)) as TruckingLogic;
_clientLogic = Program.ServiceProvider.GetService(typeof(IClientLogic)) as ClientLogic;
} }
} }
} }

View File

@ -66,6 +66,7 @@ namespace TransportCompany
services.AddTransient<FormRandomCreateTrucking>(); services.AddTransient<FormRandomCreateTrucking>();
services.AddTransient<FormTimeCheck>(); services.AddTransient<FormTimeCheck>();
services.AddTransient<FormCheckTimeJoin>(); services.AddTransient<FormCheckTimeJoin>();
services.AddTransient<FormTransferData>();
} }
//ðàáîòà ñ Postgresql //ðàáîòà ñ Postgresql