# -*- coding: utf-8 -*-
# Stalker a Production Asset Management System
# Copyright (C) 2009-2014 Erkan Ozgur Yilmaz
#
# This file is part of Stalker.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from sqlalchemy import Column, Integer, ForeignKey, Float, String, Table
from sqlalchemy.orm import relationship, validates
from stalker.db import Base
from stalker.models.entity import Entity
from stalker.models.mixins import ProjectMixin, DAGMixin
[docs]class Good(Entity):
"""Manages commercial items that is served by the Studio.
A Studio can define service prices or items that's been sold by the Studio
by using a list of commercial items.
A Good has the following attributes
:param msrp: The suggested retail price for this item.
:param cost: The cost of this item to the Studio, so generally it is better
to keep price of the related BudgetEntry bigger than this value to get
profit by selling this item.
:param unit: The unit of this item.
"""
__auto_name__ = False
__tablename__ = 'Goods'
__mapper_args__ = {'polymorphic_identity': 'Good'}
good_id = Column(
'id',
Integer,
ForeignKey('Entities.id'),
primary_key=True
)
price_lists = relationship(
'PriceList',
secondary='PriceList_Goods',
primaryjoin='Goods.c.id==PriceList_Goods.c.good_id',
secondaryjoin='PriceList_Goods.c.price_list_id==PriceLists.c.id',
back_populates='goods',
doc='PriceLists that this good is related to.'
)
cost = Column(Float, default=0.0)
msrp = Column(Float, default=0.0)
unit = Column(String(64))
[docs] def __init__(self, cost=0.0, msrp=0.0, unit='', **kwargs):
super(Good, self).__init__(**kwargs)
self.cost = cost
self.msrp = msrp
self.unit = unit
@validates('cost')
def _validate_cost(self, key, cost):
"""validates the given cost value
"""
if cost is None:
cost = 0.0
if not isinstance(cost, (float, int)):
raise TypeError(
'%s.cost should be a non-negative number, not %s' % (
self.__class__.__name__,
cost.__class__.__name__
)
)
if cost < 0.0:
raise ValueError(
'%s.cost should be a non-negative number' %
self.__class__.__name__
)
return cost
@validates('msrp')
def _validate_msrp(self, key, msrp):
"""validates the given msrp value
"""
if msrp is None:
msrp = 0.0
if not isinstance(msrp, (float, int)):
raise TypeError(
'%s.msrp should be a non-negative number, not %s' % (
self.__class__.__name__,
msrp.__class__.__name__
)
)
if msrp < 0.0:
raise ValueError(
'%s.msrp should be a non-negative number' %
self.__class__.__name__
)
return msrp
@validates('unit')
def _validate_unit(self, key, unit):
"""validates the given unit value
"""
if unit is None:
unit = ''
from stalker import __string_types__
if not isinstance(unit, __string_types__):
raise TypeError(
'%s.unit should be a str, not %s' % (
self.__class__.__name__,
unit.__class__.__name__
)
)
return unit
[docs]class PriceList(Entity):
"""Contains CommercialItems to create a list of items that is sold by the
Studio.
You can create different lists for items sold in this studio.
"""
__auto_name__ = False
__tablename__ = 'PriceLists'
__mapper_args__ = {'polymorphic_identity': 'PriceList'}
price_list_id = Column(
'id',
Integer,
ForeignKey('Entities.id'),
primary_key=True
)
goods = relationship(
'Good',
secondary='PriceList_Goods',
primaryjoin='PriceLists.c.id==PriceList_Goods.c.price_list_id',
secondaryjoin='PriceList_Goods.c.good_id==Goods.c.id',
back_populates='price_lists',
doc='Goods in this list.'
)
[docs] def __init__(self, goods=None, **kwargs):
super(PriceList, self).__init__(**kwargs)
if goods is None:
goods = []
self.goods = goods
@validates('goods')
def _validate_goods(self, key, good):
"""validates the given good value
"""
from stalker import Good
if not isinstance(good, Good):
raise TypeError(
'%s.goods should be a list of stalker.model.bugdet.Good '
'instances, not %s' % (
self.__class__.__name__,
good.__class__.__name__
)
)
return good
PriceList_Goods = Table(
'PriceList_Goods', Base.metadata,
Column(
'price_list_id',
Integer,
ForeignKey('PriceLists.id'),
primary_key=True
),
Column(
'good_id',
Integer,
ForeignKey('Goods.id'),
primary_key=True
)
)
[docs]class Budget(Entity, ProjectMixin, DAGMixin):
"""Manages project budgets
Budgets manager :class:`.Project` budgets. You can create entries as
instances of :class:`.BudgetEntry` class.
"""
__auto_name__ = False
__tablename__ = "Budgets"
__mapper_args__ = {"polymorphic_identity": "Budget"}
budget_id = Column(
"id",
Integer,
ForeignKey("Entities.id"),
primary_key=True
)
__id_column__ = 'budget_id'
entries = relationship(
'BudgetEntry',
primaryjoin='BudgetEntries.c.budget_id==Budgets.c.id',
uselist=True,
cascade="all, delete-orphan"
)
[docs] def __init__(self, **kwargs):
super(Budget, self).__init__(**kwargs)
ProjectMixin.__init__(self, **kwargs)
DAGMixin.__init__(self, **kwargs)
@validates('entries')
def _validate_entry(self, key, entry):
"""validates the given entry value
"""
if not isinstance(entry, BudgetEntry):
raise TypeError(
'%(class)s.entries should be a list of BudgetEntry instances, '
'not %(entry_class)s' % {
'class': self.__class__.__name__,
'entry_class': entry.__class__.__name__
}
)
return entry
[docs]class BudgetEntry(Entity):
"""Manages budget entries in a budget
With budget entries one can manage project budget costs. Each entry shows
one component of a bigger budget.
:param float cost: This should be the direct copy of the Good.cost that
this item is related to.
:param float msrp: Again this should be the direct copy of the Good.msrp
that this item is related to.
:param float price: The decided price of this entry. This is generally
bigger than the cost and should be also bigger than msrp but the person
that is editing the the budget which this entry is related to can decide
to do a discount on this entry and give a different price. This attribute
holds the proposed final price.
:param float realized_total: This attribute is for holding the realized
price of this entry. It can be the same number of the :attr:`.price`
multiplied by the :attr:`.amount` or can be something else that reflects
the reality. Generally it is for calculating the "service" cost/profit.
:param str unit: The unit of this budget entry. Nothing so important for
now. Just a text like "$/hour". Stalker doesn't do any unit conversions
for now.
:param float amount: Defines the amount of :class:`Good` that is in
consideration for this entry.
"""
__auto_name__ = True
__tablename__ = "BudgetEntries"
__mapper_args__ = {"polymorphic_identity": "BudgetEntry"}
entry_id = Column(
"id",
Integer,
ForeignKey("Entities.id"),
primary_key=True
)
budget_id = Column(
'budget_id',
Integer,
ForeignKey('Budgets.id')
)
budget = relationship(
'Budget',
primaryjoin='BudgetEntries.c.budget_id==Budgets.c.id',
back_populates='entries',
uselist=False
)
cost = Column(Float, default=0.0)
msrp = Column(Float, default=0.0)
price = Column(Float, default=0.0)
realized_total = Column(Float, default=0.0)
unit = Column(String(64))
amount = Column(Float, default=0.0)
[docs] def __init__(self,
budget=None,
cost=0,
msrp=0,
price=0,
realized_total=0,
unit='',
amount=0.0,
**kwargs):
super(BudgetEntry, self).__init__(**kwargs)
self.budget = budget
self.cost = cost
self.msrp = msrp
self.price = price
self.realized_total = realized_total
self.amount = amount
self.unit = unit
@property
[docs] def total(self):
"""returns the total cost of this entry
"""
return self.amount * self.cost
@validates('budget')
def _validate_budget(self, key, budget):
"""validates the given budget value
"""
if not isinstance(budget, Budget):
raise TypeError(
'%s.budget should be a Budget instance, not %s' % (
self.__class__.__name__, budget.__class__.__name__
)
)
return budget
@validates('cost')
def _validate_cost(self, key, cost):
"""validates the given cost value
"""
if cost is None:
cost = 0.0
if not isinstance(cost, (int, float)):
raise TypeError(
'%s.cost should be a number, not %s' % (
self.__class__.__name__, cost.__class__.__name__
)
)
return float(cost)
@validates('msrp')
def _validate_msrp(self, key, msrp):
"""validates the given msrp value
"""
if msrp is None:
msrp = 0.0
if not isinstance(msrp, (int, float)):
raise TypeError(
'%s.msrp should be a number, not %s' % (
self.__class__.__name__, msrp.__class__.__name__
)
)
return float(msrp)
@validates('price')
def _validate_price(self, key, price):
"""validates the given price value
"""
if price is None:
price = 0.0
if not isinstance(price, (int, float)):
raise TypeError(
'%s.price should be a number, not %s' % (
self.__class__.__name__, price.__class__.__name__
)
)
return float(price)
@validates('realized_total')
def _validate_realized_total(self, key, realized_total):
"""validates the given realized_total value
"""
if realized_total is None:
realized_total = 0.0
if not isinstance(realized_total, (int, float)):
raise TypeError(
'%s.realized_total should be a number, not %s' % (
self.__class__.__name__,
realized_total.__class__.__name__
)
)
return float(realized_total)
@validates('unit')
def _validate_unit(self, key, unit):
"""validates the given unit value
"""
if unit is None:
unit = ''
from stalker import __string_types__
if not isinstance(unit, __string_types__):
raise TypeError(
'%s.unit should be a string, not %s' % (
self.__class__.__name__, unit.__class__.__name__
)
)
return unit
@validates('amount')
def _validate_amount(self, key, amount):
"""validates the given amount value
"""
if amount is None:
amount = 0.0
if not isinstance(amount, (int, float)):
raise TypeError(
'%s.amount should be a number, not %s' % (
self.__class__.__name__, amount.__class__.__name__
)
)
return float(amount)