3883a8551f
set this flag instead of deleting project, so we do not lose other info about it. can delete manually if truly unwanted
306 lines
9.4 KiB
Python
306 lines
9.4 KiB
Python
# -*- coding: utf-8; -*-
|
|
################################################################################
|
|
#
|
|
# Rattail -- Retail Software Framework
|
|
# Copyright © 2010-2022 Lance Edgar
|
|
#
|
|
# This file is part of Rattail.
|
|
#
|
|
# Rattail 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 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# Rattail 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
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Harvest "cache" data models
|
|
"""
|
|
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
|
|
from rattail.db import model
|
|
from rattail.db.util import normalize_full_name
|
|
|
|
|
|
class HarvestUser(model.Base):
|
|
"""
|
|
Represents a user record in Harvest.
|
|
|
|
https://help.getharvest.com/api-v2/users-api/users/users/#the-user-object
|
|
"""
|
|
__tablename__ = 'harvest_user'
|
|
__table_args__ = (
|
|
sa.UniqueConstraint('id', name='harvest_user_uq_id'),
|
|
)
|
|
__versioned__ = {}
|
|
|
|
uuid = model.uuid_column()
|
|
|
|
id = sa.Column(sa.Integer(), nullable=False)
|
|
|
|
first_name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
last_name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
email = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
telephone = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
timezone = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
has_access_to_all_future_projects = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_contractor = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_admin = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_project_manager = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
can_see_rates = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
can_create_projects = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
can_create_invoices = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_active = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
weekly_capacity = sa.Column(sa.Integer(), nullable=True)
|
|
|
|
default_hourly_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
cost_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
# TODO
|
|
# roles = sa.Column(sa.Text(), nullable=True)
|
|
|
|
avatar_url = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
created_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
def __str__(self):
|
|
return normalize_full_name(self.first_name, self.last_name)
|
|
|
|
|
|
class HarvestClient(model.Base):
|
|
"""
|
|
Represents a client record in Harvest.
|
|
|
|
https://help.getharvest.com/api-v2/clients-api/clients/clients/#the-client-object
|
|
"""
|
|
__tablename__ = 'harvest_client'
|
|
__table_args__ = (
|
|
sa.UniqueConstraint('id', name='harvest_client_uq_id'),
|
|
)
|
|
__versioned__ = {}
|
|
|
|
uuid = model.uuid_column()
|
|
|
|
id = sa.Column(sa.Integer(), nullable=False)
|
|
|
|
name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
is_active = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
address = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
currency = sa.Column(sa.String(length=100), nullable=True)
|
|
|
|
created_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
def __str__(self):
|
|
return self.name or ''
|
|
|
|
|
|
class HarvestProject(model.Base):
|
|
"""
|
|
Represents a project record in Harvest.
|
|
|
|
https://help.getharvest.com/api-v2/projects-api/projects/projects/#the-project-object
|
|
"""
|
|
__tablename__ = 'harvest_project'
|
|
__table_args__ = (
|
|
sa.UniqueConstraint('id', name='harvest_project_uq_id'),
|
|
sa.ForeignKeyConstraint(['client_id'], ['harvest_client.id'], name='harvest_project_fk_client'),
|
|
)
|
|
__versioned__ = {'exclude': ['over_budget_notification_date']}
|
|
|
|
uuid = model.uuid_column()
|
|
|
|
id = sa.Column(sa.Integer(), nullable=False)
|
|
|
|
client_id = sa.Column(sa.Integer(), nullable=True) # TODO: should not allow null?
|
|
client = orm.relationship(HarvestClient, backref=orm.backref('projects'))
|
|
|
|
name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
code = sa.Column(sa.String(length=100), nullable=True)
|
|
|
|
is_active = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_billable = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_fixed_fee = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
bill_by = sa.Column(sa.String(length=100), nullable=True)
|
|
|
|
hourly_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
budget = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
budget_by = sa.Column(sa.String(length=100), nullable=True)
|
|
|
|
budget_is_monthly = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
notify_when_over_budget = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
over_budget_notification_percentage = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
over_budget_notification_date = sa.Column(sa.Date(), nullable=True)
|
|
|
|
show_budget_to_all = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
cost_budget = sa.Column(sa.Numeric(precision=9, scale=2), nullable=True)
|
|
|
|
cost_budget_include_expenses = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
fee = sa.Column(sa.Numeric(precision=8, scale=2), nullable=True)
|
|
|
|
notes = sa.Column(sa.Text(), nullable=True)
|
|
|
|
starts_on = sa.Column(sa.Date(), nullable=True)
|
|
|
|
ends_on = sa.Column(sa.Date(), nullable=True)
|
|
|
|
created_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
deleted = sa.Column(sa.Boolean(), nullable=True, doc="""
|
|
Flag indicating the record has been deleted in Harvest.
|
|
""")
|
|
|
|
def __str__(self):
|
|
return self.name or ''
|
|
|
|
|
|
class HarvestTask(model.Base):
|
|
"""
|
|
Represents a task record in Harvest.
|
|
|
|
https://help.getharvest.com/api-v2/tasks-api/tasks/tasks/#the-task-object
|
|
"""
|
|
__tablename__ = 'harvest_task'
|
|
__table_args__ = (
|
|
sa.UniqueConstraint('id', name='harvest_task_uq_id'),
|
|
)
|
|
__versioned__ = {}
|
|
|
|
uuid = model.uuid_column()
|
|
|
|
id = sa.Column(sa.Integer(), nullable=False)
|
|
|
|
name = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
billable_by_default = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
default_hourly_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
is_default = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_active = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
created_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
def __str__(self):
|
|
return self.name or ''
|
|
|
|
|
|
class HarvestTimeEntry(model.Base):
|
|
"""
|
|
Represents a time entry record in Harvest.
|
|
|
|
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#the-time-entry-object
|
|
"""
|
|
__tablename__ = 'harvest_time_entry'
|
|
__table_args__ = (
|
|
sa.UniqueConstraint('id', name='harvest_time_entry_uq_id'),
|
|
sa.ForeignKeyConstraint(['user_id'], ['harvest_user.id'], name='harvest_time_entry_fk_user'),
|
|
sa.ForeignKeyConstraint(['client_id'], ['harvest_client.id'], name='harvest_time_entry_fk_client'),
|
|
sa.ForeignKeyConstraint(['project_id'], ['harvest_project.id'], name='harvest_time_entry_fk_project'),
|
|
sa.ForeignKeyConstraint(['task_id'], ['harvest_task.id'], name='harvest_time_entry_fk_task'),
|
|
)
|
|
__versioned__ = {}
|
|
model_title_plural = "Harvest Time Entries"
|
|
|
|
uuid = model.uuid_column()
|
|
|
|
id = sa.Column(sa.Integer(), nullable=False)
|
|
|
|
spent_date = sa.Column(sa.Date(), nullable=True)
|
|
|
|
user_id = sa.Column(sa.Integer(), nullable=True)
|
|
user = orm.relationship(HarvestUser, backref=orm.backref('time_entries'))
|
|
|
|
client_id = sa.Column(sa.Integer(), nullable=True)
|
|
client = orm.relationship(HarvestClient, backref=orm.backref('time_entries'))
|
|
|
|
project_id = sa.Column(sa.Integer(), nullable=True)
|
|
project = orm.relationship(HarvestProject, backref=orm.backref('time_entries'))
|
|
|
|
task_id = sa.Column(sa.Integer(), nullable=True)
|
|
task = orm.relationship(HarvestTask, backref=orm.backref('time_entries'))
|
|
|
|
invoice_id = sa.Column(sa.Integer(), nullable=True)
|
|
|
|
hours = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
notes = sa.Column(sa.Text(), nullable=True)
|
|
|
|
is_locked = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
locked_reason = sa.Column(sa.String(length=255), nullable=True)
|
|
|
|
is_closed = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
is_billed = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
timer_started_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
started_time = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
ended_time = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
is_running = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
billable = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
budgeted = sa.Column(sa.Boolean(), nullable=True)
|
|
|
|
billable_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
cost_rate = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
|
|
|
created_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
|
|
|
def __str__(self):
|
|
return str(self.spent_date or '')
|