Compare commits
No commits in common. "master" and "v0.1.1" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1 @@
|
||||||
*~
|
|
||||||
*.pyc
|
|
||||||
dist/
|
|
||||||
rattail_harvest.egg-info/
|
rattail_harvest.egg-info/
|
||||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -5,36 +5,6 @@ All notable changes to rattail-harvest will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v0.3.2 (2024-08-18)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- avoid deprecated base class for config extension
|
|
||||||
|
|
||||||
## v0.3.1 (2024-07-01)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- remove legacy command definitions
|
|
||||||
|
|
||||||
## v0.3.0 (2024-06-10)
|
|
||||||
|
|
||||||
### Feat
|
|
||||||
|
|
||||||
- switch from setup.cfg to pyproject.toml + hatchling
|
|
||||||
|
|
||||||
## [0.2.1] - 2024-06-06
|
|
||||||
### Changed
|
|
||||||
- Add alembic scripts to project manifest.
|
|
||||||
|
|
||||||
## [0.2.0] - 2024-06-06
|
|
||||||
### Changed
|
|
||||||
- Add typer equivalents for `rattail` commands.
|
|
||||||
|
|
||||||
## [0.1.2] - 2023-11-18
|
|
||||||
### Changed
|
|
||||||
- Catch-up release, with various schema changes etc.
|
|
||||||
|
|
||||||
## [0.1.1] - 2022-01-29
|
## [0.1.1] - 2022-01-29
|
||||||
### Added
|
### Added
|
||||||
- Initial version.
|
- Initial version.
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
include *.md
|
include *.md
|
||||||
include *.rst
|
include *.rst
|
||||||
|
|
||||||
recursive-include rattail_harvest/db/alembic *.mako
|
recursive-include rattail_harvest/db/alembic *.mako
|
||||||
recursive-include rattail_harvest/db/alembic *.py
|
|
||||||
|
|
11
README.md
11
README.md
|
@ -1,11 +0,0 @@
|
||||||
|
|
||||||
# rattail-harvest
|
|
||||||
|
|
||||||
Rattail is a retail software framework, released under the GNU General
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
This package contains software interfaces for
|
|
||||||
[Harvest](https://www.getharvest.com/).
|
|
||||||
|
|
||||||
Please see the [Rattail Project](https://rattailproject.org/) for more
|
|
||||||
information.
|
|
14
README.rst
Normal file
14
README.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
rattail-harvest
|
||||||
|
===============
|
||||||
|
|
||||||
|
Rattail is a retail software framework, released under the GNU General
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
This package contains software interfaces for `Harvest`_.
|
||||||
|
|
||||||
|
.. _`Harvest`: https://www.getharvest.com/
|
||||||
|
|
||||||
|
Please see the `Rattail Project`_ for more information.
|
||||||
|
|
||||||
|
.. _`Rattail Project`: https://rattailproject.org/
|
|
@ -1,53 +0,0 @@
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["hatchling"]
|
|
||||||
build-backend = "hatchling.build"
|
|
||||||
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "rattail-harvest"
|
|
||||||
version = "0.3.2"
|
|
||||||
description = "Rattail integration package for Harvest"
|
|
||||||
readme = "README.md"
|
|
||||||
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
|
|
||||||
license = {text = "GNU GPL v3+"}
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Environment :: Console",
|
|
||||||
"Intended Audience :: Developers",
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
||||||
"Natural Language :: English",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Topic :: Office/Business",
|
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
||||||
]
|
|
||||||
dependencies = [
|
|
||||||
"invoke",
|
|
||||||
"rattail[db]",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
Homepage = "https://rattailproject.org"
|
|
||||||
Repository = "https://forgejo.wuttaproject.org/rattail/rattail-harvest"
|
|
||||||
Changelog = "https://forgejo.wuttaproject.org/rattail/rattail-harvest/src/branch/master/CHANGELOG.md"
|
|
||||||
|
|
||||||
|
|
||||||
[project.entry-points."rattail.typer_imports"]
|
|
||||||
rattail_harvest = "rattail_harvest.commands"
|
|
||||||
|
|
||||||
|
|
||||||
[project.entry-points."rattail.config.extensions"]
|
|
||||||
rattail_harvest = "rattail_harvest.config:RattailHarvestExtension"
|
|
||||||
|
|
||||||
|
|
||||||
[project.entry-points."rattail.importing"]
|
|
||||||
"to_rattail.from_harvest.import" = "rattail_harvest.importing.harvest:FromHarvestToRattail"
|
|
||||||
|
|
||||||
|
|
||||||
[tool.commitizen]
|
|
||||||
version_provider = "pep621"
|
|
||||||
tag_format = "v$version"
|
|
||||||
update_changelog_on_bump = true
|
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
from importlib.metadata import version
|
__version__ = '0.1.1'
|
||||||
|
|
||||||
|
|
||||||
__version__ = version('rattail-harvest')
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2024 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,25 +24,13 @@
|
||||||
rattail-harvest commands
|
rattail-harvest commands
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import typer
|
from rattail import commands
|
||||||
|
|
||||||
from rattail.commands import rattail_typer
|
|
||||||
from rattail.commands.typer import importer_command, typer_get_runas_user
|
|
||||||
from rattail.commands.importing import ImportCommandHandler
|
|
||||||
|
|
||||||
|
|
||||||
@rattail_typer.command()
|
class ImportHarvest(commands.ImportSubcommand):
|
||||||
@importer_command
|
|
||||||
def import_harvest(
|
|
||||||
ctx: typer.Context,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Import data to Rattail, from Harvest API
|
Import data to Rattail, from Harvest API
|
||||||
"""
|
"""
|
||||||
config = ctx.parent.rattail_config
|
name = 'import-harvest'
|
||||||
progress = ctx.parent.rattail_progress
|
description = __doc__.strip()
|
||||||
handler = ImportCommandHandler(
|
handler_key = 'to_rattail.from_harvest.import'
|
||||||
config, import_handler_key='to_rattail.from_harvest.import')
|
|
||||||
kwargs['user'] = typer_get_runas_user(ctx)
|
|
||||||
handler.run(kwargs, progress=progress)
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2024 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/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Config Extension
|
|
||||||
"""
|
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfigExtension
|
|
||||||
|
|
||||||
|
|
||||||
class RattailHarvestExtension(WuttaConfigExtension):
|
|
||||||
"""
|
|
||||||
Config extension for rattail-harvest.
|
|
||||||
"""
|
|
||||||
key = 'rattail_harvest'
|
|
||||||
|
|
||||||
def configure(self, config):
|
|
||||||
|
|
||||||
# rattail import-harvest
|
|
||||||
config.setdefault('rattail.importing', 'to_rattail.from_harvest.import.default_handler',
|
|
||||||
'rattail_harvest.importing.harvest:FromHarvestToRattail')
|
|
||||||
config.setdefault('rattail.importing', 'to_rattail.from_harvest.import.default_cmd',
|
|
||||||
'rattail import-harvest')
|
|
|
@ -1,179 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
"""rename cache tables
|
|
||||||
|
|
||||||
Revision ID: 53c066772ad5
|
|
||||||
Revises: f2a1650e7fbc
|
|
||||||
Create Date: 2023-10-04 15:19:03.857323
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '53c066772ad5'
|
|
||||||
down_revision = 'f2a1650e7fbc'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import rattail.db.types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# drop all constraints
|
|
||||||
##############################
|
|
||||||
|
|
||||||
# harvest_time_entry
|
|
||||||
op.drop_constraint('harvest_time_entry_fk_user', 'harvest_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_time_entry_fk_client', 'harvest_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_time_entry_fk_project', 'harvest_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_time_entry_fk_task', 'harvest_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_time_entry_uq_id', 'harvest_time_entry', type_='unique')
|
|
||||||
|
|
||||||
# harvest_task
|
|
||||||
op.drop_constraint('harvest_task_uq_id', 'harvest_task', type_='unique')
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.drop_constraint('harvest_project_fk_client', 'harvest_project', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_project_uq_id', 'harvest_project', type_='unique')
|
|
||||||
|
|
||||||
# harvest_client
|
|
||||||
op.drop_constraint('harvest_client_uq_id', 'harvest_client', type_='unique')
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.drop_constraint('harvest_user_fk_person', 'harvest_user', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_user_uq_id', 'harvest_user', type_='unique')
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# rename all tables
|
|
||||||
##############################
|
|
||||||
|
|
||||||
op.rename_table('harvest_user', 'harvest_cache_user')
|
|
||||||
op.rename_table('harvest_user_version', 'harvest_cache_user_version')
|
|
||||||
op.rename_table('harvest_client', 'harvest_cache_client')
|
|
||||||
op.rename_table('harvest_client_version', 'harvest_cache_client_version')
|
|
||||||
op.rename_table('harvest_project', 'harvest_cache_project')
|
|
||||||
op.rename_table('harvest_project_version', 'harvest_cache_project_version')
|
|
||||||
op.rename_table('harvest_task', 'harvest_cache_task')
|
|
||||||
op.rename_table('harvest_task_version', 'harvest_cache_task_version')
|
|
||||||
op.rename_table('harvest_time_entry', 'harvest_cache_time_entry')
|
|
||||||
op.rename_table('harvest_time_entry_version', 'harvest_cache_time_entry_version')
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# re-create all constraints
|
|
||||||
##############################
|
|
||||||
|
|
||||||
# harvest_cache_user
|
|
||||||
op.create_foreign_key('harvest_cache_user_fk_person',
|
|
||||||
'harvest_cache_user', 'person',
|
|
||||||
['person_uuid'], ['uuid'])
|
|
||||||
op.create_unique_constraint('harvest_cache_user_uq_id', 'harvest_cache_user', ['id'])
|
|
||||||
|
|
||||||
# harvest_cache_client
|
|
||||||
op.create_unique_constraint('harvest_cache_client_uq_id', 'harvest_cache_client', ['id'])
|
|
||||||
|
|
||||||
# harvest_cache_project
|
|
||||||
op.create_foreign_key('harvest_cache_project_fk_client',
|
|
||||||
'harvest_cache_project', 'harvest_cache_client',
|
|
||||||
['client_id'], ['id'])
|
|
||||||
op.create_unique_constraint('harvest_cache_project_uq_id', 'harvest_cache_project', ['id'])
|
|
||||||
|
|
||||||
# harvest_cache_task
|
|
||||||
op.create_unique_constraint('harvest_cache_task_uq_id', 'harvest_cache_task', ['id'])
|
|
||||||
|
|
||||||
# harvest_cache_time_entry
|
|
||||||
op.create_foreign_key('harvest_cache_time_entry_fk_user',
|
|
||||||
'harvest_cache_time_entry', 'harvest_cache_user',
|
|
||||||
['user_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_cache_time_entry_fk_client',
|
|
||||||
'harvest_cache_time_entry', 'harvest_cache_client',
|
|
||||||
['client_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_cache_time_entry_fk_project',
|
|
||||||
'harvest_cache_time_entry', 'harvest_cache_project',
|
|
||||||
['project_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_cache_time_entry_fk_task',
|
|
||||||
'harvest_cache_time_entry', 'harvest_cache_task',
|
|
||||||
['task_id'], ['id'])
|
|
||||||
op.create_unique_constraint('harvest_cache_time_entry_uq_id', 'harvest_cache_time_entry', ['id'])
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# drop all constraints
|
|
||||||
##############################
|
|
||||||
|
|
||||||
# harvest_cache_time_entry
|
|
||||||
op.drop_constraint('harvest_cache_time_entry_fk_user', 'harvest_cache_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_time_entry_fk_client', 'harvest_cache_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_time_entry_fk_project', 'harvest_cache_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_time_entry_fk_task', 'harvest_cache_time_entry', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_time_entry_uq_id', 'harvest_cache_time_entry', type_='unique')
|
|
||||||
|
|
||||||
# harvest_cache_task
|
|
||||||
op.drop_constraint('harvest_cache_task_uq_id', 'harvest_cache_task', type_='unique')
|
|
||||||
|
|
||||||
# harvest_cache_project
|
|
||||||
op.drop_constraint('harvest_cache_project_fk_client', 'harvest_cache_project', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_project_uq_id', 'harvest_cache_project', type_='unique')
|
|
||||||
|
|
||||||
# harvest_cache_client
|
|
||||||
op.drop_constraint('harvest_cache_client_uq_id', 'harvest_cache_client', type_='unique')
|
|
||||||
|
|
||||||
# harvest_cache_user
|
|
||||||
op.drop_constraint('harvest_cache_user_fk_person', 'harvest_cache_user', type_='foreignkey')
|
|
||||||
op.drop_constraint('harvest_cache_user_uq_id', 'harvest_cache_user', type_='unique')
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# rename all tables
|
|
||||||
##############################
|
|
||||||
|
|
||||||
op.rename_table('harvest_cache_user', 'harvest_user')
|
|
||||||
op.rename_table('harvest_cache_user_version', 'harvest_user_version')
|
|
||||||
op.rename_table('harvest_cache_client', 'harvest_client')
|
|
||||||
op.rename_table('harvest_cache_client_version', 'harvest_client_version')
|
|
||||||
op.rename_table('harvest_cache_project', 'harvest_project')
|
|
||||||
op.rename_table('harvest_cache_project_version', 'harvest_project_version')
|
|
||||||
op.rename_table('harvest_cache_task', 'harvest_task')
|
|
||||||
op.rename_table('harvest_cache_task_version', 'harvest_task_version')
|
|
||||||
op.rename_table('harvest_cache_time_entry', 'harvest_time_entry')
|
|
||||||
op.rename_table('harvest_cache_time_entry_version', 'harvest_time_entry_version')
|
|
||||||
|
|
||||||
##############################
|
|
||||||
# re-create all constraints
|
|
||||||
##############################
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.create_foreign_key('harvest_user_fk_person',
|
|
||||||
'harvest_user', 'person',
|
|
||||||
['person_uuid'], ['uuid'])
|
|
||||||
op.create_unique_constraint('harvest_user_uq_id', 'harvest_user', ['id'])
|
|
||||||
|
|
||||||
# harvest_client
|
|
||||||
op.create_unique_constraint('harvest_client_uq_id', 'harvest_client', ['id'])
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.create_foreign_key('harvest_project_fk_client',
|
|
||||||
'harvest_project', 'harvest_client',
|
|
||||||
['client_id'], ['id'])
|
|
||||||
op.create_unique_constraint('harvest_project_uq_id', 'harvest_project', ['id'])
|
|
||||||
|
|
||||||
# harvest_cache_task
|
|
||||||
op.create_unique_constraint('harvest_task_uq_id', 'harvest_task', ['id'])
|
|
||||||
|
|
||||||
# harvest_time_entry
|
|
||||||
op.create_foreign_key('harvest_time_entry_fk_user',
|
|
||||||
'harvest_time_entry', 'harvest_user',
|
|
||||||
['user_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_time_entry_fk_client',
|
|
||||||
'harvest_time_entry', 'harvest_client',
|
|
||||||
['client_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_time_entry_fk_project',
|
|
||||||
'harvest_time_entry', 'harvest_project',
|
|
||||||
['project_id'], ['id'])
|
|
||||||
op.create_foreign_key('harvest_time_entry_fk_task',
|
|
||||||
'harvest_time_entry', 'harvest_task',
|
|
||||||
['task_id'], ['id'])
|
|
||||||
op.create_unique_constraint('harvest_time_entry_uq_id', 'harvest_time_entry', ['id'])
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
"""add project.deleted
|
|
||||||
|
|
||||||
Revision ID: 5505c0e60d28
|
|
||||||
Revises: d59ce24c2f9f
|
|
||||||
Create Date: 2022-01-30 12:08:04.338229
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '5505c0e60d28'
|
|
||||||
down_revision = 'd59ce24c2f9f'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import rattail.db.types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.add_column('harvest_project', sa.Column('deleted', sa.Boolean(), nullable=True))
|
|
||||||
op.add_column('harvest_project_version', sa.Column('deleted', sa.Boolean(), autoincrement=False, nullable=True))
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.drop_column('harvest_project_version', 'deleted')
|
|
||||||
op.drop_column('harvest_project', 'deleted')
|
|
|
@ -1,35 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
"""add harvest_user.person
|
|
||||||
|
|
||||||
Revision ID: 6bc1cb21d920
|
|
||||||
Revises: 5505c0e60d28
|
|
||||||
Create Date: 2022-01-30 16:49:32.271745
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '6bc1cb21d920'
|
|
||||||
down_revision = '5505c0e60d28'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import rattail.db.types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.add_column('harvest_user', sa.Column('person_uuid', sa.String(length=32), nullable=True))
|
|
||||||
op.create_foreign_key('harvest_user_fk_person', 'harvest_user', 'person', ['person_uuid'], ['uuid'])
|
|
||||||
op.add_column('harvest_user_version', sa.Column('person_uuid', sa.String(length=32), autoincrement=False, nullable=True))
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.drop_column('harvest_user_version', 'person_uuid')
|
|
||||||
op.drop_constraint('harvest_user_fk_person', 'harvest_user', type_='foreignkey')
|
|
||||||
op.drop_column('harvest_user', 'person_uuid')
|
|
|
@ -1,105 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
"""fix indeces
|
|
||||||
|
|
||||||
Revision ID: a1cf300fb371
|
|
||||||
Revises: 53c066772ad5
|
|
||||||
Create Date: 2023-10-23 17:35:15.527740
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'a1cf300fb371'
|
|
||||||
down_revision = '53c066772ad5'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import rattail.db.types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
|
|
||||||
# harvest_cache_user
|
|
||||||
op.drop_index('ix_harvest_user_version_end_transaction_id', table_name='harvest_cache_user_version')
|
|
||||||
op.drop_index('ix_harvest_user_version_operation_type', table_name='harvest_cache_user_version')
|
|
||||||
op.drop_index('ix_harvest_user_version_transaction_id', table_name='harvest_cache_user_version')
|
|
||||||
op.create_index(op.f('ix_harvest_cache_user_version_end_transaction_id'), 'harvest_cache_user_version', ['end_transaction_id'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_user_version_operation_type'), 'harvest_cache_user_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_user_version_transaction_id'), 'harvest_cache_user_version', ['transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_client
|
|
||||||
op.drop_index('ix_harvest_client_version_end_transaction_id', table_name='harvest_cache_client_version')
|
|
||||||
op.drop_index('ix_harvest_client_version_operation_type', table_name='harvest_cache_client_version')
|
|
||||||
op.drop_index('ix_harvest_client_version_transaction_id', table_name='harvest_cache_client_version')
|
|
||||||
op.create_index(op.f('ix_harvest_cache_client_version_end_transaction_id'), 'harvest_cache_client_version', ['end_transaction_id'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_client_version_operation_type'), 'harvest_cache_client_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_client_version_transaction_id'), 'harvest_cache_client_version', ['transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_project
|
|
||||||
op.drop_index('ix_harvest_project_version_end_transaction_id', table_name='harvest_cache_project_version')
|
|
||||||
op.drop_index('ix_harvest_project_version_operation_type', table_name='harvest_cache_project_version')
|
|
||||||
op.drop_index('ix_harvest_project_version_transaction_id', table_name='harvest_cache_project_version')
|
|
||||||
op.create_index(op.f('ix_harvest_cache_project_version_end_transaction_id'), 'harvest_cache_project_version', ['end_transaction_id'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_project_version_operation_type'), 'harvest_cache_project_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_project_version_transaction_id'), 'harvest_cache_project_version', ['transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_task
|
|
||||||
op.drop_index('ix_harvest_task_version_end_transaction_id', table_name='harvest_cache_task_version')
|
|
||||||
op.drop_index('ix_harvest_task_version_operation_type', table_name='harvest_cache_task_version')
|
|
||||||
op.drop_index('ix_harvest_task_version_transaction_id', table_name='harvest_cache_task_version')
|
|
||||||
op.create_index(op.f('ix_harvest_cache_task_version_end_transaction_id'), 'harvest_cache_task_version', ['end_transaction_id'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_task_version_operation_type'), 'harvest_cache_task_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_task_version_transaction_id'), 'harvest_cache_task_version', ['transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_time_entry
|
|
||||||
op.drop_index('ix_harvest_time_entry_version_end_transaction_id', table_name='harvest_cache_time_entry_version')
|
|
||||||
op.drop_index('ix_harvest_time_entry_version_operation_type', table_name='harvest_cache_time_entry_version')
|
|
||||||
op.drop_index('ix_harvest_time_entry_version_transaction_id', table_name='harvest_cache_time_entry_version')
|
|
||||||
op.create_index(op.f('ix_harvest_cache_time_entry_version_end_transaction_id'), 'harvest_cache_time_entry_version', ['end_transaction_id'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_time_entry_version_operation_type'), 'harvest_cache_time_entry_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index(op.f('ix_harvest_cache_time_entry_version_transaction_id'), 'harvest_cache_time_entry_version', ['transaction_id'], unique=False)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
|
|
||||||
# harvest_cache_time_entry
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_time_entry_version_transaction_id'), table_name='harvest_cache_time_entry_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_time_entry_version_operation_type'), table_name='harvest_cache_time_entry_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_time_entry_version_end_transaction_id'), table_name='harvest_cache_time_entry_version')
|
|
||||||
op.create_index('ix_harvest_time_entry_version_transaction_id', 'harvest_cache_time_entry_version', ['transaction_id'], unique=False)
|
|
||||||
op.create_index('ix_harvest_time_entry_version_operation_type', 'harvest_cache_time_entry_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index('ix_harvest_time_entry_version_end_transaction_id', 'harvest_cache_time_entry_version', ['end_transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_task
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_task_version_transaction_id'), table_name='harvest_cache_task_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_task_version_operation_type'), table_name='harvest_cache_task_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_task_version_end_transaction_id'), table_name='harvest_cache_task_version')
|
|
||||||
op.create_index('ix_harvest_task_version_transaction_id', 'harvest_cache_task_version', ['transaction_id'], unique=False)
|
|
||||||
op.create_index('ix_harvest_task_version_operation_type', 'harvest_cache_task_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index('ix_harvest_task_version_end_transaction_id', 'harvest_cache_task_version', ['end_transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_project
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_project_version_transaction_id'), table_name='harvest_cache_project_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_project_version_operation_type'), table_name='harvest_cache_project_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_project_version_end_transaction_id'), table_name='harvest_cache_project_version')
|
|
||||||
op.create_index('ix_harvest_project_version_transaction_id', 'harvest_cache_project_version', ['transaction_id'], unique=False)
|
|
||||||
op.create_index('ix_harvest_project_version_operation_type', 'harvest_cache_project_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index('ix_harvest_project_version_end_transaction_id', 'harvest_cache_project_version', ['end_transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_client
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_client_version_transaction_id'), table_name='harvest_cache_client_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_client_version_operation_type'), table_name='harvest_cache_client_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_client_version_end_transaction_id'), table_name='harvest_cache_client_version')
|
|
||||||
op.create_index('ix_harvest_client_version_transaction_id', 'harvest_cache_client_version', ['transaction_id'], unique=False)
|
|
||||||
op.create_index('ix_harvest_client_version_operation_type', 'harvest_cache_client_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index('ix_harvest_client_version_end_transaction_id', 'harvest_cache_client_version', ['end_transaction_id'], unique=False)
|
|
||||||
|
|
||||||
# harvest_cache_user
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_user_version_transaction_id'), table_name='harvest_cache_user_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_user_version_operation_type'), table_name='harvest_cache_user_version')
|
|
||||||
op.drop_index(op.f('ix_harvest_cache_user_version_end_transaction_id'), table_name='harvest_cache_user_version')
|
|
||||||
op.create_index('ix_harvest_user_version_transaction_id', 'harvest_cache_user_version', ['transaction_id'], unique=False)
|
|
||||||
op.create_index('ix_harvest_user_version_operation_type', 'harvest_cache_user_version', ['operation_type'], unique=False)
|
|
||||||
op.create_index('ix_harvest_user_version_end_transaction_id', 'harvest_cache_user_version', ['end_transaction_id'], unique=False)
|
|
|
@ -1,89 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
"""grow id fields
|
|
||||||
|
|
||||||
Revision ID: f2a1650e7fbc
|
|
||||||
Revises: 6bc1cb21d920
|
|
||||||
Create Date: 2023-08-08 10:53:56.013211
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'f2a1650e7fbc'
|
|
||||||
down_revision = '6bc1cb21d920'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import rattail.db.types
|
|
||||||
from sqlalchemy.dialects import postgresql
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.alter_column('harvest_user', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_user_version', 'id', type_=sa.BigInteger())
|
|
||||||
|
|
||||||
# harvest_client
|
|
||||||
op.alter_column('harvest_client', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_client_version', 'id', type_=sa.BigInteger())
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.alter_column('harvest_project', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_project', 'client_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_project_version', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_project_version', 'client_id', type_=sa.BigInteger())
|
|
||||||
|
|
||||||
# harvest_task
|
|
||||||
op.alter_column('harvest_task', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_task_version', 'id', type_=sa.BigInteger())
|
|
||||||
|
|
||||||
# harvest_time_entry
|
|
||||||
op.alter_column('harvest_time_entry', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry', 'user_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry', 'client_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry', 'project_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry', 'task_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry', 'invoice_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'user_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'client_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'project_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'task_id', type_=sa.BigInteger())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'invoice_id', type_=sa.BigInteger())
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
|
|
||||||
# harvest_time_entry
|
|
||||||
op.alter_column('harvest_time_entry_version', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'user_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'client_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'project_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'task_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry_version', 'invoice_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'user_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'client_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'project_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'task_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_time_entry', 'invoice_id', type_=sa.Integer())
|
|
||||||
|
|
||||||
# harvest_task
|
|
||||||
op.alter_column('harvest_task_version', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_task', 'id', type_=sa.Integer())
|
|
||||||
|
|
||||||
# harvest_project
|
|
||||||
op.alter_column('harvest_project_version', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_project_version', 'client_id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_project', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_project', 'client_id', type_=sa.Integer())
|
|
||||||
|
|
||||||
# harvest_client
|
|
||||||
op.alter_column('harvest_client_version', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_client', 'id', type_=sa.Integer())
|
|
||||||
|
|
||||||
# harvest_user
|
|
||||||
op.alter_column('harvest_user_version', 'id', type_=sa.Integer())
|
|
||||||
op.alter_column('harvest_user', 'id', type_=sa.Integer())
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,6 +24,5 @@
|
||||||
Harvest integration data models
|
Harvest integration data models
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .harvest import (HarvestCacheUser, HarvestCacheClient,
|
from .harvest import (HarvestUser, HarvestClient, HarvestProject,
|
||||||
HarvestCacheProject, HarvestCacheTask,
|
HarvestTask, HarvestTimeEntry)
|
||||||
HarvestCacheTimeEntry)
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,8 +24,6 @@
|
||||||
Harvest "cache" data models
|
Harvest "cache" data models
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
@ -33,23 +31,21 @@ from rattail.db import model
|
||||||
from rattail.db.util import normalize_full_name
|
from rattail.db.util import normalize_full_name
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheUser(model.Base):
|
class HarvestUser(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents a user record in Harvest.
|
Represents a user record in Harvest.
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/users-api/users/users/#the-user-object
|
https://help.getharvest.com/api-v2/users-api/users/users/#the-user-object
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'harvest_cache_user'
|
__tablename__ = 'harvest_user'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.ForeignKeyConstraint(['person_uuid'], ['person.uuid'],
|
sa.UniqueConstraint('id', name='harvest_user_uq_id'),
|
||||||
name='harvest_cache_user_fk_person'),
|
|
||||||
sa.UniqueConstraint('id', name='harvest_cache_user_uq_id'),
|
|
||||||
)
|
)
|
||||||
__versioned__ = {}
|
__versioned__ = {}
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
id = sa.Column(sa.BigInteger(), nullable=False)
|
id = sa.Column(sa.Integer(), nullable=False)
|
||||||
|
|
||||||
first_name = sa.Column(sa.String(length=255), nullable=True)
|
first_name = sa.Column(sa.String(length=255), nullable=True)
|
||||||
|
|
||||||
|
@ -94,38 +90,25 @@ class HarvestCacheUser(model.Base):
|
||||||
|
|
||||||
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
updated_at = sa.Column(sa.DateTime(), nullable=True)
|
||||||
|
|
||||||
person_uuid = sa.Column(sa.String(length=32), nullable=True)
|
|
||||||
person = orm.relationship(
|
|
||||||
model.Person,
|
|
||||||
doc="""
|
|
||||||
Reference to the person associated with this Harvest user.
|
|
||||||
""",
|
|
||||||
backref=orm.backref(
|
|
||||||
'harvest_users',
|
|
||||||
doc="""
|
|
||||||
List of all Harvest user accounts for the person.
|
|
||||||
""")
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return normalize_full_name(self.first_name, self.last_name)
|
return normalize_full_name(self.first_name, self.last_name)
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheClient(model.Base):
|
class HarvestClient(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents a client record in Harvest.
|
Represents a client record in Harvest.
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/clients-api/clients/clients/#the-client-object
|
https://help.getharvest.com/api-v2/clients-api/clients/clients/#the-client-object
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'harvest_cache_client'
|
__tablename__ = 'harvest_client'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('id', name='harvest_cache_client_uq_id'),
|
sa.UniqueConstraint('id', name='harvest_client_uq_id'),
|
||||||
)
|
)
|
||||||
__versioned__ = {}
|
__versioned__ = {}
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
id = sa.Column(sa.BigInteger(), nullable=False)
|
id = sa.Column(sa.Integer(), nullable=False)
|
||||||
|
|
||||||
name = sa.Column(sa.String(length=255), nullable=True)
|
name = sa.Column(sa.String(length=255), nullable=True)
|
||||||
|
|
||||||
|
@ -143,26 +126,25 @@ class HarvestCacheClient(model.Base):
|
||||||
return self.name or ''
|
return self.name or ''
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheProject(model.Base):
|
class HarvestProject(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents a project record in Harvest.
|
Represents a project record in Harvest.
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/projects-api/projects/projects/#the-project-object
|
https://help.getharvest.com/api-v2/projects-api/projects/projects/#the-project-object
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'harvest_cache_project'
|
__tablename__ = 'harvest_project'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('id', name='harvest_cache_project_uq_id'),
|
sa.UniqueConstraint('id', name='harvest_project_uq_id'),
|
||||||
sa.ForeignKeyConstraint(['client_id'], ['harvest_cache_client.id'],
|
sa.ForeignKeyConstraint(['client_id'], ['harvest_client.id'], name='harvest_project_fk_client'),
|
||||||
name='harvest_cache_project_fk_client'),
|
|
||||||
)
|
)
|
||||||
__versioned__ = {'exclude': ['over_budget_notification_date']}
|
__versioned__ = {'exclude': ['over_budget_notification_date']}
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
id = sa.Column(sa.BigInteger(), nullable=False)
|
id = sa.Column(sa.Integer(), nullable=False)
|
||||||
|
|
||||||
client_id = sa.Column(sa.BigInteger(), nullable=True) # TODO: should not allow null?
|
client_id = sa.Column(sa.Integer(), nullable=True) # TODO: should not allow null?
|
||||||
client = orm.relationship(HarvestCacheClient, backref=orm.backref('projects'))
|
client = orm.relationship(HarvestClient, backref=orm.backref('projects'))
|
||||||
|
|
||||||
name = sa.Column(sa.String(length=255), nullable=True)
|
name = sa.Column(sa.String(length=255), nullable=True)
|
||||||
|
|
||||||
|
@ -208,29 +190,25 @@ class HarvestCacheProject(model.Base):
|
||||||
|
|
||||||
updated_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):
|
def __str__(self):
|
||||||
return self.name or ''
|
return self.name or ''
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTask(model.Base):
|
class HarvestTask(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents a task record in Harvest.
|
Represents a task record in Harvest.
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/tasks-api/tasks/tasks/#the-task-object
|
https://help.getharvest.com/api-v2/tasks-api/tasks/tasks/#the-task-object
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'harvest_cache_task'
|
__tablename__ = 'harvest_task'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('id', name='harvest_cache_task_uq_id'),
|
sa.UniqueConstraint('id', name='harvest_task_uq_id'),
|
||||||
)
|
)
|
||||||
__versioned__ = {}
|
__versioned__ = {}
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
id = sa.Column(sa.BigInteger(), nullable=False)
|
id = sa.Column(sa.Integer(), nullable=False)
|
||||||
|
|
||||||
name = sa.Column(sa.String(length=255), nullable=True)
|
name = sa.Column(sa.String(length=255), nullable=True)
|
||||||
|
|
||||||
|
@ -250,46 +228,42 @@ class HarvestCacheTask(model.Base):
|
||||||
return self.name or ''
|
return self.name or ''
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTimeEntry(model.Base):
|
class HarvestTimeEntry(model.Base):
|
||||||
"""
|
"""
|
||||||
Represents a time entry record in Harvest.
|
Represents a time entry record in Harvest.
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#the-time-entry-object
|
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#the-time-entry-object
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'harvest_cache_time_entry'
|
__tablename__ = 'harvest_time_entry'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('id', name='harvest_cache_time_entry_uq_id'),
|
sa.UniqueConstraint('id', name='harvest_time_entry_uq_id'),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['harvest_cache_user.id'],
|
sa.ForeignKeyConstraint(['user_id'], ['harvest_user.id'], name='harvest_time_entry_fk_user'),
|
||||||
name='harvest_cache_time_entry_fk_user'),
|
sa.ForeignKeyConstraint(['client_id'], ['harvest_client.id'], name='harvest_time_entry_fk_client'),
|
||||||
sa.ForeignKeyConstraint(['client_id'], ['harvest_cache_client.id'],
|
sa.ForeignKeyConstraint(['project_id'], ['harvest_project.id'], name='harvest_time_entry_fk_project'),
|
||||||
name='harvest_cache_time_entry_fk_client'),
|
sa.ForeignKeyConstraint(['task_id'], ['harvest_task.id'], name='harvest_time_entry_fk_task'),
|
||||||
sa.ForeignKeyConstraint(['project_id'], ['harvest_cache_project.id'],
|
|
||||||
name='harvest_cache_time_entry_fk_project'),
|
|
||||||
sa.ForeignKeyConstraint(['task_id'], ['harvest_cache_task.id'],
|
|
||||||
name='harvest_cache_time_entry_fk_task'),
|
|
||||||
)
|
)
|
||||||
__versioned__ = {}
|
__versioned__ = {}
|
||||||
model_title_plural = "Harvest Time Entries"
|
model_title_plural = "Harvest Time Entries"
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
id = sa.Column(sa.BigInteger(), nullable=False)
|
id = sa.Column(sa.Integer(), nullable=False)
|
||||||
|
|
||||||
spent_date = sa.Column(sa.Date(), nullable=True)
|
spent_date = sa.Column(sa.Date(), nullable=True)
|
||||||
|
|
||||||
user_id = sa.Column(sa.BigInteger(), nullable=True)
|
user_id = sa.Column(sa.Integer(), nullable=True)
|
||||||
user = orm.relationship(HarvestCacheUser, backref=orm.backref('time_entries'))
|
user = orm.relationship(HarvestUser, backref=orm.backref('time_entries'))
|
||||||
|
|
||||||
client_id = sa.Column(sa.BigInteger(), nullable=True)
|
client_id = sa.Column(sa.Integer(), nullable=True)
|
||||||
client = orm.relationship(HarvestCacheClient, backref=orm.backref('time_entries'))
|
client = orm.relationship(HarvestClient, backref=orm.backref('time_entries'))
|
||||||
|
|
||||||
project_id = sa.Column(sa.BigInteger(), nullable=True)
|
project_id = sa.Column(sa.Integer(), nullable=True)
|
||||||
project = orm.relationship(HarvestCacheProject, backref=orm.backref('time_entries'))
|
project = orm.relationship(HarvestProject, backref=orm.backref('time_entries'))
|
||||||
|
|
||||||
task_id = sa.Column(sa.BigInteger(), nullable=True)
|
task_id = sa.Column(sa.Integer(), nullable=True)
|
||||||
task = orm.relationship(HarvestCacheTask, backref=orm.backref('time_entries'))
|
task = orm.relationship(HarvestTask, backref=orm.backref('time_entries'))
|
||||||
|
|
||||||
invoice_id = sa.Column(sa.BigInteger(), nullable=True)
|
invoice_id = sa.Column(sa.Integer(), nullable=True)
|
||||||
|
|
||||||
hours = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
hours = sa.Column(sa.Numeric(precision=6, scale=2), nullable=True)
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- 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 config
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def get_harvest_url(config):
|
|
||||||
url = config.get('harvest', 'url')
|
|
||||||
if url:
|
|
||||||
return url.rstrip('/')
|
|
|
@ -1,27 +0,0 @@
|
||||||
# -*- 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/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Exporting data to Harvest
|
|
||||||
"""
|
|
||||||
|
|
||||||
from . import model
|
|
|
@ -1,169 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2023 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 model importers
|
|
||||||
"""
|
|
||||||
|
|
||||||
from rattail import importing
|
|
||||||
from rattail_harvest.harvest.webapi import make_harvest_webapi
|
|
||||||
|
|
||||||
|
|
||||||
class ToHarvest(importing.Importer):
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
super().setup()
|
|
||||||
self.setup_webapi()
|
|
||||||
|
|
||||||
def datasync_setup(self):
|
|
||||||
super().datasync_setup()
|
|
||||||
self.setup_webapi()
|
|
||||||
|
|
||||||
def setup_webapi(self):
|
|
||||||
self.webapi = make_harvest_webapi(self.config)
|
|
||||||
|
|
||||||
|
|
||||||
class TimeEntryImporter(ToHarvest):
|
|
||||||
"""
|
|
||||||
Harvest time entry data importer.
|
|
||||||
"""
|
|
||||||
model_name = 'TimeEntry'
|
|
||||||
key = 'id'
|
|
||||||
supported_fields = [
|
|
||||||
'id',
|
|
||||||
'user_id',
|
|
||||||
'client_id',
|
|
||||||
'project_id',
|
|
||||||
'task_id',
|
|
||||||
'spent_date',
|
|
||||||
# 'started_time',
|
|
||||||
# 'ended_time',
|
|
||||||
'hours',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
caches_local_data = True
|
|
||||||
|
|
||||||
def cache_local_data(self, host_data=None):
|
|
||||||
"""
|
|
||||||
Fetch existing time entries from Harvest.
|
|
||||||
"""
|
|
||||||
cache = {}
|
|
||||||
|
|
||||||
# TODO: we try to avoid entries w/ timer still running here,
|
|
||||||
# but for some reason they still come back, so double-check
|
|
||||||
kw = {'is_running': False}
|
|
||||||
if self.start_date:
|
|
||||||
kw['from'] = self.start_date
|
|
||||||
if self.end_date:
|
|
||||||
kw['to'] = self.end_date
|
|
||||||
entries = self.webapi.get_time_entries(**kw)
|
|
||||||
for entry in entries:
|
|
||||||
# double-check here
|
|
||||||
if not entry['is_running']:
|
|
||||||
data = self.normalize_local_object(entry)
|
|
||||||
if data:
|
|
||||||
normal = self.normalize_cache_object(entry, data)
|
|
||||||
key = self.get_cache_key(entry, normal)
|
|
||||||
cache[key] = normal
|
|
||||||
return cache
|
|
||||||
|
|
||||||
def get_single_local_object(self, key):
|
|
||||||
assert len(self.key) == 1 and self.key[0] == 'id'
|
|
||||||
entry_id = key[0]
|
|
||||||
if entry_id > 0:
|
|
||||||
return self.webapi.get_time_entry(entry_id)
|
|
||||||
|
|
||||||
def normalize_local_object(self, entry):
|
|
||||||
data = {
|
|
||||||
'id': entry['id'],
|
|
||||||
'client_id': entry['client']['id'],
|
|
||||||
'project_id': entry['project']['id'],
|
|
||||||
'task_id': entry['task']['id'],
|
|
||||||
'spent_date': entry['spent_date'],
|
|
||||||
# 'started_time': entry['started_time'],
|
|
||||||
# 'ended_time': entry['ended_time'],
|
|
||||||
'hours': entry['hours'],
|
|
||||||
'notes': entry['notes'],
|
|
||||||
}
|
|
||||||
|
|
||||||
if 'user_id' in self.fields:
|
|
||||||
data['user_id'] = entry['user']['id']
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_next_harvest_id(self):
|
|
||||||
if hasattr(self, 'next_harvest_id'):
|
|
||||||
next_id = self.next_harvest_id
|
|
||||||
else:
|
|
||||||
next_id = 1
|
|
||||||
self.next_harvest_id = next_id + 1
|
|
||||||
return -next_id
|
|
||||||
|
|
||||||
def create_object(self, key, host_data):
|
|
||||||
if self.dry_run:
|
|
||||||
# mock out return value
|
|
||||||
result = dict(host_data)
|
|
||||||
if 'user_id' in self.fields:
|
|
||||||
result['user'] = {'id': result['user_id']}
|
|
||||||
if 'client_id' in self.fields:
|
|
||||||
result['client'] = {'id': result['client_id']}
|
|
||||||
result['project'] = {'id': result['project_id']}
|
|
||||||
result['task'] = {'id': result['task_id']}
|
|
||||||
return result
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
'client_id': host_data['client_id'],
|
|
||||||
'project_id': host_data['project_id'],
|
|
||||||
'task_id': host_data['task_id'],
|
|
||||||
'spent_date': host_data['spent_date'],
|
|
||||||
# 'started_time': host_data['started_time'],
|
|
||||||
# 'ended_time': host_data['ended_time'],
|
|
||||||
'hours': host_data['hours'],
|
|
||||||
'notes': host_data['notes'],
|
|
||||||
}
|
|
||||||
if 'user_id' in self.fields:
|
|
||||||
kwargs['user_id'] = host_data['user_id']
|
|
||||||
entry = self.webapi.put_time_entry(**kwargs)
|
|
||||||
return entry
|
|
||||||
|
|
||||||
def update_object(self, entry, host_data, local_data=None, all_fields=False):
|
|
||||||
if self.dry_run:
|
|
||||||
return entry
|
|
||||||
|
|
||||||
kwargs = {
|
|
||||||
'project_id': host_data['project_id'],
|
|
||||||
'task_id': host_data['task_id'],
|
|
||||||
'spent_date': host_data['spent_date'],
|
|
||||||
# 'started_time': host_data['started_time'],
|
|
||||||
# 'ended_time': host_data['ended_time'],
|
|
||||||
'hours': host_data['hours'],
|
|
||||||
'notes': host_data['notes'],
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.webapi.update_time_entry(entry['id'], **kwargs)
|
|
||||||
|
|
||||||
def delete_object(self, entry):
|
|
||||||
if self.dry_run:
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.webapi.delete_time_entry(entry['id'])
|
|
||||||
return True
|
|
|
@ -58,12 +58,6 @@ class HarvestWebAPI(object):
|
||||||
elif request_method == 'POST':
|
elif request_method == 'POST':
|
||||||
response = requests.post('{}/{}'.format(self.base_url, api_method),
|
response = requests.post('{}/{}'.format(self.base_url, api_method),
|
||||||
headers=headers, params=params)
|
headers=headers, params=params)
|
||||||
elif request_method == 'PATCH':
|
|
||||||
response = requests.patch('{}/{}'.format(self.base_url, api_method),
|
|
||||||
headers=headers, params=params)
|
|
||||||
elif request_method == 'DELETE':
|
|
||||||
response = requests.delete('{}/{}'.format(self.base_url, api_method),
|
|
||||||
headers=headers, params=params)
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("unknown request method: {}".format(
|
raise NotImplementedError("unknown request method: {}".format(
|
||||||
request_method))
|
request_method))
|
||||||
|
@ -82,18 +76,6 @@ class HarvestWebAPI(object):
|
||||||
"""
|
"""
|
||||||
return self._request('POST', api_method, params=params)
|
return self._request('POST', api_method, params=params)
|
||||||
|
|
||||||
def patch(self, api_method, params=None):
|
|
||||||
"""
|
|
||||||
Perform a PATCH request for the given API method, and return the response.
|
|
||||||
"""
|
|
||||||
return self._request('PATCH', api_method, params=params)
|
|
||||||
|
|
||||||
def delete(self, api_method, params=None):
|
|
||||||
"""
|
|
||||||
Perform a DELETE request for the given API method, and return the response.
|
|
||||||
"""
|
|
||||||
return self._request('DELETE', api_method, params=params)
|
|
||||||
|
|
||||||
def get_company(self):
|
def get_company(self):
|
||||||
"""
|
"""
|
||||||
Retrieves the company for the currently authenticated user.
|
Retrieves the company for the currently authenticated user.
|
||||||
|
@ -131,17 +113,7 @@ class HarvestWebAPI(object):
|
||||||
https://help.getharvest.com/api-v2/projects-api/projects/projects/#list-all-projects
|
https://help.getharvest.com/api-v2/projects-api/projects/projects/#list-all-projects
|
||||||
"""
|
"""
|
||||||
response = self.get('/projects', params=kwargs)
|
response = self.get('/projects', params=kwargs)
|
||||||
data = response.json()
|
return response.json()
|
||||||
projects = data['projects']
|
|
||||||
while data['next_page']:
|
|
||||||
|
|
||||||
kw = dict(kwargs)
|
|
||||||
kw['page'] = data['next_page']
|
|
||||||
response = self.get('/projects', params=kw)
|
|
||||||
data = response.json()
|
|
||||||
projects.extend(data['projects'])
|
|
||||||
|
|
||||||
return projects
|
|
||||||
|
|
||||||
def get_tasks(self, **kwargs):
|
def get_tasks(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -179,15 +151,10 @@ class HarvestWebAPI(object):
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#retrieve-a-time-entry
|
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#retrieve-a-time-entry
|
||||||
"""
|
"""
|
||||||
try:
|
response = self.get('/time_entries/{}'.format(time_entry_id))
|
||||||
response = self.get('/time_entries/{}'.format(time_entry_id))
|
return response.json()
|
||||||
except requests.exceptions.HTTPError as error:
|
|
||||||
if error.response.status_code != 404:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
def create_time_entry(self, **kwargs):
|
def put_time_entry(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a new time entry. All kwargs are passed on as POST parameters.
|
Create a new time entry. All kwargs are passed on as POST parameters.
|
||||||
|
|
||||||
|
@ -200,41 +167,3 @@ class HarvestWebAPI(object):
|
||||||
raise ValueError("must provide all of: {}".format(', '.join(required)))
|
raise ValueError("must provide all of: {}".format(', '.join(required)))
|
||||||
response = self.post('/time_entries', params=kwargs)
|
response = self.post('/time_entries', params=kwargs)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
# TODO: deprecate / remove this
|
|
||||||
put_time_entry = create_time_entry
|
|
||||||
|
|
||||||
def stop_time_entry(self, time_entry_id):
|
|
||||||
"""
|
|
||||||
Stop a running time entry.
|
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#stop-a-running-time-entry
|
|
||||||
"""
|
|
||||||
response = self.patch('/time_entries/{}/stop'.format(time_entry_id))
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
def update_time_entry(self, time_entry_id, **kwargs):
|
|
||||||
"""
|
|
||||||
Update a time entry.
|
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#update-a-time-entry
|
|
||||||
"""
|
|
||||||
response = self.patch('/time_entries/{}'.format(time_entry_id), params=kwargs)
|
|
||||||
return response.json()
|
|
||||||
|
|
||||||
def delete_time_entry(self, time_entry_id, **kwargs):
|
|
||||||
"""
|
|
||||||
Delete a time entry.
|
|
||||||
|
|
||||||
https://help.getharvest.com/api-v2/timesheets-api/timesheets/time-entries/#delete-a-time-entry
|
|
||||||
"""
|
|
||||||
self.delete('/time_entries/{}'.format(time_entry_id), params=kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def make_harvest_webapi(config):
|
|
||||||
access_token = config.require('harvest', 'api.access_token')
|
|
||||||
account_id = config.require('harvest', 'api.account_id')
|
|
||||||
user_agent = config.require('harvest', 'api.user_agent')
|
|
||||||
return HarvestWebAPI(access_token=access_token,
|
|
||||||
account_id=account_id,
|
|
||||||
user_agent=user_agent)
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -27,13 +27,11 @@ Harvest -> Rattail "cache" data import
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from rattail import importing
|
from rattail import importing
|
||||||
|
from rattail.util import OrderedDict
|
||||||
from rattail_harvest import importing as rattail_harvest_importing
|
from rattail_harvest import importing as rattail_harvest_importing
|
||||||
from rattail_harvest.harvest.webapi import make_harvest_webapi
|
from rattail_harvest.harvest.webapi import HarvestWebAPI
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -49,11 +47,11 @@ class FromHarvestToRattail(importing.ToRattailHandler):
|
||||||
|
|
||||||
def get_importers(self):
|
def get_importers(self):
|
||||||
importers = OrderedDict()
|
importers = OrderedDict()
|
||||||
importers['HarvestCacheUser'] = HarvestCacheUserImporter
|
importers['HarvestUser'] = HarvestUserImporter
|
||||||
importers['HarvestCacheClient'] = HarvestCacheClientImporter
|
importers['HarvestClient'] = HarvestClientImporter
|
||||||
importers['HarvestCacheProject'] = HarvestCacheProjectImporter
|
importers['HarvestProject'] = HarvestProjectImporter
|
||||||
importers['HarvestCacheTask'] = HarvestCacheTaskImporter
|
importers['HarvestTask'] = HarvestTaskImporter
|
||||||
importers['HarvestCacheTimeEntry'] = HarvestCacheTimeEntryImporter
|
importers['HarvestTimeEntry'] = HarvestTimeEntryImporter
|
||||||
return importers
|
return importers
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,7 +69,13 @@ class FromHarvest(importing.Importer):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
super(FromHarvest, self).setup()
|
super(FromHarvest, self).setup()
|
||||||
self.webapi = make_harvest_webapi(self.config)
|
|
||||||
|
access_token = self.config.require('harvest', 'api.access_token')
|
||||||
|
account_id = self.config.require('harvest', 'api.account_id')
|
||||||
|
user_agent = self.config.require('harvest', 'api.user_agent')
|
||||||
|
self.webapi = HarvestWebAPI(access_token=access_token,
|
||||||
|
account_id=account_id,
|
||||||
|
user_agent=user_agent)
|
||||||
|
|
||||||
def time_from_harvest(self, value):
|
def time_from_harvest(self, value):
|
||||||
# all harvest times appear to come as UTC, so no conversion needed
|
# all harvest times appear to come as UTC, so no conversion needed
|
||||||
|
@ -90,17 +94,14 @@ class FromHarvest(importing.Importer):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheUserImporter(FromHarvest, rattail_harvest_importing.model.HarvestCacheUserImporter):
|
class HarvestUserImporter(FromHarvest, rattail_harvest_importing.model.HarvestUserImporter):
|
||||||
"""
|
"""
|
||||||
Import user data from Harvest
|
Import user data from Harvest
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_fields(self):
|
def supported_fields(self):
|
||||||
fields = list(super().supported_fields)
|
fields = list(super(HarvestUserImporter, self).supported_fields)
|
||||||
|
|
||||||
# this is for local tracking only; is not in harvest
|
|
||||||
fields.remove('person_uuid')
|
|
||||||
|
|
||||||
# this used to be in harvest i thought, but is no longer?
|
# this used to be in harvest i thought, but is no longer?
|
||||||
fields.remove('name')
|
fields.remove('name')
|
||||||
|
@ -110,28 +111,8 @@ class HarvestCacheUserImporter(FromHarvest, rattail_harvest_importing.model.Harv
|
||||||
def get_host_objects(self):
|
def get_host_objects(self):
|
||||||
return self.webapi.get_users()['users']
|
return self.webapi.get_users()['users']
|
||||||
|
|
||||||
def normalize_host_object(self, user):
|
|
||||||
data = super().normalize_host_object(user)
|
|
||||||
if data:
|
|
||||||
|
|
||||||
# TODO: for some reason the API used to include the these
|
class HarvestClientImporter(FromHarvest, rattail_harvest_importing.model.HarvestClientImporter):
|
||||||
# fields, but no longer does as of 2022-11-11, so null is
|
|
||||||
# kinda the only thing that makes sense now. if possible,
|
|
||||||
# should figure out "what changed" at Harvest, but maybe
|
|
||||||
# these fields should just be removed from our cache
|
|
||||||
# schema?
|
|
||||||
data.setdefault('is_admin', None)
|
|
||||||
data.setdefault('is_project_manager', None)
|
|
||||||
data.setdefault('can_see_rates', None)
|
|
||||||
data.setdefault('can_create_invoices', None)
|
|
||||||
|
|
||||||
if data['telephone'] == '':
|
|
||||||
data['telephone'] = None
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheClientImporter(FromHarvest, rattail_harvest_importing.model.HarvestCacheClientImporter):
|
|
||||||
"""
|
"""
|
||||||
Import client data from Harvest
|
Import client data from Harvest
|
||||||
"""
|
"""
|
||||||
|
@ -140,32 +121,16 @@ class HarvestCacheClientImporter(FromHarvest, rattail_harvest_importing.model.Ha
|
||||||
return self.webapi.get_clients()['clients']
|
return self.webapi.get_clients()['clients']
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheProjectImporter(FromHarvest, rattail_harvest_importing.model.HarvestCacheProjectImporter):
|
class HarvestProjectImporter(FromHarvest, rattail_harvest_importing.model.HarvestProjectImporter):
|
||||||
"""
|
"""
|
||||||
Import project data from Harvest
|
Import project data from Harvest
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_fields(self):
|
|
||||||
fields = list(super().supported_fields)
|
|
||||||
|
|
||||||
# this is for local tracking only; is not in harvest
|
|
||||||
fields.remove('deleted')
|
|
||||||
|
|
||||||
return fields
|
|
||||||
|
|
||||||
def cache_query(self):
|
|
||||||
model = self.model
|
|
||||||
return self.session.query(model.HarvestCacheProject)\
|
|
||||||
.filter(sa.or_(
|
|
||||||
model.HarvestCacheProject.deleted == False,
|
|
||||||
model.HarvestCacheProject.deleted == None))
|
|
||||||
|
|
||||||
def get_host_objects(self):
|
def get_host_objects(self):
|
||||||
return self.webapi.get_projects()
|
return self.webapi.get_projects()['projects']
|
||||||
|
|
||||||
def normalize_host_object(self, project):
|
def normalize_host_object(self, project):
|
||||||
data = super().normalize_host_object(project)
|
data = super(HarvestProjectImporter, self).normalize_host_object(project)
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -203,15 +168,8 @@ class HarvestCacheProjectImporter(FromHarvest, rattail_harvest_importing.model.H
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def can_delete_object(self, project, data):
|
|
||||||
return not project.deleted
|
|
||||||
|
|
||||||
def delete_object(self, project):
|
class HarvestTaskImporter(FromHarvest, rattail_harvest_importing.model.HarvestTaskImporter):
|
||||||
project.deleted = True
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTaskImporter(FromHarvest, rattail_harvest_importing.model.HarvestCacheTaskImporter):
|
|
||||||
"""
|
"""
|
||||||
Import task data from Harvest
|
Import task data from Harvest
|
||||||
"""
|
"""
|
||||||
|
@ -220,49 +178,40 @@ class HarvestCacheTaskImporter(FromHarvest, rattail_harvest_importing.model.Harv
|
||||||
return self.webapi.get_tasks()['tasks']
|
return self.webapi.get_tasks()['tasks']
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTimeEntryImporter(FromHarvest, rattail_harvest_importing.model.HarvestCacheTimeEntryImporter):
|
class HarvestTimeEntryImporter(FromHarvest, rattail_harvest_importing.model.HarvestTimeEntryImporter):
|
||||||
"""
|
"""
|
||||||
Import time entry data from Harvest
|
Import time entry data from Harvest
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_host_objects(self):
|
def setup(self):
|
||||||
kw = {}
|
super(HarvestTimeEntryImporter, self).setup()
|
||||||
if self.start_date:
|
model = self.model
|
||||||
kw['from'] = self.start_date
|
|
||||||
if self.end_date:
|
|
||||||
kw['to'] = self.end_date
|
|
||||||
return self.webapi.get_time_entries(**kw)
|
|
||||||
|
|
||||||
def get_single_host_object(self, key):
|
self.harvest_projects_by_id = self.app.cache_model(self.session,
|
||||||
assert len(self.key) == 1 and self.key[0] == 'id'
|
model.HarvestProject,
|
||||||
entry_id = key[0]
|
key='id')
|
||||||
return self.webapi.get_time_entry(entry_id)
|
|
||||||
|
def get_host_objects(self):
|
||||||
|
return self.webapi.get_time_entries(**{'from': self.start_date,
|
||||||
|
'to': self.end_date})
|
||||||
|
|
||||||
def normalize_host_object(self, entry):
|
def normalize_host_object(self, entry):
|
||||||
data = super().normalize_host_object(entry)
|
data = super(HarvestTimeEntryImporter, self).normalize_host_object(entry)
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
if entry['is_running']:
|
|
||||||
log.debug("Harvest time entry is still running: %s", entry)
|
|
||||||
return
|
|
||||||
|
|
||||||
data['user_id'] = entry['user']['id']
|
data['user_id'] = entry['user']['id']
|
||||||
data['client_id'] = entry['client']['id']
|
data['client_id'] = entry['client']['id']
|
||||||
|
|
||||||
|
data['project_id'] = entry['project']['id']
|
||||||
|
if data['project_id'] not in self.harvest_projects_by_id:
|
||||||
|
log.warning("time entry references non-existent project id %s: %s",
|
||||||
|
data['project_id'], entry)
|
||||||
|
data['project_id'] = None
|
||||||
|
|
||||||
data['task_id'] = entry['task']['id']
|
data['task_id'] = entry['task']['id']
|
||||||
data['invoice_id'] = entry['invoice']['id'] if entry['invoice'] else None
|
data['invoice_id'] = entry['invoice']['id'] if entry['invoice'] else None
|
||||||
|
|
||||||
# project_id
|
|
||||||
if 'project_id' in self.fields:
|
|
||||||
data['project_id'] = entry['project']['id']
|
|
||||||
project = self.get_harvest_project(data['project_id'])
|
|
||||||
if not project:
|
|
||||||
logger = log.warning if self.warn_for_unknown_project else log.debug
|
|
||||||
logger("time entry references non-existent project id %s: %s",
|
|
||||||
data['project_id'], entry)
|
|
||||||
if not self.auto_create_unknown_project:
|
|
||||||
data['project_id'] = None
|
|
||||||
|
|
||||||
# spent_date
|
# spent_date
|
||||||
spent_date = data['spent_date']
|
spent_date = data['spent_date']
|
||||||
if spent_date:
|
if spent_date:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,86 +24,30 @@
|
||||||
rattail-harvest model importers
|
rattail-harvest model importers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from rattail.importing.model import ToRattail
|
from rattail.importing.model import ToRattail
|
||||||
from rattail_harvest.db import model
|
from rattail_harvest.db import model
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# harvest cache models
|
# harvest cache models
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
class HarvestCacheUserImporter(ToRattail):
|
class HarvestUserImporter(ToRattail):
|
||||||
model_class = model.HarvestCacheUser
|
model_class = model.HarvestUser
|
||||||
|
|
||||||
class HarvestCacheClientImporter(ToRattail):
|
class HarvestClientImporter(ToRattail):
|
||||||
model_class = model.HarvestCacheClient
|
model_class = model.HarvestClient
|
||||||
|
|
||||||
class HarvestCacheProjectImporter(ToRattail):
|
class HarvestProjectImporter(ToRattail):
|
||||||
model_class = model.HarvestCacheProject
|
model_class = model.HarvestProject
|
||||||
|
|
||||||
class HarvestCacheTaskImporter(ToRattail):
|
class HarvestTaskImporter(ToRattail):
|
||||||
model_class = model.HarvestCacheTask
|
model_class = model.HarvestTask
|
||||||
|
|
||||||
class HarvestCacheTimeEntryImporter(ToRattail):
|
class HarvestTimeEntryImporter(ToRattail):
|
||||||
model_class = model.HarvestCacheTimeEntry
|
model_class = model.HarvestTimeEntry
|
||||||
|
|
||||||
# flags to auto-create records for "unknown" references
|
|
||||||
auto_create_unknown_project = True
|
|
||||||
|
|
||||||
# flags to log warning vs. debug for "unknown" references
|
|
||||||
warn_for_unknown_project = True
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
super().setup()
|
|
||||||
model = self.model
|
|
||||||
|
|
||||||
if 'project_id' in self.fields:
|
|
||||||
self.harvest_projects_by_id = self.app.cache_model(
|
|
||||||
self.session, model.HarvestCacheProject, key='id')
|
|
||||||
|
|
||||||
def cache_query(self):
|
def cache_query(self):
|
||||||
query = super().cache_query()
|
query = super(HarvestTimeEntryImporter, self).cache_query()
|
||||||
|
return query.filter(self.model_class.spent_date >= self.start_date)\
|
||||||
if self.start_date:
|
.filter(self.model_class.spent_date <= self.end_date)
|
||||||
query = query.filter(self.model_class.spent_date >= self.start_date)
|
|
||||||
if self.end_date:
|
|
||||||
query = query.filter(self.model_class.spent_date <= self.end_date)
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
||||||
def get_harvest_project(self, project_id):
|
|
||||||
if hasattr(self, 'harvest_projects_by_id'):
|
|
||||||
return self.harvest_projects_by_id.get(project_id)
|
|
||||||
|
|
||||||
model = self.model
|
|
||||||
return self.session.query(model.HarvestCacheProject)\
|
|
||||||
.filter(model.HarvestCacheProject.id == project_id)\
|
|
||||||
.first()
|
|
||||||
|
|
||||||
def update_object(self, entry, data, local_data=None):
|
|
||||||
entry = super().update_object(entry, data, local_data)
|
|
||||||
model = self.model
|
|
||||||
|
|
||||||
if 'project_id' in self.fields:
|
|
||||||
project_id = data['project_id']
|
|
||||||
project = self.get_harvest_project(project_id)
|
|
||||||
if not project:
|
|
||||||
logger = log.warning if self.warn_for_unknown_project else log.debug
|
|
||||||
logger("unknown project id %s for time entry id %s: %s",
|
|
||||||
project_id, entry.id, entry)
|
|
||||||
if self.auto_create_unknown_project:
|
|
||||||
project = model.HarvestCacheProject()
|
|
||||||
project.id = project_id
|
|
||||||
project.name = "(unknown)"
|
|
||||||
self.session.add(project)
|
|
||||||
if hasattr(self, 'harvest_projects_by_id'):
|
|
||||||
self.harvest_projects_by_id[project_id] = project
|
|
||||||
elif entry.project_id:
|
|
||||||
entry.project_id = None
|
|
||||||
|
|
||||||
return entry
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -34,11 +34,11 @@ class FromRattailToRattailHarvestMixin(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add_harvest_importers(self, importers):
|
def add_harvest_importers(self, importers):
|
||||||
importers['HarvestCacheUser'] = HarvestCacheUserImporter
|
importers['HarvestUser'] = HarvestUserImporter
|
||||||
importers['HarvestCacheClient'] = HarvestCacheClientImporter
|
importers['HarvestClient'] = HarvestClientImporter
|
||||||
importers['HarvestCacheProject'] = HarvestCacheProjectImporter
|
importers['HarvestProject'] = HarvestProjectImporter
|
||||||
importers['HarvestCacheTask'] = HarvestCacheTaskImporter
|
importers['HarvestTask'] = HarvestTaskImporter
|
||||||
importers['HarvestCacheTimeEntry'] = HarvestCacheTimeEntryImporter
|
importers['HarvestTimeEntry'] = HarvestTimeEntryImporter
|
||||||
return importers
|
return importers
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,26 +46,17 @@ class FromRattailToRattailHarvestMixin(object):
|
||||||
# harvest cache models
|
# harvest cache models
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
class HarvestCacheUserImporter(base.FromRattail, rattail_harvest_importing.model.HarvestCacheUserImporter):
|
class HarvestUserImporter(base.FromRattail, rattail_harvest_importing.model.HarvestUserImporter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HarvestCacheClientImporter(base.FromRattail, rattail_harvest_importing.model.HarvestCacheClientImporter):
|
class HarvestClientImporter(base.FromRattail, rattail_harvest_importing.model.HarvestClientImporter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HarvestCacheProjectImporter(base.FromRattail, rattail_harvest_importing.model.HarvestCacheProjectImporter):
|
class HarvestProjectImporter(base.FromRattail, rattail_harvest_importing.model.HarvestProjectImporter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HarvestCacheTaskImporter(base.FromRattail, rattail_harvest_importing.model.HarvestCacheTaskImporter):
|
class HarvestTaskImporter(base.FromRattail, rattail_harvest_importing.model.HarvestTaskImporter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HarvestCacheTimeEntryImporter(base.FromRattail, rattail_harvest_importing.model.HarvestCacheTimeEntryImporter):
|
class HarvestTimeEntryImporter(base.FromRattail, rattail_harvest_importing.model.HarvestTimeEntryImporter):
|
||||||
|
pass
|
||||||
def query(self):
|
|
||||||
query = super().query()
|
|
||||||
|
|
||||||
if self.start_date:
|
|
||||||
query = query.filter(self.model_class.spent_date >= self.start_date)
|
|
||||||
if self.end_date:
|
|
||||||
query = query.filter(self.model_class.spent_date <= self.end_date)
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# Rattail -- Retail Software Framework
|
|
||||||
# Copyright © 2010-2023 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/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
Rattail -> Rattail "versions" data import
|
|
||||||
"""
|
|
||||||
|
|
||||||
from rattail.importing import versions as base
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestVersionMixin(object):
|
|
||||||
|
|
||||||
def add_harvest_importers(self, importers):
|
|
||||||
importers['HarvestCacheUser'] = HarvestCacheUserImporter
|
|
||||||
importers['HarvestCacheClient'] = HarvestCacheClientImporter
|
|
||||||
importers['HarvestCacheProject'] = HarvestCacheProjectImporter
|
|
||||||
importers['HarvestCacheTask'] = HarvestCacheTaskImporter
|
|
||||||
importers['HarvestCacheTimeEntry'] = HarvestCacheTimeEntryImporter
|
|
||||||
return importers
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheUserImporter(base.VersionImporter):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_model_class(self):
|
|
||||||
return self.model.HarvestCacheUser
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheClientImporter(base.VersionImporter):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_model_class(self):
|
|
||||||
return self.model.HarvestCacheClient
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheProjectImporter(base.VersionImporter):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_model_class(self):
|
|
||||||
return self.model.HarvestCacheProject
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTaskImporter(base.VersionImporter):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_model_class(self):
|
|
||||||
return self.model.HarvestCacheTask
|
|
||||||
|
|
||||||
|
|
||||||
class HarvestCacheTimeEntryImporter(base.VersionImporter):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host_model_class(self):
|
|
||||||
return self.model.HarvestCacheTimeEntry
|
|
106
setup.py
Normal file
106
setup.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# -*- 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/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
rattail-harvest setup script
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
exec(open(os.path.join(here, 'rattail_harvest', '_version.py')).read())
|
||||||
|
README = open(os.path.join(here, 'README.rst')).read()
|
||||||
|
|
||||||
|
|
||||||
|
requires = [
|
||||||
|
#
|
||||||
|
# Version numbers within comments below have specific meanings.
|
||||||
|
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
|
||||||
|
# In other words:
|
||||||
|
#
|
||||||
|
# If either a 'low' or 'high' value exists, the primary point to be
|
||||||
|
# made about the value is that it represents the most current (stable)
|
||||||
|
# version available for the package (assuming typical public access
|
||||||
|
# methods) whenever this project was started and/or documented.
|
||||||
|
# Therefore:
|
||||||
|
#
|
||||||
|
# If a 'low' version is present, you should know that attempts to use
|
||||||
|
# versions of the package significantly older than the 'low' version
|
||||||
|
# may not yield happy results. (A "hard" high limit may or may not be
|
||||||
|
# indicated by a true version requirement.)
|
||||||
|
#
|
||||||
|
# Similarly, if a 'high' version is present, and especially if this
|
||||||
|
# project has laid dormant for a while, you may need to refactor a bit
|
||||||
|
# when attempting to support a more recent version of the package. (A
|
||||||
|
# "hard" low limit should be indicated by a true version requirement
|
||||||
|
# when a 'high' version is present.)
|
||||||
|
#
|
||||||
|
# In any case, developers and other users are encouraged to play
|
||||||
|
# outside the lines with regard to these soft limits. If bugs are
|
||||||
|
# encountered then they should be filed as such.
|
||||||
|
#
|
||||||
|
# package # low high
|
||||||
|
|
||||||
|
'invoke', # 1.5.0
|
||||||
|
'rattail[db]', # 0.9.246
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = "rattail-harvest",
|
||||||
|
version = __version__,
|
||||||
|
author = "Lance Edgar",
|
||||||
|
author_email = "lance@edbob.org",
|
||||||
|
url = "https://rattailproject.org/",
|
||||||
|
description = "Rattail integration package for Harvest",
|
||||||
|
long_description = README,
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
'Development Status :: 3 - Alpha',
|
||||||
|
'Environment :: Console',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Topic :: Office/Business',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
|
],
|
||||||
|
|
||||||
|
install_requires = requires,
|
||||||
|
packages = find_packages(),
|
||||||
|
include_package_data = True,
|
||||||
|
|
||||||
|
entry_points = {
|
||||||
|
|
||||||
|
'rattail.commands': [
|
||||||
|
'import-harvest = rattail_harvest.commands:ImportHarvest',
|
||||||
|
],
|
||||||
|
|
||||||
|
'rattail.importing': [
|
||||||
|
'to_rattail.from_harvest.import = rattail_harvest.importing.harvest:FromHarvestToRattail',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
26
tasks.py
26
tasks.py
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2024 Lance Edgar
|
# Copyright © 2010-2022 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -25,36 +25,24 @@ Tasks for rattail-harvest
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from invoke import task
|
from invoke import task
|
||||||
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
__version__ = None
|
exec(open(os.path.join(here, 'rattail_harvest', '_version.py')).read())
|
||||||
pattern = re.compile(r'^version = "(\d+\.\d+\.\d+)"$')
|
|
||||||
with open(os.path.join(here, 'pyproject.toml'), 'rt') as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.rstrip('\n')
|
|
||||||
match = pattern.match(line)
|
|
||||||
if match:
|
|
||||||
__version__ = match.group(1)
|
|
||||||
break
|
|
||||||
if not __version__:
|
|
||||||
raise RuntimeError("could not parse version!")
|
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def release(c):
|
def release(ctx):
|
||||||
"""
|
"""
|
||||||
Release a new version of rattail-harvest
|
Release a new version of rattail-harvest
|
||||||
"""
|
"""
|
||||||
# rebuild local tar.gz file for distribution
|
# rebuild local tar.gz file for distribution
|
||||||
if os.path.exists('rattail_harvest.egg-info'):
|
shutil.rmtree('rattail_harvest.egg-info')
|
||||||
shutil.rmtree('rattail_harvest.egg-info')
|
ctx.run('python setup.py sdist --formats=gztar')
|
||||||
c.run('python -m build --sdist')
|
|
||||||
|
|
||||||
# upload to public PyPI
|
# upload to public PyPI
|
||||||
filename = f'rattail_harvest-{__version__}.tar.gz'
|
filename = 'rattail-harvest-{}.tar.gz'.format(__version__)
|
||||||
c.run(f'twine upload dist/{filename}')
|
ctx.run('twine upload dist/{}'.format(filename))
|
||||||
|
|
Loading…
Reference in a new issue