Changeset - d436dd22a26c
[Not reviewed]
0 3 3
Lance Edgar (lance) - 2 years ago 2022-08-09 22:55:59
lance@edbob.org
Add basic model, handler for work orders

much more to come later i'm sure
6 files changed with 426 insertions and 2 deletions:
0 comments (0 inline, 0 general)
rattail/app.py
Show inline comments
 
@@ -861,6 +861,20 @@ class AppHandler(object):
 
            self.vendor_handler = factory(self.config, **kwargs)
 
        return self.vendor_handler
 

	
 
    def get_workorder_handler(self, **kwargs):
 
        """
 
        Get the configured "work order" handler.
 

	
 
        :returns: The :class:`~rattail.workorders.WorkOrderHandler`
 
           instance for the app.
 
        """
 
        if not hasattr(self, 'workorder_handler'):
 
            spec = self.config.get('rattail', 'workorders.handler',
 
                                   default='rattail.workorders:WorkOrderHandler')
 
            Handler = self.load_object(spec)
 
            self.workorder_handler = Handler(self.config)
 
        return self.workorder_handler
 

	
 
    def progress_loop(self, *args, **kwargs):
 
        """
 
        Run a given function for a given sequence, and optionally show
rattail/db/alembic/versions/62cfde95d655_add_work_orders.py
Show inline comments
 
new file 100644
 
# -*- coding: utf-8; -*-
 
"""add work orders
 

	
 
Revision ID: 62cfde95d655
 
Revises: 9c111f4b5451
 
Create Date: 2022-08-09 19:43:01.326910
 

	
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
# revision identifiers, used by Alembic.
 
revision = '62cfde95d655'
 
down_revision = '9c111f4b5451'
 
branch_labels = None
 
depends_on = None
 

	
 
from alembic import op
 
import sqlalchemy as sa
 
import rattail.db.types
 

	
 

	
 
workorder_id_seq = sa.Sequence('workorder_id_seq')
 

	
 

	
 
def upgrade():
 
    from sqlalchemy.schema import CreateSequence
 

	
 
    # id sequence
 
    op.execute(CreateSequence(workorder_id_seq))
 

	
 
    # workorder
 
    op.create_table('workorder',
 
                    sa.Column('uuid', sa.String(length=32), nullable=False),
 
                    sa.Column('id', sa.Integer(), nullable=False),
 
                    sa.Column('customer_uuid', sa.String(length=32), nullable=False),
 
                    sa.Column('date_submitted', sa.Date(), nullable=True),
 
                    sa.Column('date_received', sa.Date(), nullable=True),
 
                    sa.Column('date_released', sa.Date(), nullable=True),
 
                    sa.Column('date_delivered', sa.Date(), nullable=True),
 
                    sa.Column('notes', sa.Text(), nullable=True),
 
                    sa.Column('status_code', sa.Integer(), nullable=False),
 
                    sa.Column('status_text', sa.String(length=255), nullable=True),
 
                    sa.ForeignKeyConstraint(['customer_uuid'], ['customer.uuid'], name='workorder_fk_customer'),
 
                    sa.PrimaryKeyConstraint('uuid')
 
    )
 

	
 
    # workorder_event
 
    op.create_table('workorder_event',
 
                    sa.Column('uuid', sa.String(length=32), nullable=False),
 
                    sa.Column('workorder_uuid', sa.String(length=32), nullable=False),
 
                    sa.Column('type_code', sa.Integer(), nullable=False),
 
                    sa.Column('occurred', sa.DateTime(), nullable=False),
 
                    sa.Column('user_uuid', sa.String(length=32), nullable=False),
 
                    sa.Column('note', sa.Text(), nullable=True),
 
                    sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], name='workorder_event_fk_user'),
 
                    sa.ForeignKeyConstraint(['workorder_uuid'], ['workorder.uuid'], name='workorder_event_fk_workorder'),
 
                    sa.PrimaryKeyConstraint('uuid')
 
    )
 

	
 

	
 
