~tristan957/harvest-almanac

a1d16aabbf7ce25286059df87cdc30fe46fdb867 — Tristan Partin 1 year, 4 months ago 8544114
harvest-glib: continue organization
14 files changed, 415 insertions(+), 87 deletions(-)

M harvest-glib/estimate/harvest-estimate.c
M harvest-glib/harvest.h
M harvest-glib/meson.build
R harvest-glib/{task/harvest-task-assignment.c => project/harvest-project-task-assignment.c}
R harvest-glib/project/{harvest-user-assignment.h => harvest-project-task-assignment.h}
R harvest-glib/project/{harvest-user-assignment.c => harvest-project-user-assignment.c}
R harvest-glib/{task/harvest-task-assignment.h => project/harvest-project-user-assignment.h}
M harvest-glib/project/meson.build
M harvest-glib/task/meson.build
M harvest-glib/time-entry/harvest-time-entry.c
M harvest-glib/time-entry/requests/harvest-late-request.c
A harvest-glib/user/harvest-user-project-assignment.c
A harvest-glib/user/harvest-user-project-assignment.h
M harvest-glib/user/meson.build
M harvest-glib/estimate/harvest-estimate.c => harvest-glib/estimate/harvest-estimate.c +5 -5
@@ 98,11 98,11 @@ harvest_estimate_deserialize_property(JsonSerializable *serializable, const gcha

		return TRUE;
	} else if (g_strcmp0(prop_name, "line_items") == 0) {
		JsonArray *arr	   = json_node_get_array(prop_node);
		const guint length = json_array_get_length(arr);
		GPtrArray *roles   = g_ptr_array_sized_new(length);
		json_array_foreach_element(arr, line_items_for_each, roles);
		g_value_set_boxed(val, roles);
		JsonArray *arr		  = json_node_get_array(prop_node);
		const guint length	  = json_array_get_length(arr);
		GPtrArray *line_items = g_ptr_array_sized_new(length);
		json_array_foreach_element(arr, line_items_for_each, line_items);
		g_value_set_boxed(val, line_items);

		return TRUE;
	} else if (g_strcmp0(prop_name, "creator") == 0) {

M harvest-glib/harvest.h => harvest-glib/harvest.h +3 -2
@@ 25,12 25,13 @@
#include "harvest-glib/invoice/harvest-invoice-item-category.h"
#include "harvest-glib/invoice/harvest-invoice-line-item.h"
#include "harvest-glib/invoice/harvest-invoice.h"
#include "harvest-glib/project/harvest-project-task-assignment.h"
#include "harvest-glib/project/harvest-project-user-assignment.h"
#include "harvest-glib/project/harvest-project.h"
#include "harvest-glib/project/harvest-user-assignment.h"
#include "harvest-glib/task/harvest-task-assignment.h"
#include "harvest-glib/task/harvest-task.h"
#include "harvest-glib/time-entry/harvest-time-entry.h"
#include "harvest-glib/time-entry/requests/harvest-late-request.h"
#include "harvest-glib/user/harvest-user-project-assignment.h"
#include "harvest-glib/user/harvest-user.h"
#include "harvest-glib/user/requests/harvest-users-me-request.h"


M harvest-glib/meson.build => harvest-glib/meson.build +3 -2
@@ 31,12 31,13 @@ harvest_glib_sources = [
    'invoice/harvest-invoice-item-category.c',
    'invoice/harvest-invoice-line-item.c',
    'invoice/harvest-invoice.c',
    'project/harvest-project-task-assignment.c',
    'project/harvest-project-user-assignment.c',
    'project/harvest-project.c',
    'project/harvest-user-assignment.c',
    'task/harvest-task-assignment.c',
    'task/harvest-task.c',
    'time-entry/harvest-time-entry.c',
    'time-entry/requests/harvest-late-request.c',
    'user/harvest-user-project-assignment.c',
    'user/harvest-user.c',
    'user/requests/harvest-users-me-request.c'
]

R harvest-glib/task/harvest-task-assignment.c => harvest-glib/project/harvest-project-task-assignment.c +24 -21
@@ 7,11 7,11 @@
#include <glib/gi18n-lib.h>
#include <json-glib/json-glib.h>

#include "harvest-glib/project/harvest-project-task-assignment.h"
#include "harvest-glib/project/harvest-project.h"
#include "harvest-glib/task/harvest-task-assignment.h"
#include "harvest-glib/task/harvest-task.h"

struct _HarvestTaskAssignment
struct _HarvestProjectTaskAssignment
{
	GObject parent_instance;



@@ 26,12 26,14 @@ struct _HarvestTaskAssignment
	GDateTime *updated_at;
};

static void harvest_task_assignment_json_serializable_init(JsonSerializableIface *iface);
static void harvest_project_task_assignment_json_serializable_init(JsonSerializableIface *iface);

G_DEFINE_TYPE_WITH_CODE(HarvestTaskAssignment, harvest_task_assignment, G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE(JSON_TYPE_SERIALIZABLE, harvest_task_assignment_json_serializable_init))
G_DEFINE_TYPE_WITH_CODE(HarvestProjectTaskAssignment, harvest_project_task_assignment,
	G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE(
		JSON_TYPE_SERIALIZABLE, harvest_project_task_assignment_json_serializable_init))

enum HarvestTaskAssignmentProps
enum HarvestProjectTaskAssignmentProps
{
	PROP_0,
	PROP_ID,


@@ 49,7 51,7 @@ enum HarvestTaskAssignmentProps
static GParamSpec *obj_properties[N_PROPS];

static gboolean
harvest_task_assignment_json_deserialize_property(JsonSerializable *serializable,
harvest_project_task_assignment_json_deserialize_property(JsonSerializable *serializable,
	const gchar *prop_name, GValue *val, GParamSpec *pspec, JsonNode *prop_node)
{
	if (g_strcmp0(prop_name, "created_at") == 0 || g_strcmp0(prop_name, "updated_at") == 0) {


@@ 74,15 76,15 @@ harvest_task_assignment_json_deserialize_property(JsonSerializable *serializable
}

static void
harvest_task_assignment_json_serializable_init(JsonSerializableIface *iface)
harvest_project_task_assignment_json_serializable_init(JsonSerializableIface *iface)
{
	iface->deserialize_property = harvest_task_assignment_json_deserialize_property;
	iface->deserialize_property = harvest_project_task_assignment_json_deserialize_property;
}

static void
harvest_task_assignment_finalize(GObject *obj)
harvest_project_task_assignment_finalize(GObject *obj)
{
	HarvestTaskAssignment *self = HARVEST_TASK_ASSIGNMENT(obj);
	HarvestProjectTaskAssignment *self = HARVEST_PROJECT_TASK_ASSIGNMENT(obj);

	if (self->project != NULL)
		g_object_unref(self->project);


@@ 93,13 95,14 @@ harvest_task_assignment_finalize(GObject *obj)
	if (self->updated_at != NULL)
		g_date_time_unref(self->updated_at);

	G_OBJECT_CLASS(harvest_task_assignment_parent_class)->finalize(obj);
	G_OBJECT_CLASS(harvest_project_task_assignment_parent_class)->finalize(obj);
}

static void
harvest_task_assignment_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
harvest_project_task_assignment_get_property(
	GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
{
	HarvestTaskAssignment *self = HARVEST_TASK_ASSIGNMENT(obj);
	HarvestProjectTaskAssignment *self = HARVEST_PROJECT_TASK_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:


@@ 135,10 138,10 @@ harvest_task_assignment_get_property(GObject *obj, guint prop_id, GValue *val, G
}

static void
harvest_task_assignment_set_property(
harvest_project_task_assignment_set_property(
	GObject *obj, guint prop_id, const GValue *val, GParamSpec *pspec)
{
	HarvestTaskAssignment *self = HARVEST_TASK_ASSIGNMENT(obj);
	HarvestProjectTaskAssignment *self = HARVEST_PROJECT_TASK_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:


@@ 182,13 185,13 @@ harvest_task_assignment_set_property(
}

static void
harvest_task_assignment_class_init(HarvestTaskAssignmentClass *klass)
harvest_project_task_assignment_class_init(HarvestProjectTaskAssignmentClass *klass)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize		= harvest_task_assignment_finalize;
	obj_class->get_property = harvest_task_assignment_get_property;
	obj_class->set_property = harvest_task_assignment_set_property;
	obj_class->finalize		= harvest_project_task_assignment_finalize;
	obj_class->get_property = harvest_project_task_assignment_get_property;
	obj_class->set_property = harvest_project_task_assignment_set_property;

	obj_properties[PROP_ID]
		= g_param_spec_int("id", _("ID"), _("Unique ID for the task assignment."), 0, INT_MAX, 0,


@@ 223,5 226,5 @@ harvest_task_assignment_class_init(HarvestTaskAssignmentClass *klass)
}

static void
harvest_task_assignment_init(G_GNUC_UNUSED HarvestTaskAssignment *self)
harvest_project_task_assignment_init(G_GNUC_UNUSED HarvestProjectTaskAssignment *self)
{}

R harvest-glib/project/harvest-user-assignment.h => harvest-glib/project/harvest-project-task-assignment.h +3 -3
@@ 8,8 8,8 @@

G_BEGIN_DECLS

#define HARVEST_TYPE_USER_ASSIGNMENT (harvest_invoice_get_type())
G_DECLARE_FINAL_TYPE(
	HarvestUserAssignment, harvest_user_assignment, HARVEST, USER_ASSIGNMENT, GObject)
#define HARVEST_TYPE_PROJECT_TASK_ASSIGNMENT (harvest_project_task_assignment_get_type())
G_DECLARE_FINAL_TYPE(HarvestProjectTaskAssignment, harvest_project_task_assignment, HARVEST,
	PROJECT_TASK_ASSIGNMENT, GObject)

G_END_DECLS

R harvest-glib/project/harvest-user-assignment.c => harvest-glib/project/harvest-project-user-assignment.c +25 -22
@@ 7,11 7,11 @@
#include <glib/gi18n-lib.h>
#include <json-glib/json-glib.h>

#include "harvest-glib/project/harvest-project-user-assignment.h"
#include "harvest-glib/project/harvest-project.h"
#include "harvest-glib/project/harvest-user-assignment.h"
#include "harvest-glib/user/harvest-user.h"

struct _HarvestUserAssignment
struct _HarvestProjectUserAssignment
{
	GObject parent_instance;



@@ 27,12 27,14 @@ struct _HarvestUserAssignment
	GDateTime *updated_at;
};

static void harvest_user_assignment_json_serializable_init(JsonSerializableIface *iface);
static void harvest_project_user_assignment_json_serializable_init(JsonSerializableIface *iface);

G_DEFINE_TYPE_WITH_CODE(HarvestUserAssignment, harvest_user_assignment, G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE(JSON_TYPE_SERIALIZABLE, harvest_user_assignment_json_serializable_init))
G_DEFINE_TYPE_WITH_CODE(HarvestProjectUserAssignment, harvest_project_user_assignment,
	G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE(
		JSON_TYPE_SERIALIZABLE, harvest_project_user_assignment_json_serializable_init))

enum HarvestUserAssignmentProps
enum HarvestProjectUserAssignmentProps
{
	PROP_0,
	PROP_ID,


@@ 51,8 53,8 @@ enum HarvestUserAssignmentProps
static GParamSpec *obj_properties[N_PROPS];

static gboolean
harvest_user_assignment_deserialize_property(JsonSerializable *serializable, const gchar *prop_name,
	GValue *val, GParamSpec *pspec, JsonNode *prop_node)
harvest_project_user_assignment_deserialize_property(JsonSerializable *serializable,
	const gchar *prop_name, GValue *val, GParamSpec *pspec, JsonNode *prop_node)
{
	if (g_strcmp0(prop_name, "created_at") == 0 || g_strcmp0(prop_name, "updated_at") == 0) {
		const GDateTime *dt = g_date_time_new_from_iso8601(json_node_get_string(prop_node), NULL);


@@ 76,15 78,15 @@ harvest_user_assignment_deserialize_property(JsonSerializable *serializable, con
}

static void
harvest_user_assignment_json_serializable_init(JsonSerializableIface *iface)
harvest_project_user_assignment_json_serializable_init(JsonSerializableIface *iface)
{
	iface->deserialize_property = harvest_user_assignment_deserialize_property;
	iface->deserialize_property = harvest_project_user_assignment_deserialize_property;
}

static void
harvest_user_assignment_finalize(GObject *obj)
harvest_project_user_assignment_finalize(GObject *obj)
{
	HarvestUserAssignment *self = HARVEST_USER_ASSIGNMENT(obj);
	HarvestProjectUserAssignment *self = HARVEST_PROJECT_USER_ASSIGNMENT(obj);

	if (self->project != NULL)
		g_object_unref(self->project);


@@ 95,13 97,14 @@ harvest_user_assignment_finalize(GObject *obj)
	if (self->updated_at != NULL)
		g_date_time_unref(self->updated_at);

	G_OBJECT_CLASS(harvest_user_assignment_parent_class)->finalize(obj);
	G_OBJECT_CLASS(harvest_project_user_assignment_parent_class)->finalize(obj);
}

static void
harvest_user_assignment_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
harvest_project_user_assignment_get_property(
	GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
{
	HarvestUserAssignment *self = HARVEST_USER_ASSIGNMENT(obj);
	HarvestProjectUserAssignment *self = HARVEST_PROJECT_USER_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:


@@ 140,10 143,10 @@ harvest_user_assignment_get_property(GObject *obj, guint prop_id, GValue *val, G
}

static void
harvest_user_assignment_set_property(
harvest_project_user_assignment_set_property(
	GObject *obj, guint prop_id, const GValue *val, GParamSpec *pspec)
{
	HarvestUserAssignment *self = HARVEST_USER_ASSIGNMENT(obj);
	HarvestProjectUserAssignment *self = HARVEST_PROJECT_USER_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:


@@ 190,13 193,13 @@ harvest_user_assignment_set_property(
}

static void
harvest_user_assignment_class_init(HarvestUserAssignmentClass *klass)
harvest_project_user_assignment_class_init(HarvestProjectUserAssignmentClass *klass)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize		= harvest_user_assignment_finalize;
	obj_class->get_property = harvest_user_assignment_get_property;
	obj_class->set_property = harvest_user_assignment_set_property;
	obj_class->finalize		= harvest_project_user_assignment_finalize;
	obj_class->get_property = harvest_project_user_assignment_get_property;
	obj_class->set_property = harvest_project_user_assignment_set_property;

	obj_properties[PROP_ID]
		= g_param_spec_int("id", _("ID"), _("Unique ID for the user assignment."), 0, INT_MAX, 0,


@@ 237,5 240,5 @@ harvest_user_assignment_class_init(HarvestUserAssignmentClass *klass)
}

static void
harvest_user_assignment_init(G_GNUC_UNUSED HarvestUserAssignment *self)
harvest_project_user_assignment_init(G_GNUC_UNUSED HarvestProjectUserAssignment *self)
{}

R harvest-glib/task/harvest-task-assignment.h => harvest-glib/project/harvest-project-user-assignment.h +3 -3
@@ 8,8 8,8 @@

G_BEGIN_DECLS

#define HARVEST_TYPE_TASK_ASSIGNMENT (harvest_task_assignment_get_type())
G_DECLARE_FINAL_TYPE(
	HarvestTaskAssignment, harvest_task_assignment, HARVEST, TASK_ASSIGNMENT, GObject)
#define HARVEST_TYPE_PROJECT_USER_ASSIGNMENT (harvest_project_user_assignment_get_type())
G_DECLARE_FINAL_TYPE(HarvestProjectUserAssignment, harvest_project_user_assignment, HARVEST,
	PROJECT_USER_ASSIGNMENT, GObject)

G_END_DECLS

M harvest-glib/project/meson.build => harvest-glib/project/meson.build +2 -1
@@ 1,6 1,7 @@
component_public_headers = [
	'harvest-project-task-assignment.h',
	'harvest-project-user-assignment.h',
	'harvest-project.h',
	'harvest-user-assignment.h',
]

component_header_subdir = join_paths(harvest_glib_header_subdir, 'project')

M harvest-glib/task/meson.build => harvest-glib/task/meson.build +0 -1
@@ 1,5 1,4 @@
component_public_headers = [
	'harvest-task-assignment.h',
	'harvest-task.h',
]


M harvest-glib/time-entry/harvest-time-entry.c => harvest-glib/time-entry/harvest-time-entry.c +27 -27
@@ 10,9 10,9 @@
#include "harvest-glib/client/harvest-client.h"
#include "harvest-glib/common/harvest-common.h"
#include "harvest-glib/invoice/harvest-invoice.h"
#include "harvest-glib/project/harvest-project-task-assignment.h"
#include "harvest-glib/project/harvest-project-user-assignment.h"
#include "harvest-glib/project/harvest-project.h"
#include "harvest-glib/project/harvest-user-assignment.h"
#include "harvest-glib/task/harvest-task-assignment.h"
#include "harvest-glib/task/harvest-task.h"
#include "harvest-glib/time-entry/harvest-time-entry.h"
#include "harvest-glib/user/harvest-user.h"


@@ 24,11 24,11 @@ struct _HarvestTimeEntry
	int id;
	GDateTime *spent_date;
	HarvestUser *user;
	HarvestUserAssignment *user_assignment;
	HarvestProjectUserAssignment *user_assignment;
	HarvestClient *client;
	HarvestProject *project;
	HarvestTask *task;
	HarvestTaskAssignment *task_assingment;
	HarvestProjectTaskAssignment *task_assignment;
	HarvestInvoice *invoice;
	double hours;
	char *notes;


@@ 111,7 111,7 @@ harvest_time_entry_deserialize_property(JsonSerializable *serializable, const gc

		return TRUE;
	} else if (g_strcmp0(prop_name, "user_assignment") == 0) {
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_USER_ASSIGNMENT, prop_node);
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_PROJECT_USER_ASSIGNMENT, prop_node);
		g_value_set_object(val, obj);

		return TRUE;


@@ 131,7 131,7 @@ harvest_time_entry_deserialize_property(JsonSerializable *serializable, const gc

		return TRUE;
	} else if (g_strcmp0(prop_name, "task_assignment") == 0) {
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_TASK_ASSIGNMENT, prop_node);
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_PROJECT_TASK_ASSIGNMENT, prop_node);
		g_value_set_object(val, obj);

		return TRUE;


@@ 169,8 169,8 @@ harvest_time_entry_finalize(GObject *obj)
		g_object_unref(self->project);
	if (self->task != NULL)
		g_object_unref(self->task);
	if (self->task_assingment != NULL)
		g_object_unref(self->task_assingment);
	if (self->task_assignment != NULL)
		g_object_unref(self->task_assignment);
	if (self->invoice != NULL)
		g_object_unref(self->invoice);
	g_free(self->notes);


@@ 217,7 217,7 @@ harvest_time_entry_get_property(GObject *obj, guint prop_id, GValue *val, GParam
		g_value_set_object(val, self->task);
		break;
	case PROP_TASK_ASSIGNMEMT:
		g_value_set_object(val, self->task_assingment);
		g_value_set_object(val, self->task_assignment);
		break;
	case PROP_INVOICE:
		g_value_set_object(val, self->invoice);


@@ 315,9 315,9 @@ harvest_time_entry_set_property(GObject *obj, guint prop_id, const GValue *val, 
		self->task = g_value_get_object(val);
		break;
	case PROP_TASK_ASSIGNMEMT:
		if (self->task_assingment != NULL)
			g_object_unref(self->task_assingment);
		self->task_assingment = g_value_get_object(val);
		if (self->task_assignment != NULL)
			g_object_unref(self->task_assignment);
		self->task_assignment = g_value_get_object(val);
		break;
	case PROP_INVOICE:
		if (self->invoice != NULL)


@@ 403,31 403,31 @@ harvest_time_entry_class_init(HarvestTimeEntryClass *klass)
	obj_properties[PROP_SPENT_DATE]
		= g_param_spec_boxed("spent_date", _("Spent Date"), _("Date of the time entry."),
			G_TYPE_DATE_TIME, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_USER] = g_param_spec_object("user", _("User"),
		_("An object containing the id and name of the associated user."), HARVEST_TYPE_USER,
	obj_properties[PROP_USER]			 = g_param_spec_object("user", _("User"),
		   _("An object containing the id and name of the associated user."), HARVEST_TYPE_USER,
		   G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_USER_ASSIGNMENT] = g_param_spec_object("user_assignment",
		_("User Assignment"), _("A user assignment object of the associated user."),
		HARVEST_TYPE_PROJECT_USER_ASSIGNMENT,
		G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_USER_ASSIGNMENT]
		= g_param_spec_object("user_assignment", _("User Assignment"),
			_("A user assignment object of the associated user."), HARVEST_TYPE_USER_ASSIGNMENT,
			G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_CLIENT]	 = g_param_spec_object("client", _("Client"),
	obj_properties[PROP_CLIENT]			 = g_param_spec_object("client", _("Client"),
		 _("An object containing the id and name of the associated client."), HARVEST_TYPE_CLIENT,
		 G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_PROJECT] = g_param_spec_object("project", _("Project"),
	obj_properties[PROP_PROJECT]		 = g_param_spec_object("project", _("Project"),
		_("An object containing the id and name of the associated project."), HARVEST_TYPE_PROJECT,
		G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_TASK]	 = g_param_spec_object("task", _("Task"),
	obj_properties[PROP_TASK]			 = g_param_spec_object("task", _("Task"),
		   _("An object containing the id and name of the associated task."), HARVEST_TYPE_TASK,
		   G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_TASK_ASSIGNMEMT]
		= g_param_spec_object("task_assignment", _("Task Assignment"),
			_("A task assignment object of the associated task."), HARVEST_TYPE_TASK_ASSIGNMENT,
			G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_INVOICE] = g_param_spec_object("invoice", _("Invoice"),
	obj_properties[PROP_TASK_ASSIGNMEMT] = g_param_spec_object("task_assignment",
		_("Task Assignment"), _("A task assignment object of the associated task."),
		HARVEST_TYPE_PROJECT_TASK_ASSIGNMENT,
		G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_INVOICE]		 = g_param_spec_object("invoice", _("Invoice"),
		_("Once the time entry has been invoiced, this field will include the associated invoice’s "
		  "id and number."),
		HARVEST_TYPE_INVOICE, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_HOURS]	 = g_param_spec_double("hours", _("Hours"),
	obj_properties[PROP_HOURS]			 = g_param_spec_double("hours", _("Hours"),
		  _("Number of (decimal time) hours tracked in this time entry."), 0, DBL_MAX, 0,
		  G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_NOTES]

M harvest-glib/time-entry/requests/harvest-late-request.c => harvest-glib/time-entry/requests/harvest-late-request.c +4 -0
@@ 109,9 109,13 @@ harvest_late_request_set_property(GObject *obj, guint prop_id, const GValue *val

	switch (prop_id) {
	case PROP_USER_ID:
		if (self->user_id != NULL)
			g_variant_unref(self->user_id);
		self->user_id = g_value_dup_variant(val);
		break;
	case PROP_CLIENT_ID:
		if (self->client_id != NULL)
			g_variant_unref(self->client_id);
		self->client_id = g_value_dup_variant(val);
		break;
	case PROP_PROJECT_ID:

A harvest-glib/user/harvest-user-project-assignment.c => harvest-glib/user/harvest-user-project-assignment.c +300 -0
@@ 0,0 1,300 @@
#include "harvest-glib/config.h"

#include <float.h>
#include <limits.h>

#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <json-glib/json-glib.h>

#include "harvest-glib/client/harvest-client.h"
#include "harvest-glib/project/harvest-project-task-assignment.h"
#include "harvest-glib/project/harvest-project.h"
#include "harvest-glib/user/harvest-user-project-assignment.h"
#include "harvest-glib/user/harvest-user.h"

struct _HarvestUserProjectAssignment
{
	GObject parent_instance;

	int id;
	gboolean is_active : 1;
	gboolean is_project_manager : 1;
	gboolean use_default_rates : 1;
	double hourly_rate;
	double budget;
	GDateTime *created_at;
	GDateTime *updated_at;
	HarvestProject *project;
	HarvestClient *client;
	GPtrArray *task_assignments; // HarvestProjectTaskAssignment
};

static void harvest_user_project_assignment_json_serializable_init(JsonSerializableIface *iface);

G_DEFINE_TYPE_WITH_CODE(HarvestUserProjectAssignment, harvest_user_project_assignment,
	G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE(
		JSON_TYPE_SERIALIZABLE, harvest_user_project_assignment_json_serializable_init))

enum HarvestUserProjectAssignmentProps
{
	PROP_0,
	PROP_ID,
	PROP_IS_ACTIVE,
	PROP_IS_PROJECT_MANAGER,
	PROP_USE_DEFAULT_RATES,
	PROP_HOURLY_RATE,
	PROP_BUDGET,
	PROP_CREATED_AT,
	PROP_UPDATED_AT,
	PROP_PROJECT,
	PROP_CLIENT,
	PROP_TASK_ASSIGNMENTS,
	N_PROPS,
};

static GParamSpec *obj_properties[N_PROPS];

static void
task_assignments_for_each(
	G_GNUC_UNUSED JsonArray *array, G_GNUC_UNUSED guint index, JsonNode *node, gpointer user_data)
{
	g_ptr_array_add(
		user_data, json_gobject_deserialize(HARVEST_TYPE_PROJECT_TASK_ASSIGNMENT, node));
}

static gboolean
harvest_user_project_assignment_deserialize_property(JsonSerializable *serializable,
	const gchar *prop_name, GValue *val, GParamSpec *pspec, JsonNode *prop_node)
{
	if (g_strcmp0(prop_name, "created_at") == 0 || g_strcmp0(prop_name, "updated_at") == 0) {
		const GDateTime *dt = g_date_time_new_from_iso8601(json_node_get_string(prop_node), NULL);
		g_value_set_boxed(val, dt);

		return TRUE;
	} else if (g_strcmp0(prop_name, "project") == 0) {
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_PROJECT, prop_node);
		g_value_set_object(val, obj);

		return TRUE;
	} else if (g_strcmp0(prop_name, "client") == 0) {
		GObject *obj = json_gobject_deserialize(HARVEST_TYPE_CLIENT, prop_node);
		g_value_set_object(val, obj);

		return TRUE;
	} else if (g_strcmp0(prop_name, "task_assignments") == 0) {
		JsonArray *arr				= json_node_get_array(prop_node);
		const guint length			= json_array_get_length(arr);
		GPtrArray *task_assignments = g_ptr_array_sized_new(length);
		json_array_foreach_element(arr, task_assignments_for_each, task_assignments);
		g_value_set_boxed(val, task_assignments);

		return TRUE;
	}

	return json_serializable_default_deserialize_property(
		serializable, prop_name, val, pspec, prop_node);
}

static void
harvest_user_project_assignment_json_serializable_init(JsonSerializableIface *iface)
{
	iface->deserialize_property = harvest_user_project_assignment_deserialize_property;
}

static void
harvest_user_project_assignment_finalize(GObject *obj)
{
	HarvestUserProjectAssignment *self = HARVEST_USER_PROJECT_ASSIGNMENT(obj);

	if (self->created_at != NULL)
		g_date_time_unref(self->created_at);
	if (self->updated_at != NULL)
		g_date_time_unref(self->updated_at);
	if (self->project != NULL)
		g_object_unref(self->project);
	if (self->client != NULL)
		g_object_unref(self->client);
	if (self->task_assignments != NULL) {
		/**
		 * Always remove the last one so that the array does not need to be resized. If we
		 * always remove the last one in conjunction with g_ptr_array_remove_index_fast, then
		 * the reverse order of the array should be preserved and we will be able to iterate
		 * linearly.
		 */
		for (int i = self->task_assignments->len - 1; i >= 0; i--) {
			HarvestProjectTaskAssignment *task_assignment = HARVEST_PROJECT_TASK_ASSIGNMENT(
				g_ptr_array_remove_index_fast(self->task_assignments, i));
			if (task_assignment != NULL)
				g_object_unref(task_assignment);
		}
		g_ptr_array_unref(self->task_assignments);
	}

	G_OBJECT_CLASS(harvest_user_project_assignment_parent_class)->finalize(obj);
}

static void
harvest_user_project_assignment_get_property(
	GObject *obj, guint prop_id, GValue *val, GParamSpec *pspec)
{
	HarvestUserProjectAssignment *self = HARVEST_USER_PROJECT_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:
		g_value_set_int(val, self->id);
		break;
	case PROP_IS_ACTIVE:
		g_value_set_boolean(val, self->is_active);
		break;
	case PROP_IS_PROJECT_MANAGER:
		g_value_set_boolean(val, self->is_project_manager);
		break;
	case PROP_USE_DEFAULT_RATES:
		g_value_set_boolean(val, self->use_default_rates);
		break;
	case PROP_HOURLY_RATE:
		g_value_set_double(val, self->hourly_rate);
		break;
	case PROP_BUDGET:
		g_value_set_double(val, self->budget);
		break;
	case PROP_CREATED_AT:
		g_value_set_boxed(val, self->created_at);
		break;
	case PROP_UPDATED_AT:
		g_value_set_boxed(val, self->updated_at);
		break;
	case PROP_PROJECT:
		g_value_set_object(val, self->project);
		break;
	case PROP_CLIENT:
		g_value_set_object(val, self->client);
		break;
	case PROP_TASK_ASSIGNMENTS:
		g_value_set_boxed(val, self->task_assignments);
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
	}
}

static void
harvest_user_project_assignment_set_property(
	GObject *obj, guint prop_id, const GValue *val, GParamSpec *pspec)
{
	HarvestUserProjectAssignment *self = HARVEST_USER_PROJECT_ASSIGNMENT(obj);

	switch (prop_id) {
	case PROP_ID:
		self->id = g_value_get_int(val);
		break;
	case PROP_IS_ACTIVE:
		self->is_active = g_value_get_boolean(val);
		break;
	case PROP_IS_PROJECT_MANAGER:
		self->is_project_manager = g_value_get_boolean(val);
		break;
	case PROP_USE_DEFAULT_RATES:
		self->use_default_rates = g_value_get_boolean(val);
		break;
	case PROP_HOURLY_RATE:
		self->hourly_rate = g_value_get_double(val);
		break;
	case PROP_BUDGET:
		self->budget = g_value_get_double(val);
		break;
	case PROP_CREATED_AT:
		if (self->created_at != NULL)
			g_date_time_unref(self->created_at);
		self->created_at = g_value_dup_boxed(val);
		break;
	case PROP_UPDATED_AT:
		if (self->updated_at != NULL)
			g_date_time_unref(self->updated_at);
		self->updated_at = g_value_dup_boxed(val);
		break;
	case PROP_PROJECT:
		if (self->project != NULL)
			g_object_unref(self->project);
		self->project = g_value_dup_object(val);
		break;
	case PROP_CLIENT:
		if (self->client != NULL)
			g_object_unref(self->client);
		self->client = g_value_dup_object(val);
		break;
	case PROP_TASK_ASSIGNMENTS:
		if (self->task_assignments != NULL) {
			/**
			 * Always remove the last one so that the array does not need to be resized. If we
			 * always remove the last one in conjunction with g_ptr_array_remove_index_fast, then
			 * the reverse order of the array should be preserved and we will be able to iterate
			 * linearly.
			 */
			for (int i = self->task_assignments->len - 1; i >= 0; i--) {
				HarvestProjectTaskAssignment *task_assignment = HARVEST_PROJECT_TASK_ASSIGNMENT(
					g_ptr_array_remove_index_fast(self->task_assignments, i));
				if (task_assignment != NULL)
					g_object_unref(task_assignment);
			}
			g_ptr_array_unref(self->task_assignments);
		}
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
	}
}

static void
harvest_user_project_assignment_class_init(HarvestUserProjectAssignmentClass *klass)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);

	obj_class->finalize		= harvest_user_project_assignment_finalize;
	obj_class->get_property = harvest_user_project_assignment_get_property;
	obj_class->set_property = harvest_user_project_assignment_set_property;

	obj_properties[PROP_ID]
		= g_param_spec_int("id", _("ID"), _("Unique ID for the project assignment."), 0, INT_MAX, 0,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_IS_ACTIVE] = g_param_spec_boolean("is_active", _("Is Active"),
		_("Whether the project assignment is active or archived."), FALSE,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_IS_PROJECT_MANAGER]
		= g_param_spec_boolean("is-project-manager", _("Is Project Manager"),
			_("Determines if the user has project manager permissions for the project."), FALSE,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_USE_DEFAULT_RATES] = g_param_spec_boolean("use_default_rates",
		_("Use Default Rates"),
		_("Determines which billable rate(s) will be used on the project for this user when "
		  "bill_by is People. When true, the project will use the user’s default billable rates. "
		  "When false, the project will use the custom rate defined on this user assignment."),
		FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_HOURLY_RATE]	   = g_param_spec_double("hourly-rate", _("Hourly Rate"),
		  _("Custom rate used when the project’s bill_by is People and use_default_rates is false."),
		  0, DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_BUDGET]			   = g_param_spec_double("budget", _("Budget"),
		   _("Budget used when the project’s budget_by is person."), 0, DBL_MAX, 0,
		   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_CREATED_AT]		   = g_param_spec_boxed("created-at", _("Created At"),
		   _("Date and time the project assignment was created."), G_TYPE_DATE_TIME,
		   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_UPDATED_AT]		   = g_param_spec_boxed("updated-at", _("Updated At"),
		   _("Date and time the project assignment was last updated."), G_TYPE_DATE_TIME,
		   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_PROJECT]		   = g_param_spec_object("project", _("Project"),
		  _("An object containing the assigned project id, name, and code."), HARVEST_TYPE_PROJECT,
		  G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_CLIENT]			   = g_param_spec_object("client", _("Client"),
		   _("An object containing the project’s client id and name."), HARVEST_TYPE_CLIENT,
		   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_TASK_ASSIGNMENTS]  = g_param_spec_boxed("task-assignments",
		 _("Task Assignments"), _("Array of task assignment objects associated with the project."),
		 G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, N_PROPS, obj_properties);
}

static void
harvest_user_project_assignment_init(G_GNUC_UNUSED HarvestUserProjectAssignment *self)
{}

A harvest-glib/user/harvest-user-project-assignment.h => harvest-glib/user/harvest-user-project-assignment.h +15 -0
@@ 0,0 1,15 @@
#pragma once

#if !defined(__HARVEST_HEADER_INTERNAL__) && !defined(__HARVEST_COMPILATION__)
#	error "Only <harvest-glib/harvest.h> can be included directly."
#endif

#include <glib-object.h>

G_BEGIN_DECLS

#define HARVEST_TYPE_USER_PROJECT_ASSIGNMENT (harvest_user_project_assignment_get_type())
G_DECLARE_FINAL_TYPE(HarvestUserProjectAssignment, harvest_user_project_assignment, HARVEST,
	USER_PROJECT_ASSIGNMENT, GObject)

G_END_DECLS

M harvest-glib/user/meson.build => harvest-glib/user/meson.build +1 -0
@@ 1,4 1,5 @@
component_public_headers = [
	'harvest-user-project-assignment.h',
	'harvest-user.h',
]