mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Merge branch 'single-price' into maint
This commit is contained in:
commit
bc9285bbfb
@ -1170,9 +1170,9 @@ create_each_transaction_helper(Transaction *template_txn, void *user_data)
|
||||
}
|
||||
else
|
||||
{
|
||||
exchange = gnc_numeric_div(gnc_numeric_create(1,1),
|
||||
gnc_price_get_value(price),
|
||||
1000, GNC_HOW_RND_ROUND_HALF_UP);
|
||||
exchange = gnc_numeric_invert(gnc_price_get_value(price));
|
||||
exchange = gnc_numeric_convert(exchange, 1000,
|
||||
GNC_HOW_RND_ROUND_HALF_UP);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1779,4 +1779,3 @@ GHashTable* gnc_sx_all_instantiate_cashflow_all(GDate range_start, GDate range_e
|
||||
result_map, NULL);
|
||||
return result_map;
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ write_price( GNCPrice* p, gpointer data )
|
||||
g_return_val_if_fail( p != NULL, FALSE );
|
||||
g_return_val_if_fail( data != NULL, FALSE );
|
||||
|
||||
if ( s->is_ok )
|
||||
if ( s->is_ok && gnc_price_get_source(p) == PRICE_SOURCE_INVOICE)
|
||||
{
|
||||
s->is_ok = save_price( s->be, QOF_INSTANCE(p) );
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ price_parse_xml_sub_node(GNCPrice *p, xmlNodePtr sub_node, QofBook *book)
|
||||
{
|
||||
char *text = dom_tree_to_text(sub_node);
|
||||
if (!text) return FALSE;
|
||||
gnc_price_set_source(p, text);
|
||||
gnc_price_set_source_string(p, text);
|
||||
g_free(text);
|
||||
}
|
||||
else if (g_strcmp0("price:type", (char*)sub_node->name) == 0)
|
||||
@ -440,7 +440,7 @@ gnc_price_to_dom_tree(const xmlChar *tag, GNCPrice *price)
|
||||
tmpnode = timespec_to_dom_tree("price:time", ×p);
|
||||
if (!add_child_or_kill_parent(price_xml, tmpnode)) return NULL;
|
||||
|
||||
sourcestr = gnc_price_get_source(price);
|
||||
sourcestr = gnc_price_get_source_string(price);
|
||||
if (sourcestr && (strlen(sourcestr) != 0))
|
||||
{
|
||||
tmpnode = text_to_dom_tree("price:source", sourcestr);
|
||||
|
@ -3112,7 +3112,7 @@ price_parse_xml_sub_node(GNCPrice *p, xmlNodePtr sub_node, QofBook *book)
|
||||
{
|
||||
char *text = dom_tree_to_text(sub_node);
|
||||
if (!text) return FALSE;
|
||||
gnc_price_set_source(p, text);
|
||||
gnc_price_set_source_string(p, text);
|
||||
g_free(text);
|
||||
}
|
||||
else if (g_strcmp0("price:type", (char*)sub_node->name) == 0)
|
||||
|
@ -901,11 +901,8 @@ gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
|
||||
gnc_price_set_commodity (convprice, account_currency);
|
||||
gnc_price_set_currency (convprice, gncInvoiceGetCurrency (invoice));
|
||||
gnc_price_set_time (convprice, postdate);
|
||||
gnc_price_set_source (convprice, "user:invoice-post");
|
||||
|
||||
/* Yes, magic strings are evil but I can't find any defined constants
|
||||
for this..*/
|
||||
gnc_price_set_typestr (convprice, "last");
|
||||
gnc_price_set_source (convprice, PRICE_SOURCE_INVOICE);
|
||||
gnc_price_set_typestr (convprice, PRICE_TYPE_LAST);
|
||||
gnc_price_set_value (convprice, exch_rate);
|
||||
gncInvoiceAddPrice(invoice, convprice);
|
||||
gnc_price_commit_edit (convprice);
|
||||
@ -1702,7 +1699,7 @@ gnc_invoice_update_window (InvoiceWindow *iw, GtkWidget *widget)
|
||||
}
|
||||
|
||||
/* Set the type label */
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), iw->is_credit_note ? _("Credit Note")
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), iw->is_credit_note ? _("Credit Note")
|
||||
: gtk_label_get_text (GTK_LABEL(iw->type_label)));
|
||||
|
||||
if (iw->owner_choice)
|
||||
@ -1820,14 +1817,14 @@ gnc_invoice_update_window (InvoiceWindow *iw, GtkWidget *widget)
|
||||
gtk_widget_hide (hide);
|
||||
hide = GTK_WIDGET (gtk_builder_get_object (iw->builder, "hide4"));
|
||||
gtk_widget_hide (hide);
|
||||
|
||||
|
||||
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "posted_label"));
|
||||
gtk_widget_show (show);
|
||||
gtk_widget_show (iw->posted_date_hbox);
|
||||
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "acct_label"));
|
||||
gtk_widget_show (show);
|
||||
gtk_widget_show (acct_entry);
|
||||
|
||||
|
||||
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "hide1"));
|
||||
gtk_widget_show (show);
|
||||
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "hide2"));
|
||||
@ -2347,17 +2344,17 @@ gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
|
||||
{
|
||||
case GNC_OWNER_VENDOR:
|
||||
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Bill Information"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Bill ID"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Bill ID"));
|
||||
break;
|
||||
case GNC_OWNER_EMPLOYEE:
|
||||
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Voucher Information"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Voucher"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Voucher ID"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Voucher ID"));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
entry_ledger = gnc_entry_ledger_new (iw->book, ledger_type);
|
||||
|
||||
/* Save the ledger... */
|
||||
@ -2430,7 +2427,7 @@ gnc_invoice_window_new_invoice (InvoiceDialogType dialog_type, QofBook *bookp,
|
||||
const GncOwner *start_owner;
|
||||
GncBillTerm *owner_terms = NULL;
|
||||
GncOwnerType owner_type;
|
||||
|
||||
|
||||
g_assert (dialog_type == NEW_INVOICE || dialog_type == MOD_INVOICE || dialog_type == DUP_INVOICE);
|
||||
|
||||
if (invoice)
|
||||
@ -2513,20 +2510,20 @@ gnc_invoice_window_new_invoice (InvoiceDialogType dialog_type, QofBook *bookp,
|
||||
iw->id_label = GTK_WIDGET (gtk_builder_get_object (builder, "label14"));
|
||||
iw->info_label = GTK_WIDGET (gtk_builder_get_object (builder, "label1"));
|
||||
invoice_radio = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_invoice_type"));
|
||||
|
||||
|
||||
iw->type_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_type_choice_hbox"));
|
||||
iw->type_choice = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_type_invoice"));
|
||||
|
||||
|
||||
/* The default GUI lables are for invoices, so change them if it isn't. */
|
||||
owner_type = gncOwnerGetType (&iw->owner);
|
||||
switch(owner_type)
|
||||
{
|
||||
case GNC_OWNER_VENDOR:
|
||||
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Bill Information"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
|
||||
gtk_button_set_label (GTK_BUTTON(invoice_radio), _("Bill"));
|
||||
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Bill ID"));
|
||||
|
||||
|
||||
break;
|
||||
case GNC_OWNER_EMPLOYEE:
|
||||
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Voucher Information"));
|
||||
@ -2536,7 +2533,7 @@ gnc_invoice_window_new_invoice (InvoiceDialogType dialog_type, QofBook *bookp,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* configure the type related widgets based on dialog type and invoice type */
|
||||
switch (dialog_type)
|
||||
{
|
||||
@ -3306,4 +3303,3 @@ gnc_invoice_remind_bills_due_cb (void)
|
||||
|
||||
gnc_invoice_remind_bills_due();
|
||||
}
|
||||
|
||||
|
@ -334,7 +334,7 @@ KvpValue * kvp_frame_get_slot_path_gslist (KvpFrame *frame, GSList *key_path);
|
||||
SET_ENUM("TRANS-DATE-POSTED");
|
||||
SET_ENUM("TRANS-DESCRIPTION");
|
||||
SET_ENUM("TRANS-NUM");
|
||||
|
||||
|
||||
SET_ENUM("KVP-OPTION-PATH");
|
||||
|
||||
SET_ENUM("OPTION-SECTION-ACCOUNTS");
|
||||
@ -355,6 +355,15 @@ KvpValue * kvp_frame_get_slot_path_gslist (KvpFrame *frame, GSList *key_path);
|
||||
SET_ENUM("GNC-HOW-RND-ROUND");
|
||||
SET_ENUM("GNC-HOW-RND-NEVER");
|
||||
|
||||
SET_ENUM("PRICE-SOURCE-EDIT-DLG");
|
||||
SET_ENUM("PRICE-SOURCE-FQ");
|
||||
SET_ENUM("PRICE-SOURCE-USER-PRICE");
|
||||
SET_ENUM("PRICE-SOURCE-XFER-DLG-VAL");
|
||||
SET_ENUM("PRICE-SOURCE-SPLIT-REG");
|
||||
SET_ENUM("PRICE-SOURCE-STOCK-SPLIT");
|
||||
SET_ENUM("PRICE-SOURCE-INVOICE");
|
||||
SET_ENUM("PRICE-SOURCE-INVALID");
|
||||
|
||||
#undef SET_ENUM
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ struct gnc_price_s
|
||||
gnc_commodity *commodity;
|
||||
gnc_commodity *currency;
|
||||
Timespec tmspec;
|
||||
char *source;
|
||||
PriceSource source;
|
||||
char *type;
|
||||
gnc_numeric value;
|
||||
|
||||
|
@ -58,9 +58,32 @@ gnc_price_init(GNCPrice* price)
|
||||
price->refcount = 1;
|
||||
price->value = gnc_numeric_zero();
|
||||
price->type = NULL;
|
||||
price->source = NULL;
|
||||
price->source = PRICE_SOURCE_INVALID;
|
||||
}
|
||||
|
||||
/* Array of char constants for converting price-source enums. Be sure to keep in
|
||||
* sync with the enum values in gnc-pricedb.h The string user:price-editor is
|
||||
* explicitly used by price_to_gui() in dialog-price-editor.c and the string
|
||||
* Finance::Quote is explicitly used by fq-results->commod-tz-quote-triples in
|
||||
* price-quotes.scm. Take care to keep them in sync if you make changes. Beware
|
||||
* that the strings are used to store the enum values in the backends so any
|
||||
* changes will affect backward data compatibility.
|
||||
*/
|
||||
static const char* source_names[] =
|
||||
{
|
||||
/* sync with price_to_gui in dialog-price-editor.c */
|
||||
"user:price-editor",
|
||||
/* sync with commidity-tz-quote->price in price-quotes.scm */
|
||||
"Finance::Quote",
|
||||
"user:price",
|
||||
/* String retained for backwards compatibility. */
|
||||
"user:xfer-dialog",
|
||||
"user:split-register",
|
||||
"user:stock-split",
|
||||
"user:invoice-post",
|
||||
"invalid"
|
||||
};
|
||||
|
||||
static void
|
||||
gnc_price_dispose(GObject *pricep)
|
||||
{
|
||||
@ -90,7 +113,7 @@ gnc_price_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SOURCE:
|
||||
g_value_set_string(value, price->source);
|
||||
g_value_set_string(value, gnc_price_get_source_string(price));
|
||||
break;
|
||||
case PROP_TYPE:
|
||||
g_value_set_string(value, price->type);
|
||||
@ -126,7 +149,7 @@ gnc_price_set_property(GObject* object, guint prop_id, const GValue* value, GPar
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SOURCE:
|
||||
gnc_price_set_source(price, g_value_get_string(value));
|
||||
gnc_price_set_source_string(price, g_value_get_string(value));
|
||||
break;
|
||||
case PROP_TYPE:
|
||||
gnc_price_set_typestr(price, g_value_get_string(value));
|
||||
@ -188,10 +211,10 @@ gnc_price_class_init(GNCPriceClass *klass)
|
||||
PROP_SOURCE,
|
||||
g_param_spec_string ("source",
|
||||
"Price source",
|
||||
"The price source is a string describing the "
|
||||
"source of a price quote. It will be something "
|
||||
"like this: 'Finance::Quote', 'user:misc', "
|
||||
"'user:foo', etc.",
|
||||
"The price source is PriceSource enum describing how"
|
||||
" the price was created. This property works on the"
|
||||
" string values in source_names for SQL database"
|
||||
" compatibility.",
|
||||
NULL,
|
||||
G_PARAM_READWRITE));
|
||||
|
||||
@ -252,7 +275,6 @@ gnc_price_destroy (GNCPrice *p)
|
||||
qof_event_gen (&p->inst, QOF_EVENT_DESTROY, NULL);
|
||||
|
||||
if (p->type) CACHE_REMOVE(p->type);
|
||||
if (p->source) CACHE_REMOVE(p->source);
|
||||
|
||||
/* qof_instance_release (&p->inst); */
|
||||
g_object_unref(p);
|
||||
@ -439,22 +461,29 @@ gnc_price_set_time(GNCPrice *p, Timespec t)
|
||||
}
|
||||
|
||||
void
|
||||
gnc_price_set_source(GNCPrice *p, const char *s)
|
||||
gnc_price_set_source(GNCPrice *p, PriceSource s)
|
||||
{
|
||||
if (!p) return;
|
||||
if (g_strcmp0(p->source, s) != 0)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
gnc_price_begin_edit (p);
|
||||
tmp = CACHE_INSERT((gpointer) s);
|
||||
if (p->source) CACHE_REMOVE(p->source);
|
||||
p->source = tmp;
|
||||
gnc_price_set_dirty(p);
|
||||
gnc_price_commit_edit (p);
|
||||
}
|
||||
gnc_price_begin_edit (p);
|
||||
p->source = s;
|
||||
gnc_price_set_dirty(p);
|
||||
gnc_price_commit_edit(p);
|
||||
}
|
||||
|
||||
void
|
||||
gnc_price_set_source_string(GNCPrice *p, const char* str)
|
||||
{
|
||||
if (!p) return;
|
||||
for (PriceSource s = PRICE_SOURCE_EDIT_DLG;
|
||||
s < PRICE_SOURCE_INVALID; ++s)
|
||||
if (strcmp(source_names[s], str) == 0)
|
||||
{
|
||||
gnc_price_set_source(p, s);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
void
|
||||
gnc_price_set_typestr(GNCPrice *p, const char* type)
|
||||
{
|
||||
@ -518,13 +547,20 @@ gnc_price_get_time(const GNCPrice *p)
|
||||
return p->tmspec;
|
||||
}
|
||||
|
||||
const char *
|
||||
PriceSource
|
||||
gnc_price_get_source(const GNCPrice *p)
|
||||
{
|
||||
if (!p) return NULL;
|
||||
if (!p) return PRICE_SOURCE_INVALID;
|
||||
return p->source;
|
||||
}
|
||||
|
||||
const char*
|
||||
gnc_price_get_source_string(const GNCPrice *p)
|
||||
{
|
||||
if (!p) return NULL;
|
||||
return source_names[p->source];
|
||||
}
|
||||
|
||||
const char *
|
||||
gnc_price_get_typestr(const GNCPrice *p)
|
||||
{
|
||||
@ -573,8 +609,7 @@ gnc_price_equal (const GNCPrice *p1, const GNCPrice *p2)
|
||||
if (!timespec_equal (&ts1, &ts2))
|
||||
return FALSE;
|
||||
|
||||
if (g_strcmp0 (gnc_price_get_source (p1),
|
||||
gnc_price_get_source (p2)) != 0)
|
||||
if (gnc_price_get_source (p1) != gnc_price_get_source (p2))
|
||||
return FALSE;
|
||||
|
||||
if (g_strcmp0 (gnc_price_get_typestr (p1),
|
||||
@ -966,6 +1001,18 @@ gnc_pricedb_equal (GNCPriceDB *db1, GNCPriceDB *db2)
|
||||
return equal_data.equal;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
insert_or_replace_price(GNCPriceDB *db, GNCPrice *p)
|
||||
{
|
||||
GNCPrice *old_price = gnc_pricedb_lookup_day (db, p->commodity,
|
||||
p->currency, p->tmspec);
|
||||
if (old_price == NULL)
|
||||
return TRUE;
|
||||
if (p->source < old_price->source)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
/* ==================================================================== */
|
||||
/* The add_price() function is a utility that only manages the
|
||||
* dual hash table instertion */
|
||||
@ -1030,6 +1077,12 @@ add_price(GNCPriceDB *db, GNCPrice *p)
|
||||
LEAVE (" no price list");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!insert_or_replace_price(db, p))
|
||||
{
|
||||
LEAVE("A better price already exists");
|
||||
return FALSE;
|
||||
}
|
||||
g_hash_table_insert(currency_hash, currency, price_list);
|
||||
p->db = db;
|
||||
qof_event_gen (&p->inst, QOF_EVENT_ADD, NULL);
|
||||
@ -1192,7 +1245,7 @@ static gboolean
|
||||
check_one_price_date (GNCPrice *price, gpointer user_data)
|
||||
{
|
||||
remove_info *data = user_data;
|
||||
const gchar *source;
|
||||
PriceSource source;
|
||||
Timespec pt;
|
||||
|
||||
ENTER("price %p (%s), data %p", price,
|
||||
@ -1201,7 +1254,7 @@ check_one_price_date (GNCPrice *price, gpointer user_data)
|
||||
if (!data->delete_user)
|
||||
{
|
||||
source = gnc_price_get_source (price);
|
||||
if (g_strcmp0(source, "Finance::Quote") != 0)
|
||||
if (source != PRICE_SOURCE_FQ)
|
||||
{
|
||||
LEAVE("Not an automatic quote");
|
||||
return TRUE;
|
||||
@ -2454,8 +2507,8 @@ gnc_price_print(GNCPrice *p, FILE *f, int indent)
|
||||
str = str ? str : "(null)";
|
||||
fprintf(f, "%s <cmdty:ref-id>%s</cmdty:ref-id>\n", istr, str);
|
||||
fprintf(f, "%s </pdb:currency>\n", istr);
|
||||
str = gnc_price_get_source(p);
|
||||
str = str ? str : "(null)";
|
||||
str = source_names[gnc_price_get_source(p)];
|
||||
str = str ? str : "invalid";
|
||||
fprintf(f, "%s %s\n", istr, str);
|
||||
str = gnc_price_get_typestr(p);
|
||||
str = str ? str : "(null)";
|
||||
|
@ -156,6 +156,26 @@ GType gnc_pricedb_get_type(void);
|
||||
typedef struct gnc_price_lookup_s GNCPriceLookup;
|
||||
typedef GList PriceList;
|
||||
|
||||
/** Price source enum. Be sure to keep in sync with the source_name array in
|
||||
* gnc-pricedb.c. These are in preference order, so for example a quote with
|
||||
* PRICE_SOURCE_EDIT_DLG will overwrite one with PRICE_SOURCE_FQ but not the
|
||||
* other way around.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
PRICE_SOURCE_EDIT_DLG, // "user:price-editor"
|
||||
PRICE_SOURCE_FQ, // "Finance::Quote"
|
||||
PRICE_SOURCE_USER_PRICE, // "user:price"
|
||||
PRICE_SOURCE_XFER_DLG_VAL, // "user:xfer-dialog"
|
||||
PRICE_SOURCE_SPLIT_REG, // "user:split-register"
|
||||
PRICE_SOURCE_STOCK_SPLIT, // "user:stock-split"
|
||||
PRICE_SOURCE_INVOICE, // "user:invoice-post"
|
||||
PRICE_SOURCE_INVALID,
|
||||
} PriceSource;
|
||||
|
||||
#define PRICE_TYPE_LAST "last"
|
||||
#define PRICE_TYPE_UNK "unknown"
|
||||
#define PRICE_TYPE_TRN "transaction"
|
||||
/* ------------------ */
|
||||
/** @name Constructors
|
||||
@{ */
|
||||
@ -202,7 +222,8 @@ void gnc_price_commit_edit (GNCPrice *p);
|
||||
void gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c);
|
||||
void gnc_price_set_currency(GNCPrice *p, gnc_commodity *c);
|
||||
void gnc_price_set_time(GNCPrice *p, Timespec t);
|
||||
void gnc_price_set_source(GNCPrice *p, const char *source);
|
||||
void gnc_price_set_source(GNCPrice *p, PriceSource source);
|
||||
void gnc_price_set_source_string(GNCPrice *p, const char* s);
|
||||
void gnc_price_set_typestr(GNCPrice *p, const char* type);
|
||||
void gnc_price_set_value(GNCPrice *p, gnc_numeric value);
|
||||
/** @} */
|
||||
@ -213,13 +234,14 @@ void gnc_price_set_value(GNCPrice *p, gnc_numeric value);
|
||||
to the GNCPrice, not copies, so don't free these values.
|
||||
@{ */
|
||||
|
||||
GNCPrice * gnc_price_lookup (const GncGUID *guid, QofBook *book);
|
||||
GNCPrice * gnc_price_lookup (const GncGUID *guid, QofBook *book);
|
||||
/*@ dependent @*/
|
||||
gnc_commodity * gnc_price_get_commodity(const GNCPrice *p);
|
||||
/*@ dependent @*/
|
||||
gnc_commodity * gnc_price_get_currency(const GNCPrice *p);
|
||||
Timespec gnc_price_get_time(const GNCPrice *p);
|
||||
const char * gnc_price_get_source(const GNCPrice *p);
|
||||
PriceSource gnc_price_get_source(const GNCPrice *p);
|
||||
const char * gnc_price_get_source_string(const GNCPrice *p);
|
||||
const char * gnc_price_get_typestr(const GNCPrice *p);
|
||||
gnc_numeric gnc_price_get_value(const GNCPrice *p);
|
||||
gboolean gnc_price_equal(const GNCPrice *p1, const GNCPrice *p2);
|
||||
@ -234,6 +256,15 @@ gboolean gnc_price_equal(const GNCPrice *p1, const GNCPrice *p2);
|
||||
/** This simple function can be useful for debugging the price code */
|
||||
void gnc_price_print(GNCPrice *db, FILE *f, int indent);
|
||||
/** @} */
|
||||
/** @name Denominator Constants Price policy: In order to avoid rounding
|
||||
* problems, currency prices (often called exchange rates) are saved in terms of
|
||||
* the smaller currency, so that price > 1, with a fixed denominator of
|
||||
* 1/1000. Commodity prices in currency are always expressed as value per unit
|
||||
* of the commodity with a fixed denominator of the pricing currency's
|
||||
* SCU * 10000.
|
||||
*/
|
||||
#define CURRENCY_DENOM 10000
|
||||
#define COMMODITY_DENOM_MULT 10000
|
||||
|
||||
/* ================================================================ */
|
||||
/** @name GNCPrice lists
|
||||
|
@ -656,6 +656,7 @@ void
|
||||
make_random_changes_to_price (QofBook *book, GNCPrice *p)
|
||||
{
|
||||
Timespec *ts;
|
||||
PriceSource ps;
|
||||
char *string;
|
||||
gnc_commodity *c;
|
||||
|
||||
@ -673,9 +674,9 @@ make_random_changes_to_price (QofBook *book, GNCPrice *p)
|
||||
gnc_price_set_time (p, *ts);
|
||||
g_free (ts);
|
||||
|
||||
string = get_random_string ();
|
||||
gnc_price_set_source (p, string);
|
||||
g_free (string);
|
||||
ps = (PriceSource)get_random_int_in_range((int)PRICE_SOURCE_EDIT_DLG,
|
||||
(int)PRICE_SOURCE_INVALID);
|
||||
gnc_price_set_source (p, ps);
|
||||
|
||||
string = get_random_string ();
|
||||
gnc_price_set_typestr (p, string);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -786,7 +786,7 @@ gnc_tree_model_price_get_value (GtkTreeModel *tree_model,
|
||||
break;
|
||||
case GNC_TREE_MODEL_PRICE_COL_SOURCE:
|
||||
g_value_init (value, G_TYPE_STRING);
|
||||
g_value_set_string (value, gettext (gnc_price_get_source (price)));
|
||||
g_value_set_string (value, gettext (gnc_price_get_source_string (price)));
|
||||
break;
|
||||
case GNC_TREE_MODEL_PRICE_COL_TYPE:
|
||||
g_value_init (value, G_TYPE_STRING);
|
||||
|
@ -300,8 +300,7 @@ sort_by_source (GtkTreeModel *f_model,
|
||||
return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
|
||||
|
||||
/* sort by source first */
|
||||
result = safe_utf8_collate (gnc_price_get_source (price_a),
|
||||
gnc_price_get_source (price_b));
|
||||
result = gnc_price_get_source (price_a) < gnc_price_get_source (price_b);
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
|
@ -399,8 +399,8 @@ gnc_stock_split_assistant_finish (GtkAssistant *assistant,
|
||||
gnc_price_set_commodity (price, xaccAccountGetCommodity (account));
|
||||
gnc_price_set_currency (price, gnc_currency_edit_get_currency (ce));
|
||||
gnc_price_set_time (price, ts);
|
||||
gnc_price_set_source (price, "user:stock-split");
|
||||
gnc_price_set_typestr (price, "unknown");
|
||||
gnc_price_set_source (price, PRICE_SOURCE_STOCK_SPLIT);
|
||||
gnc_price_set_typestr (price, PRICE_TYPE_UNK);
|
||||
gnc_price_set_value (price, amount);
|
||||
gnc_price_commit_edit (price);
|
||||
|
||||
|
@ -50,8 +50,6 @@
|
||||
|
||||
#define DIALOG_PRICE_EDIT_CM_CLASS "dialog-price-edit"
|
||||
#define GNC_PREFS_GROUP "dialogs.price-editor"
|
||||
#define DIALOG_PRICE_EDIT_SOURCE "user:price-editor"
|
||||
|
||||
|
||||
/* This static indicates the debugging module that this .o belongs to. */
|
||||
G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_GUI;
|
||||
@ -163,7 +161,7 @@ price_to_gui (PriceEditDialog *pedit_dialog)
|
||||
|
||||
currency = gnc_price_get_currency (pedit_dialog->price);
|
||||
date = gnc_price_get_time (pedit_dialog->price);
|
||||
source = gnc_price_get_source (pedit_dialog->price);
|
||||
source = gnc_price_get_source_string (pedit_dialog->price);
|
||||
type = gnc_price_get_typestr (pedit_dialog->price);
|
||||
value = gnc_price_get_value (pedit_dialog->price);
|
||||
}
|
||||
@ -172,7 +170,7 @@ price_to_gui (PriceEditDialog *pedit_dialog)
|
||||
currency = gnc_default_currency ();
|
||||
date.tv_sec = gnc_time (NULL);
|
||||
date.tv_nsec = 0;
|
||||
source = DIALOG_PRICE_EDIT_SOURCE;
|
||||
source = "user:price-editor"; //Sync with source_names in gnc-pricedb.c
|
||||
type = "";
|
||||
value = gnc_numeric_zero ();
|
||||
}
|
||||
@ -239,7 +237,7 @@ gui_to_price (PriceEditDialog *pedit_dialog)
|
||||
gnc_price_set_commodity (pedit_dialog->price, commodity);
|
||||
gnc_price_set_currency (pedit_dialog->price, currency);
|
||||
gnc_price_set_time (pedit_dialog->price, date);
|
||||
gnc_price_set_source (pedit_dialog->price, source);
|
||||
gnc_price_set_source_string (pedit_dialog->price, source);
|
||||
gnc_price_set_typestr (pedit_dialog->price, type);
|
||||
gnc_price_set_value (pedit_dialog->price, value);
|
||||
gnc_price_commit_edit (pedit_dialog->price);
|
||||
@ -553,7 +551,7 @@ gnc_price_edit_dialog (GtkWidget * parent,
|
||||
price = gnc_price_clone(price, pedit_dialog->book);
|
||||
// } else {
|
||||
// price = gnc_price_create (pedit_dialog->book);
|
||||
gnc_price_set_source (price, DIALOG_PRICE_EDIT_SOURCE);
|
||||
gnc_price_set_source (price, PRICE_SOURCE_EDIT_DLG);
|
||||
}
|
||||
|
||||
pedit_dialog->is_new = TRUE;
|
||||
|
@ -1149,7 +1149,26 @@ gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gnc_numeric
|
||||
gnc_numeric_invert(gnc_numeric num)
|
||||
{
|
||||
if (num.num == 0)
|
||||
return gnc_numeric_zero();
|
||||
if (num.denom > 0)
|
||||
{
|
||||
if (num.num < 0)
|
||||
return gnc_numeric_create (-num.denom, -num.num);
|
||||
return gnc_numeric_create (num.denom, num.num);
|
||||
}
|
||||
else /* Negative denominator means multiply instead of divide. */
|
||||
{
|
||||
int64_t mult = (num.num < 0 ? INT64_C(-1) : INT64_C(1));
|
||||
qofint128 denom = mult128(-num.denom, mult * num.num);
|
||||
if (denom.hi)
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
return gnc_numeric_create (mult, denom.lo);
|
||||
}
|
||||
}
|
||||
/* *******************************************************************
|
||||
* double_to_gnc_numeric
|
||||
********************************************************************/
|
||||
|
@ -504,6 +504,13 @@ gnc_numeric gnc_numeric_reduce(gnc_numeric n);
|
||||
********************************************************************/
|
||||
gboolean gnc_numeric_to_decimal(gnc_numeric * a,
|
||||
guint8 * max_decimal_places);
|
||||
|
||||
/** Invert a gnc_numeric.
|
||||
* Much faster than dividing 1 by it.
|
||||
* @param num The number to be inverted
|
||||
* @return a gnc_numeric that is the inverse of num
|
||||
*/
|
||||
gnc_numeric gnc_numeric_invert (gnc_numeric num);
|
||||
/** @} */
|
||||
|
||||
/** @name GValue
|
||||
|
@ -166,6 +166,15 @@ if ($exchange eq "currency") {
|
||||
while ($#ARGV >= 0) {
|
||||
my $to = shift;
|
||||
my $result = $q->currency($from, $to);
|
||||
unless (defined($result) && $result >= 1) {
|
||||
my $inv_res = $q->currency($to, $from);
|
||||
if (defined($inv_res)) {
|
||||
my $tmp = $to;
|
||||
$to = $from;
|
||||
$from = $tmp;
|
||||
$result = $inv_res;
|
||||
}
|
||||
}
|
||||
if (defined($result)) {
|
||||
printf "1 $from = $result $to\n";
|
||||
} else {
|
||||
|
@ -350,6 +350,19 @@ while(<>) {
|
||||
last unless $to_currency;
|
||||
|
||||
my $price = $quoter->currency($from_currency, $to_currency);
|
||||
my $inv_price = undef;
|
||||
#Sometimes price quotes are available in only one direction, and if the
|
||||
#direction we asked for results in a quote < 1 we want the other direction
|
||||
#if it's available to get more significant digits.
|
||||
unless (defined($price) && $price > 1) {
|
||||
$inv_price = $quoter->currency($to_currency, $from_currency);
|
||||
if (defined($inv_price)) {
|
||||
my $tmp = $to_currency;
|
||||
$to_currency = $from_currency;
|
||||
$from_currency = $tmp;
|
||||
$price = $inv_price;
|
||||
}
|
||||
}
|
||||
|
||||
$quote_data{$from_currency, "success"} = defined($price);
|
||||
$quote_data{$from_currency, "symbol"} = $from_currency;
|
||||
|
@ -64,8 +64,9 @@ static QofLogModule log_module = GNC_MOD_LEDGER;
|
||||
static CursorClass copied_class = CURSOR_CLASS_NONE;
|
||||
static SCM copied_item = SCM_UNDEFINED;
|
||||
static GncGUID copied_leader_guid;
|
||||
|
||||
|
||||
/* A denominator representing number of digits to the right of the decimal point
|
||||
* displayed in a price cell. */
|
||||
static int PRICE_CELL_DENOM = 1000000;
|
||||
/** static prototypes *****************************************************/
|
||||
|
||||
static gboolean gnc_split_register_save_to_scm (SplitRegister *reg,
|
||||
@ -2043,7 +2044,8 @@ recalculate_value (Split *split, SplitRegister *reg,
|
||||
}
|
||||
|
||||
static void
|
||||
record_price (SplitRegister *reg, Account *account, gnc_numeric value)
|
||||
record_price (SplitRegister *reg, Account *account, gnc_numeric value,
|
||||
PriceSource source)
|
||||
{
|
||||
Transaction *trans = gnc_split_register_get_current_trans (reg);
|
||||
QofBook *book = qof_instance_get_book (QOF_INSTANCE (account));
|
||||
@ -2051,8 +2053,11 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
|
||||
gnc_commodity *comm = xaccAccountGetCommodity (account);
|
||||
gnc_commodity *curr = xaccTransGetCurrency (trans);
|
||||
GNCPrice *price;
|
||||
gnc_numeric price_value;
|
||||
int scu = gnc_commodity_get_fraction(curr);
|
||||
Timespec ts;
|
||||
BasicCell *cell;
|
||||
BasicCell *cell = gnc_table_layout_get_cell (reg->table->layout, DATE_CELL);
|
||||
gboolean swap = FALSE;
|
||||
|
||||
/* Only record the price for account types that don't have a
|
||||
* "rate" cell. They'll get handled later by
|
||||
@ -2060,14 +2065,58 @@ record_price (SplitRegister *reg, Account *account, gnc_numeric value)
|
||||
*/
|
||||
if (gnc_split_reg_has_rate_cell (reg->type))
|
||||
return;
|
||||
cell = gnc_table_layout_get_cell (reg->table->layout, DATE_CELL);
|
||||
gnc_date_cell_get_date ((DateCell*)cell, &ts);
|
||||
price = gnc_pricedb_lookup_day (pricedb, comm, curr, ts);
|
||||
if (!price)
|
||||
{
|
||||
price = gnc_pricedb_lookup_day (pricedb, curr, comm, ts);
|
||||
if (price)
|
||||
/* It might be better to raise an error here: We shouldn't be creating
|
||||
* currency->commodity prices.
|
||||
*/
|
||||
swap = TRUE;
|
||||
}
|
||||
if (price)
|
||||
{
|
||||
price_value = gnc_price_get_value(price);
|
||||
if (gnc_numeric_equal(swap ? gnc_numeric_invert(value) : value,
|
||||
price_value))
|
||||
{
|
||||
gnc_price_unref (price);
|
||||
return;
|
||||
}
|
||||
if (gnc_price_get_source(price) < PRICE_SOURCE_XFER_DLG_VAL)
|
||||
{
|
||||
/* Existing price is preferred over this one. */
|
||||
gnc_price_unref(price);
|
||||
return;
|
||||
}
|
||||
if (swap)
|
||||
{
|
||||
value = gnc_numeric_invert(value);
|
||||
scu = gnc_commodity_get_fraction(comm);
|
||||
}
|
||||
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
|
||||
GNC_HOW_RND_ROUND_HALF_UP);
|
||||
gnc_price_begin_edit (price);
|
||||
gnc_price_set_time (price, ts);
|
||||
gnc_price_set_source (price, source);
|
||||
gnc_price_set_typestr (price, PRICE_TYPE_TRN);
|
||||
gnc_price_set_value (price, value);
|
||||
gnc_price_commit_edit (price);
|
||||
gnc_price_unref (price);
|
||||
return;
|
||||
}
|
||||
|
||||
value = gnc_numeric_convert(value, scu * COMMODITY_DENOM_MULT,
|
||||
GNC_HOW_RND_ROUND_HALF_UP);
|
||||
price = gnc_price_create (book);
|
||||
gnc_price_begin_edit (price);
|
||||
gnc_price_set_commodity (price, comm);
|
||||
gnc_price_set_currency (price, curr);
|
||||
gnc_price_set_time (price, ts);
|
||||
gnc_price_set_source (price, "user:split-register");
|
||||
gnc_price_set_source (price, source);
|
||||
gnc_price_set_typestr (price, PRICE_TYPE_TRN);
|
||||
gnc_price_set_value (price, value);
|
||||
gnc_pricedb_add_price (pricedb, price);
|
||||
gnc_price_commit_edit (price);
|
||||
@ -2090,6 +2139,7 @@ gnc_split_register_auto_calc (SplitRegister *reg, Split *split)
|
||||
Account *account;
|
||||
int denom;
|
||||
int choice;
|
||||
PriceSource source = PRICE_SOURCE_USER_PRICE;
|
||||
|
||||
if (STOCK_REGISTER != reg->type &&
|
||||
CURRENCY_REGISTER != reg->type &&
|
||||
@ -2238,6 +2288,7 @@ gnc_split_register_auto_calc (SplitRegister *reg, Split *split)
|
||||
{
|
||||
recalculate_price (split, reg, value, amount);
|
||||
price_changed = TRUE;
|
||||
source = PRICE_SOURCE_SPLIT_REG;
|
||||
}
|
||||
if (recalc_value)
|
||||
recalculate_value (split, reg, price, amount, shares_changed);
|
||||
@ -2248,7 +2299,7 @@ gnc_split_register_auto_calc (SplitRegister *reg, Split *split)
|
||||
PRIC_CELL);
|
||||
price = gnc_price_cell_get_value (cell);
|
||||
if (gnc_numeric_positive_p(price))
|
||||
record_price (reg, account, price);
|
||||
record_price (reg, account, price, source);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@ -2547,7 +2598,8 @@ gnc_split_register_config_cells (SplitRegister *reg)
|
||||
/* Use 6 decimal places for prices and "exchange rates" */
|
||||
gnc_price_cell_set_fraction
|
||||
((PriceCell *)
|
||||
gnc_table_layout_get_cell (reg->table->layout, PRIC_CELL), 1000000);
|
||||
gnc_table_layout_get_cell (reg->table->layout, PRIC_CELL),
|
||||
PRICE_CELL_DENOM);
|
||||
|
||||
/* Initialize shares and share balance cells */
|
||||
gnc_price_cell_set_print_info
|
||||
|
@ -1,17 +1,17 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; price-quotes.scm - manage sub-processes.
|
||||
;;; Copyright 2001 Rob Browning <rlb@cs.utexas.edu>
|
||||
;;;
|
||||
;;; This program is free software; you can redistribute it and/or
|
||||
;;; modify it under the terms of the GNU General Public License as
|
||||
;;; published by the Free Software Foundation; either version 2 of
|
||||
;;; the License, or (at your option) any later version.
|
||||
;;;
|
||||
;;; This program is distributed in the hope that it will be useful,
|
||||
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
;;;
|
||||
;;;
|
||||
;;; This program is free software; you can redistribute it and/or
|
||||
;;; modify it under the terms of the GNU General Public License as
|
||||
;;; published by the Free Software Foundation; either version 2 of
|
||||
;;; the License, or (at your option) any later version.
|
||||
;;;
|
||||
;;; This program is distributed in the hope that it will be useful,
|
||||
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
;;;
|
||||
;;; You should have received a copy of the GNU General Public License
|
||||
;;; along with this program; if not, contact:
|
||||
;;;
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
(define (item-list->hash! lst hash
|
||||
getkey getval
|
||||
hashref hashset
|
||||
hashref hashset
|
||||
list-duplicates?)
|
||||
;; Takes a list of the form (item item item item) and returns a hash
|
||||
;; formed by traversing the list, and getting the key and val from
|
||||
@ -58,7 +58,7 @@
|
||||
(if existing-val
|
||||
(hashset hash key (cons val existing-val))
|
||||
(hashset hash key (list val))))))
|
||||
|
||||
|
||||
(for-each handle-item lst)
|
||||
hash)
|
||||
|
||||
@ -205,7 +205,7 @@
|
||||
;; a list of the corresponding commodities. Also perform a bit of
|
||||
;; optimization, merging calls for symbols to the same
|
||||
;; Finance::Quote method.
|
||||
;;
|
||||
;;
|
||||
;; Returns a list of the info needed for a set of calls to
|
||||
;; gnc-fq-helper. Each item will of the list will be of the
|
||||
;; form:
|
||||
@ -223,7 +223,7 @@
|
||||
(commodity-list #f)
|
||||
(currency-list (filter
|
||||
(lambda (a) (not (gnc-commodity-equiv (cadr a) (caddr a))))
|
||||
(call-with-values
|
||||
(call-with-values
|
||||
(lambda () (partition!
|
||||
(lambda (cmd)
|
||||
(not (string=? (car cmd) "currency")))
|
||||
@ -257,7 +257,7 @@
|
||||
;;
|
||||
;; ("yahoo" (commodity-1 currency-1 tz-1)
|
||||
;; (commodity-2 currency-2 tz-2) ...)
|
||||
;;
|
||||
;;
|
||||
;; ("yahoo" "IBM" "AMD" ...)
|
||||
;;
|
||||
|
||||
@ -370,8 +370,20 @@
|
||||
(string? currency-str)
|
||||
(gnc-commodity-table-lookup commodity-table
|
||||
"ISO4217"
|
||||
(string-upcase currency-str)))))
|
||||
|
||||
(string-upcase currency-str))))
|
||||
(pricedb (gnc-pricedb-get-db book))
|
||||
(saved-price #f)
|
||||
(commodity-str (gnc-commodity-get-printname commodity))
|
||||
)
|
||||
(if (equal? (gnc-commodity-get-printname currency) commodity-str)
|
||||
(let* ((symbol (assq-ref quote-data 'symbol))
|
||||
(other-curr
|
||||
(and commodity-table
|
||||
(string? symbol)
|
||||
(gnc-commodity-table-lookup commodity-table "ISO4217"
|
||||
(string-upcase symbol)))))
|
||||
(set! commodity other-curr))
|
||||
)
|
||||
(or-map (lambda (price-sym)
|
||||
(let ((p (assq-ref quote-data price-sym)))
|
||||
(if p
|
||||
@ -403,27 +415,52 @@
|
||||
(if (not (and commodity currency gnc-time price price-type))
|
||||
(string-append
|
||||
currency-str ":" (gnc-commodity-get-mnemonic commodity))
|
||||
(let ((gnc-price (gnc-price-create book)))
|
||||
(if (not gnc-price)
|
||||
(string-append
|
||||
currency-str ":" (gnc-commodity-get-mnemonic commodity))
|
||||
(begin
|
||||
(set! saved-price (gnc-pricedb-lookup-day pricedb
|
||||
commodity currency
|
||||
gnc-time))
|
||||
(if (null? saved-price)
|
||||
(begin
|
||||
(gnc-price-begin-edit gnc-price)
|
||||
(gnc-price-set-commodity gnc-price commodity)
|
||||
(gnc-price-set-currency gnc-price currency)
|
||||
(gnc-price-set-time gnc-price gnc-time)
|
||||
(gnc-price-set-source gnc-price "Finance::Quote")
|
||||
(gnc-price-set-typestr gnc-price price-type)
|
||||
(gnc-price-set-value gnc-price price)
|
||||
(gnc-price-commit-edit gnc-price)
|
||||
gnc-price))))))
|
||||
(set! saved-price (gnc-pricedb-lookup-day pricedb currency
|
||||
commodity gnc-time))
|
||||
(if (not (null? saved-price))
|
||||
(set! price (gnc-numeric-invert(price))))))
|
||||
(if (not (null? saved-price))
|
||||
(if (> (gnc-price-get-source saved-price) PRICE-SOURCE-FQ)
|
||||
(begin
|
||||
(gnc-price-begin-edit saved-price)
|
||||
(gnc-price-set-time saved-price gnc-time)
|
||||
(gnc-price-set-source saved-price PRICE-SOURCE-FQ)
|
||||
(gnc-price-set-typestr saved-price price-type)
|
||||
(gnc-price-set-value saved-price price)
|
||||
(gnc-price-commit-edit saved-price)
|
||||
#f)
|
||||
#f)
|
||||
(let ((gnc-price (gnc-price-create book)))
|
||||
(if (not gnc-price)
|
||||
(string-append
|
||||
currency-str ":" (gnc-commodity-get-mnemonic commodity))
|
||||
(begin
|
||||
(gnc-price-begin-edit gnc-price)
|
||||
(gnc-price-set-commodity gnc-price commodity)
|
||||
(gnc-price-set-currency gnc-price currency)
|
||||
(gnc-price-set-time gnc-price gnc-time)
|
||||
(gnc-price-set-source gnc-price PRICE-SOURCE-FQ)
|
||||
(gnc-price-set-typestr gnc-price price-type)
|
||||
(gnc-price-set-value gnc-price price)
|
||||
(gnc-price-commit-edit gnc-price)
|
||||
gnc-price)))))
|
||||
)))
|
||||
|
||||
(define (book-add-prices! book prices)
|
||||
(let ((pricedb (gnc-pricedb-get-db book)))
|
||||
(for-each
|
||||
(lambda (price)
|
||||
(gnc-pricedb-add-price pricedb price)
|
||||
(gnc-price-unref price))
|
||||
(if price
|
||||
(begin
|
||||
(gnc-pricedb-add-price pricedb price)
|
||||
(gnc-price-unref price)
|
||||
#f)))
|
||||
prices)))
|
||||
|
||||
;; FIXME: uses of gnc:warn in here need to be cleaned up. Right
|
||||
|
Loading…
Reference in New Issue
Block a user