def downgrade():
 
    from sqlalchemy.schema import DropSequence
 

	
 
    # workorder_event
 
    op.drop_table('workorder_event')
 

	
 
    # workorder
 
    op.drop_table('workorder')
 

	
 
    # id sequence
 
    op.execute(DropSequence(workorder_id_seq))
rattail/db/model/__init__.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2021 Lance Edgar
 
#  Copyright © 2010-2022 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -63,6 +63,8 @@ from .custorders import (CustomerOrderBase, CustomerOrderItemBase,
 
                         CustomerOrder, CustomerOrderItem,
 
                         CustomerOrderItemEvent, CustomerOrderItemNote)
 

	
 
from .workorders import WorkOrder, WorkOrderEvent
 

	
 
from .messages import Message, MessageRecipient
 

	
 
from .datasync import DataSyncChange
rattail/db/model/workorders.py
Show inline comments
 
new file 100644
 
# -*- 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/>.
 
#
 
################################################################################
 
"""
 
Data Models for Work Orders
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import sqlalchemy as sa
 
from sqlalchemy import orm
 

	
 
from rattail.db.model import Base, uuid_column
 
from rattail.time import make_utc
 

	
 

	
 
class WorkOrder(Base):
 
    """
 
    Represents a generic work order for a customer.
 
    """
 
    __tablename__ = 'workorder'
 
    __table_args__ = (
 
        sa.ForeignKeyConstraint(['customer_uuid'], ['customer.uuid'], 
 
                                name='workorder_fk_customer'),
 
    )
 

	
 
    uuid = uuid_column()
 

	
 
    id = sa.Column(sa.Integer(), nullable=False, doc="""
 
    Numeric ID for the work order.
 
    """)
 

	
 
    customer_uuid = sa.Column(sa.String(length=32), nullable=False)
 
    customer = orm.relationship(
 
        'Customer',
 
        doc="""
 
        Reference to the customer who requested the work.
 
        """,
 
        backref=orm.backref(
 
            'workorders',
 
            doc="""
 
            Sequence of all work orders for this customer.
 
            """))
 

	
 
    date_submitted = sa.Column(sa.Date(), nullable=True, doc="""
 
    Date on which the work order was first "submitted" by the customer.
 
    """)
 

	
 
    date_received = sa.Column(sa.Date(), nullable=True, doc="""
 
    Date on which the org received the work order from the customer.
 
    """)
 

	
 
    date_released = sa.Column(sa.Date(), nullable=True, doc="""
 
    Date on which the org "released" (e.g. mailed) the work result
 
    back to the customer.
 
    """)
 

	
 
    date_delivered = sa.Column(sa.Date(), nullable=True, doc="""
 
    Date on which the work result was truly delivered back to the
 
    customer.
 
    """)
 

	
 
    notes = sa.Column(sa.Text(), nullable=True, doc="""
 
    Extra notes about the work order.
 
    """)
 

	
 
    status_code = sa.Column(sa.Integer(), nullable=False, doc="""
 
    Status code for the work order.
 
    """)
 

	
 
    status_text = sa.Column(sa.String(length=255), nullable=True, doc="""
 
    Text which may briefly explain the status code, if needed.
 
    """)
 

	
 
    def __str__(self):
 
        return "#{} for {}".format(self.id, self.customer)
 

	
 
    @property
 
    def id_str(self):
 
        if not self.id:
 
            return ''
 

	
 
        from rattail.batch import batch_id_str
 
        return batch_id_str(self.id)
 

	
 

	
 
class WorkOrderEvent(Base):
 
    """
 
    An event in the life of a work order
 
    """
 
    __tablename__ = 'workorder_event'
 
    __table_args__ = (
 
        sa.ForeignKeyConstraint(['workorder_uuid'], ['workorder.uuid'],
 
                                name='workorder_event_fk_workorder'),
 
        sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'],
 
                                name='workorder_event_fk_user'),
 
    )
 

	
 
    uuid = uuid_column()
 

	
 
    workorder_uuid = sa.Column(sa.String(length=32), nullable=False)
 
    workorder = orm.relationship(
 
        WorkOrder,
 
        doc="""
 
        Reference to the :class:`CustomerOrder` instance to which the item belongs.
 
        """,
 
        backref=orm.backref(
 
            'events',
 
            order_by='WorkOrderEvent.occurred',
 
            cascade='all, delete-orphan'))
 

	
 
    type_code = sa.Column(sa.Integer, nullable=False, doc="""
 
    Code specifying the type of event this is.
 
    """)
 

	
 
    occurred = sa.Column(sa.DateTime(), nullable=False, default=make_utc, doc="""
 
    Date and time when the event occurred.
 
    """)
 

	
 
    user_uuid = sa.Column(sa.String(length=32), nullable=False)
 
    user = orm.relationship(
 
        'User',
 
        doc="""
 
        User who was the "actor" for the event.
 
        """)
 

	
 
    note = sa.Column(sa.Text(), nullable=True, doc="""
 
    Optional note recorded for the event.
 
    """)
rattail/enum.py
Show inline comments
 
@@ -2,7 +2,7 @@
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2021 Lance Edgar
 
#  Copyright © 2010-2022 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
@@ -172,6 +172,52 @@ CUSTORDER_ITEM_EVENT = OrderedDict([
 
])
 

	
 

	
 
# WORKORDER_STATUS_CREATED                 = 1
 
WORKORDER_STATUS_SUBMITTED               = 10
 
WORKORDER_STATUS_RECEIVED                = 20
 
WORKORDER_STATUS_PENDING_ESTIMATE        = 30
 
WORKORDER_STATUS_WAITING_FOR_PARTS       = 40
 
WORKORDER_STATUS_WORKING_ON_IT           = 50
 
WORKORDER_STATUS_RELEASED                = 60
 
WORKORDER_STATUS_DELIVERED               = 70
 
WORKORDER_STATUS_CANCELED                = 99
 

	
 
WORKORDER_STATUS = OrderedDict([
 
    # (WORKORDER_STATUS_CREATED,           "created"),
 
    (WORKORDER_STATUS_SUBMITTED,         "submitted by customer"),
 
    (WORKORDER_STATUS_RECEIVED,          "received from customer"),
 
    (WORKORDER_STATUS_PENDING_ESTIMATE,  "pending estimate approval"),
 
    (WORKORDER_STATUS_WAITING_FOR_PARTS, "waiting for parts"),
 
    (WORKORDER_STATUS_WORKING_ON_IT,     "working on it"),
 
    (WORKORDER_STATUS_RELEASED,          "released"),
 
    (WORKORDER_STATUS_DELIVERED,         "delivered"),
 
    (WORKORDER_STATUS_CANCELED,          "canceled"),
 
])
 

	
 

	
 
# WORKORDER_EVENT_CREATED                 = 1
 
WORKORDER_EVENT_SUBMITTED               = 10
 
WORKORDER_EVENT_RECEIVED                = 20
 
WORKORDER_EVENT_PENDING_ESTIMATE        = 30
 
WORKORDER_EVENT_WAITING_FOR_PARTS       = 40
 
WORKORDER_EVENT_WORKING_ON_IT           = 50
 
WORKORDER_EVENT_RELEASED                = 60
 
WORKORDER_EVENT_DELIVERED               = 70
 
WORKORDER_EVENT_CANCELED                = 99
 

	
 
WORKORDER_EVENT = OrderedDict([
 
    # (WORKORDER_EVENT_CREATED,           "created"),
 
    (WORKORDER_EVENT_SUBMITTED,         "submitted by customer"),
 
    (WORKORDER_EVENT_RECEIVED,          "received from customer"),
 
    (WORKORDER_EVENT_PENDING_ESTIMATE,  "pending estimate approval"),
 
    (WORKORDER_EVENT_WAITING_FOR_PARTS, "waiting for parts"),
 
    (WORKORDER_EVENT_WORKING_ON_IT,     "working on it"),
 
    (WORKORDER_EVENT_RELEASED,          "released"),
 
    (WORKORDER_EVENT_DELIVERED,         "delivered"),
 
    (WORKORDER_EVENT_CANCELED,          "canceled"),
 
])
 

	
 

	
 
EMAIL_ATTEMPT_CREATED           = 0
 
EMAIL_ATTEMPT_SUCCESS           = 1
 
EMAIL_ATTEMPT_FAILURE           = 2
rattail/workorders.py
Show inline comments
 
new file 100644
 
# -*- 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/>.
 
#
 
################################################################################
 
"""
 
