"""A document type that provides an interface to a houseaccts DB""" __author__ = '"Nathaniel W. Turner" ' __docformat__ = 'plaintext' from AccessControl import ClassSecurityInfo, ModuleSecurityInfo try: from Products.LinguaPlone.public import * except ImportError: # No multilingual support from Products.Archetypes.public import * from Products.HouseAcctsView.interfaces import IHouseAcctsView from Products.ATContentTypes.content.base import updateActions from Products.ATContentTypes.content.base import updateAliases from Products.ATContentTypes.content.document import ATDocument from Products.ATContentTypes.content.document import finalizeATCTSchema from Products.CMFCore.permissions import View from Products.CMFCore.permissions import ModifyPortalContent from Products.CMFCore.permissions import ManageProperties from DateTime import DateTime import datetime import time import houseaccts.core import re security = ModuleSecurityInfo('Products.HouseAcctsView.content') # create a new permission category security.declarePublic('AddTransaction') AddTransaction = 'HouseAcctsView: Add transaction' # set up the schema HouseAcctsViewSchema = ATDocument.schema.copy() + Schema(( StringField('dsn', required=True, default="", languageIndependent=1, widget=StringWidget( label="DB Connection String", description=""" a PostgreSQL database connection string; usually something like "dbname=foo" """), ), BooleanField('restrict_post_dates', default=True, widget=BooleanWidget( label="Restrict Post Dates", description=""" If selected, new transactions will be required to have a post date within the current month. """), ), ),) # Finalise the schema according to ATContentTypes standards. This basically # moves the Related items and Allow discussion fields to the bottom of the # form. See ATContentTypes.content.schemata for details. finalizeATCTSchema(HouseAcctsViewSchema) class HouseAcctsView(ATDocument): """ A content type that provides a gateway to a houseaccts database """ # portal_type = meta_type = 'HouseAcctsView' archetype_name = 'House accounting gateway' content_icon = 'HouseAcctsView.png' schema = HouseAcctsViewSchema typeDescription= 'A gateway to a houseaccts database' typeDescMsgId = 'HouseAcctsView_description_edit' # Set up our views - these are available from the 'display' menu default_view = 'houseaccts_view' immediate_view = 'houseaccts_view' # Make sure we get title-to-id generation when an object is created _at_rename_after_creation = True # Make sure we get all the interface declarations from ATDocument, # which includes support for ISelectableBrowserDefault to get the # 'display' menu to work, IHistoryAware and other standard interfaces. __implements__ = ATDocument.__implements__ + (IHouseAcctsView,) #security = ClassSecurityInfo() actions = updateActions(ATDocument, ({ 'id': 'new_txn', 'name': 'New Transaction', 'action': 'string:${object_url}/newtxn', 'permissions': (View,) }, { 'id': 'statements', 'name': 'Statements', 'action': 'string:${object_url}/statement', 'permissions': (View,) }, # { # 'id': 'edit', # 'name': 'Configuration', # 'action': 'string:${object_url}/edit', # 'permissions': (ManageProperties,) # }, )) aliases = updateAliases(ATDocument, { 'txn': 'houseaccts_txn_detail', 'newtxn': 'houseaccts_txn_form', 'statement': 'houseaccts_statement', }) # no columns of "portlets" when using this app #left_slots = "" right_slots = "" def _getAccts(self): return houseaccts.core.HouseAccts(self.dsn) # security.declareProtected(View, "getUser") def getUser(self, id): """Return a single user""" return self._getAccts().getUser(id) # security.declareProtected(View, "getUsers") def getUsers(self): """Return list of users""" return self._getAccts().getUsers() # security.declareProtected(View, "getActiveUsers") def getActiveUsers(self): """Return list of users""" return self._getAccts().getUsers(active_only=True) # security.declareProtected(View, "getVisibleUsers") def getVisibleUsers(self): """Return list of users""" return self._getAccts().getUsers(visible_only=True) # security.declareProtected(View, "getBalances") def getBalances(self): """ Returns balances for each user """ return self._getAccts().getBalances() # security.declareProtected(View, "getOverviewDays") def getOverviewDays(self): if self.REQUEST.form.has_key('d'): return int(self.REQUEST.form['d']) return 365 # security.declareProtected(View, "getRecentTransactions") def getRecentTransactions(self): """ Returns a list of transactions """ print("DeprecationWarning: getRecentTransactions is deprecated; use " + "getTransactionsForView instead.") return self._getAccts().getTransactionsForView() def getTransactionsForView(self): """Return a list of transactions""" otxnlist = [] filter = self.REQUEST.get('filter', None) start = None if filter is None: days = self.getOverviewDays() start = datetime.date.today() - datetime.timedelta(days=days) for txn in self._getAccts().getTransactions(start=start, filter=filter): otxn = {} for f in ('id', 'posted', 'posted_date', 'description', 'creator', 'splits'): otxn[f] = txn[f] paidby = {} usedby = {} totalpaid = 0 for s in txn['splits']: if s['debit_amt'] < 0: if not paidby.has_key(s['user']): paidby[s['user']] = 0 paidby[s['user']] -= s['debit_amt'] totalpaid -= s['debit_amt'] else: if not usedby.has_key(s['user']): usedby[s['user']] = 0 usedby[s['user']] += s['debit_amt'] otxn['paidby'] = [(x, paidby[x]) for x in paidby.keys()] otxn['usedby'] = [(x, usedby[x]) for x in usedby.keys()] otxn['totalpaid'] = totalpaid otxnlist.append(otxn) otxnlist.reverse() return otxnlist # security.declareProtected(View, "getTransaction") def getTransaction(self, id): """Return a single transaction""" return self._getAccts().getTransaction(id) # security.declareProtected(View, "getEarliestTxnDate") def getEarliestTxnDate(self, user): """Return the date of the earliest transaction for user""" etd = DateTime(str(self._getAccts().getEarliestTxnDate(user))) return etd.parts()[:2] # security.declareProtected(View, "getStatementYM") def getStatementYM(self): """Return the year and month of the currently requested statement""" if self.REQUEST.form.has_key('m'): return [int(x) for x in self.REQUEST.form['m'].split('-')[:2]] today = DateTime(time.time()) return today.parts()[:2] # security.declareProtected(View, "getTodayTuple") def getTodayTuple(self): today = DateTime(time.time()) return today.parts()[:3] # security.declareProtected(View, "getStatement") def getStatement(self, user): """Return a statement for user""" year, month = self.getStatementYM() return self._getAccts().getStatement(user, year, month) # security.declareProtected(View, "SearchableText") def SearchableText(self): """ Used by the catalog for basic full text indexing """ return "%s %s" % (self.Title(), self.Description()) # security.declareProtected(AddTransaction, "addTransaction") def addTransaction(self, description=None, amount=None, buyer=None, users=None, post_date=None): """Add a new transaction""" accts = self._getAccts() user = self.REQUEST.get('AUTHENTICATED_USER', None) creator = user.getUserName() if (reduce(lambda a,b: a or b, [u[1] for u in users])): txn = houseaccts.core.newWeightedSplitTxn(creator=creator, description=description, amount=amount, buyer=buyer, users=users, post_date=post_date) else: txn = houseaccts.core.newSplitPurchaseTxn(creator=creator, description=description, amount=amount, buyer=buyer, users=[u[0] for u in users], post_date=post_date) accts.postTransaction(txn) def today_date(self): return datetime.date.today() def earliest_new_txn_date(self): return datetime.date.today().replace(day=1) def validate_new_txn_date(self, str): if re.compile(r'\d{4}-\d{1,2}-\d{1,2}').match(str) == None: return (False, "Post Date must be in YYYY-M-D format.") try: year, month, day = [int(x) for x in str.split('-')] thedate = datetime.date(year, month, day) except ValueError: return (False, "Invalid date.") earliest = self.earliest_new_txn_date() if thedate < earliest: return (False, "Post Date must be %s or later." % earliest) return (True, None) registerType(HouseAcctsView)