Work Order Handler
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
from rattail.app import GenericHandler
 

	
 

	
 
class WorkOrderHandler(GenericHandler):
 
    """
 
    Handler for work orders.
 
    """
 

	
 
    def make_workorder(self, session, **kwargs):
 
        """
 
        Make and return a new work order.
 
        """
 
        model = self.model
 

	
 
        if 'id' not in kwargs:
 
            kwargs['id'] = self.app.next_counter_value(session, 'workorder_id')
 

	
 
        if 'date_submitted' not in kwargs:
 
            kwargs['date_submitted'] = self.app.today()
 

	
 
        if 'status_code' not in kwargs:
 
            kwargs['status_code'] = self.enum.WORKORDER_STATUS_SUBMITTED
 

	
 
        workorder = model.WorkOrder(**kwargs)
 
        session.add(workorder)
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_SUBMITTED)
 
        session.flush()
 
        return workorder
 

	
 
    def status_codes(self):
 
        """
 
        Retrieve all info about possible work order status codes.
 
        """
 
        code_names = {}
 
        for name in dir(self.enum):
 
            if name.startswith('WORKORDER_STATUS_'):
 
                code_names[getattr(self.enum, name)] = name
 

	
 
        status_codes = []
 
        for key, label in self.enum.WORKORDER_STATUS.items():
 
            status_codes.append({
 
                'code': key,
 
                'code_name': code_names[key],
 
                'label': label,
 
            })
 

	
 
        return status_codes
 

	
 
    def record_event(self, workorder, type_code, **kwargs):
 
        model = self.model
 

	
 
        # who did this?
 
        if 'user' in kwargs:
 
            user = kwargs['user']
 
        else:
 
            session = kwargs.get('session') or self.app.get_session(workorder)
 
            user = session.continuum_user
 
        kwargs['user'] = user
 

	
 
        # record the event
 
        workorder.events.append(model.WorkOrderEvent(
 
            type_code=type_code, **kwargs))
 

	
 
    def receive(self, workorder, **kwargs):
 
        """
 
        Sets work order status to "received".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_RECEIVED
 
        workorder.date_received = self.app.today()
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_RECEIVED)
 

	
 
    def await_estimate(self, workorder):
 
        """
 
        Sets work order status to "awaiting estimate confirmation".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_PENDING_ESTIMATE
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_PENDING_ESTIMATE)
 

	
 
    def await_parts(self, workorder):
 
        """
 
        Sets work order status to "awaiting parts".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_WAITING_FOR_PARTS
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_WAITING_FOR_PARTS)
 

	
 
    def work_on_it(self, workorder):
 
        """
 
        Sets work order status to "working on it".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_WORKING_ON_IT
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_WORKING_ON_IT)
 

	
 
    def release(self, workorder):
 
        """
 
        Sets work order status to "released".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_RELEASED
 
        workorder.date_released = self.app.today()
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_RELEASED)
 

	
 
    def deliver(self, workorder):
 
        """
 
        Sets work order status to "delivered".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_DELIVERED
 
        workorder.date_delivered = self.app.today()
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_DELIVERED)
 

	
 
    def cancel(self, workorder):
 
        """
 
        Sets work order status to "canceled".
 
        """
 
        workorder.status_code = self.enum.WORKORDER_STATUS_CANCELED
 
        self.record_event(workorder, self.enum.WORKORDER_EVENT_CANCELED)
0 comments (0 inline, 0 general)