#!/usr/bin/env python
# encoding: utf-8
# Copyright (C) 2017 Chintalagiri Shashank
#
# This file is part of tendril-connector-tally.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Tally Stock Masters and Positions
---------------------------------
"""
from lxml import etree
from warnings import warn
from .utils.converters import TXBoolean
from .utils.converters import TXString
from .utils.converters import TXDecimal
from .utils.converters import TXMultilineString
from . import TallyReport
from . import TallyRequestHeader
from . import TallyNotAvailable
from . import TallyElement
from . import ledgers
[docs]class TallyStockGroup(TallyElement):
# NOTE All masters has more fields?
attrs = {
'name': ('name', TXString(required=True), True),
'reservedname': ('reservedname', TXString(), False),
}
descendent_elements = {
'extendedname': ('name.list', TXMultilineString(required=True), True),
}
elements = {
'_parent': ('parent', TXString(), True),
'narration': ('narration', TXString(), True),
'costingmethod': ('costingmethod', TXString(), True),
'valuationmethod': ('valuationmethod', TXString(), True),
'_baseunits': ('baseunits', TXString(), True),
'_additionalunits': ('additionalunits', TXString(), True),
'isbatchwiseon': ('isbatchwiseon', TXBoolean(), True),
'isperishableon': ('isperishableon', TXBoolean(), True),
'isaddable': ('isaddable', TXBoolean(), True),
'ignorephysicaldifference': ('ignorephysicaldifference', TXBoolean(), True),
'ignorenegativestock': ('ignorenegativestock', TXBoolean(), True),
'treatsalesasmanufactured': ('treatsalesasmanufactured', TXBoolean(), True),
'treatpurchasesasconsumed': ('treatpurchasesasconsumed', TXBoolean(), True),
'treatrejectsasscrap': ('treatrejectsasscrap', TXBoolean(), True),
'hasmfgdate': ('hasmfgdate', TXBoolean(), True),
'allowuseofexpireditems': ('allowuseofexpireditems', TXBoolean(), True),
'ignorebatches': ('ignorebatches', TXBoolean(), True),
'ignoregodowns': ('ignoregodowns', TXBoolean(), True),
}
@property
def parent(self):
if self._parent and self._parent != self.name:
return self.company_masters.stockgroups[self._parent]
@property
def path(self):
if self.parent and self.parent.path:
return self.parent.path + [self.name]
else:
return [self.name]
@property
def baseunits(self):
if self._baseunits:
return self.company_masters.units[self._baseunits]
@property
def additionalunits(self):
if self._additionalunits:
return self.company_masters.units[self._additionalunits]
def __repr__(self):
return "<TallyStockGroup {0}>".format(self.name)
[docs]class TallyStockCategory(TallyElement):
# NOTE All masters has more fields?
descendent_elements = {
'name': ('name', TXString(required=True), True),
}
elements = {
'_parent': ('parent', TXString(), True),
'narration': ('narration', TXString(), True),
}
@property
def parent(self):
if self._parent and self._parent != self.name:
return self.company_masters.stockcategories[self._parent]
def __repr__(self):
return "<TallyStockCategory {0}>".format(self.name)
[docs]class TallyStockItem(TallyElement):
# NOTE All masters has more fields?
attrs = {
'name': ('name', TXString(required=True), True),
'reservedname': ('reservedname', TXString(), False),
}
descendent_elements = {
'extendedname': ('name.list', TXMultilineString(required=True), True),
'_godownname': ('godownname', TXString(), False),
}
elements = {
'_parent': ('parent', TXString(), True),
'narration': ('narration', TXString(), True),
'_category': ('category', TXString(), False),
'taxclassificationname': ('taxclassificationname', TXString(), False),
'ledgername': ('ledgername', TXString(), False),
'_costingmethod': ('costingmethod', TXString(), True),
'_valuationmethod': ('valuationmethod', TXString(), True),
'_baseunits': ('baseunits', TXString(), True),
'_additionalunits': ('additionalunits', TXString(), True),
'description': ('description', TXString(), True),
'natureofitem': ('natureofitem', TXString(), False),
'isbatchwiseon': ('isbatchwiseon', TXBoolean(), True),
'isperishableon': ('isperishableon', TXBoolean(), True),
'iscostcentreson': ('iscostcentreson', TXBoolean(), False),
'isentrytaxapplicable': ('isentrytaxapplicable', TXBoolean(), False),
'iscosttrackingon': ('iscosttrackingon', TXBoolean(), False),
'ignorephysicaldifference': ('ignorephysicaldifference', TXBoolean(), True),
'ignorenegativestock': ('ignorenegativestock', TXBoolean(), True),
'treatsalesasmanufactured': ('treatsalesasmanufactured', TXBoolean(), True),
'treatpurchasesasconsumed': ('treatpurchasesasconsumed', TXBoolean(), True),
'treatrejectsasscrap': ('treatrejectsasscrap', TXBoolean(), True),
'hasmfgdate': ('hasmfgdate', TXBoolean(), True),
'allowuseofexpireditems': ('allowuseofexpireditems', TXBoolean(), True),
'ignorebatches': ('ignorebatches', TXBoolean(), True),
'ignoregodowns': ('ignoregodowns', TXBoolean(), True),
'calconmrp': ('calconmrp', TXBoolean(), False),
'excludejrnlforvaluation': ('excludejrnlforvaluation', TXBoolean(), True),
'_openingbalance': ('openingbalance', TXString(), True),
'_openingvalue': ('openingvalue', TXString(), True),
'_openingrate': ('openingrate', TXString(), True),
'batchname': ('batchname', TXString(), False),
}
@property
def parent(self):
if self._parent and self._parent != self.name:
try:
return self.company_masters.stockgroups[self._parent]
except KeyError:
print(self.name)
print(self._parent)
print(self.company_masters.stockgroups.keys())
raise
@property
def catgory(self):
if self._parent and self._parent != self.name:
return self.company_masters.stockcategories[self._parent]
@property
def baseunits(self):
if self._baseunits:
return self.company_masters.units[self._baseunits]
@property
def additionalunits(self):
if self._additionalunits:
return self.company_masters.units[self._additionalunits]
@property
def costingmethod(self):
if self._costingmethod:
return self._costingmethod
if self.parent:
return self.parent.costingmethod
@property
def valuationmethod(self):
if self._valuationmethod:
return self._valuationmethod
if self.parent:
return self.parent.valuationmethod
@property
def openingbalance(self):
raise NotImplementedError
@property
def openingrate(self):
raise NotImplementedError
@property
def openingvalue(self):
raise NotImplementedError
@property
def godowns(self):
if self._godownname:
if ':' in self._godownname:
names = set(self._godownname.split(':'))
else:
names = [self._godownname]
return [self.company_masters.godowns[x] for x in names]
else:
return []
@property
def path(self):
if self.parent and self.parent.path:
return self.parent.path + [self.name]
else:
return [self.name]
def __repr__(self):
return "<TallyStockItem {0}>".format(self.name)
[docs]class TallyGodown(TallyElement):
# NOTE All masters has more fields?
attrs = {
'name': ('name', TXString(required=True), True),
'reservedname': ('reservedname', TXString(), False),
}
descendent_elements = {
'extendedname': ('name.list', TXMultilineString(required=True), True),
}
elements = {
'_parent': ('parent', TXString(), True),
'narration': ('narration', TXString(), True),
'hasnospace': ('hasnospace', TXBoolean(), False),
'hasnostock': ('hasnostock', TXBoolean(), False),
'isexternal': ('isexternal', TXBoolean(), False),
'isinternal': ('isinternal', TXBoolean(), False),
}
@property
def parent(self):
if self._parent and self._parent != self.name:
return self.company_masters.godowns[self._parent]
def __repr__(self):
return "<TallyGodown {0}>".format(self.name)
[docs]class TallyStockBatchAllocation(TallyElement):
pass
[docs]class TallyVoucherBatchAllocation(TallyElement):
elements = {
'mfdon': ('mfdon', TXString(), False),
'godownname': ('godownname', TXString(), True),
'batchname': ('batchname', TXString(), True),
'destinationgodownname': ('destinationgodownname', TXString(), True),
'indentno': ('indentno', TXString(), False),
'orderno': ('orderno', TXString(), False),
'trackingnumber': ('trackingnumber', TXString(), False),
'addlamount': ('addlamount', TXString(), False),
'amount': ('amount', TXString(), True),
'actualqty': ('actualqty', TXString(), True), #
'billedqty': ('billedqty', TXString(), True), #
'expiryperiod': ('expiryperiod', TXString(), False),
'indentduedate': ('indentduedate', TXString(), False),
'orderduedate': ('orderduedate', TXString(), False),
}
@property
def godown(self):
return self.company_masters.godowns[self.godownname]
@property
def batch(self):
raise NotImplementedError
@property
def destinationgodown(self):
return self.company_masters.godowns[self.destinationgodownname]
def __repr__(self):
return "<TallyVoucherBatchAllocation {0} {1} {2}>" \
"".format(self.amount, self.actualqty, self.godownname)
[docs]class TallyInventoryEntry(TallyElement):
elements = {
'isdeemedpositive': ('isdeemedpositive', TXBoolean(), True),
'amount': ('amount', TXString(), True),
'actualqty': ('actualqty', TXString(), True), #
'billedqty': ('billedqty', TXString(), True), #
'description': ('description', TXString(), False),
'stockitemname': ('stockitemname', TXString(required=True), True),
'excisetariff': ('excisetariff', TXString(), False),
'exciseexemption': ('exciseexemption', TXString(), False),
'tradercnsalesnumber': ('tradercnsalesnumber', TXString(), False),
'basicpackagemarks': ('basicpackagemarks', TXString(), False),
'basicnumpackages': ('basicnumpackages', TXString(), False),
'sdtaxclassificationname': ('sdtaxclassificationname', TXString(), False),
'addlamount': ('addlamount', TXString(), False),
'isautonegate': ('isautonegate', TXBoolean(), True),
'rate': ('rate', TXString(), True), #
'discount': ('discount', TXString(), True), #
'mrprate': ('mrprate', TXString(), False),
'basicuserdescription': ('basicuserdescription.list', TXMultilineString(), False),
}
lists = {
'accountingallocations': ('accountingallocations', ledgers.TallyAccountingAllocation, True),
'batchallocations': ('batchallocations', TallyVoucherBatchAllocation, True)
}
@property
def name(self):
return 'Unnamed'
@property
def stockitem(self):
return self.company_masters.stockitems[self.stockitemname]
def __repr__(self):
return "<TallyInventoryEntry {0}, {1}@{2}>".format(self.stockitemname, self.billedqty, self.rate)
[docs]class TallyStockItemPosition(TallyElement):
attrs = {
'name': ('name', TXString(required=True), True),
'reservedname': ('reservedname', TXString(), False),
}
descendent_elements = {
'extendedname': ('name.list', TXMultilineString(required=True), True),
}
elements = {
'_parent': ('parent', TXString(), True),
'_baseunits': ('baseunits', TXString(), True),
'closingbalance': ('closingbalance', TXString(), True),
'closingvalue': ('closingvalue', TXDecimal(), True),
'closingrate': ('closingrate', TXString(), True),
}
@property
def parent(self):
try:
return self.company_masters.stockgroups[self._parent]
except KeyError:
warn("Could not find Parent {0} for {1}"
"".format(self._parent, self.name))
return self._parent
@property
def baseunits(self):
if getattr(self, '_baseunits', None):
try:
return self.company_masters.units[self._baseunits]
except KeyError:
return None
def __repr__(self):
return "<TallyStockItemPosition {0}, {1}>".format(self.name, self.closingbalance)
[docs]class TallyStockPosition(TallyReport):
_cachename = 'TallyStockPosition'
_header = TallyRequestHeader(1, 'Export', 'Collection',
'All items under Groups')
[docs] def _build_request_body(self):
r = etree.Element('DESC')
sv = etree.SubElement(r, 'STATICVARIABLES')
self._set_request_staticvariables(sv)
self._set_request_date(sv)
tdl = etree.SubElement(r, 'TDL')
tdlmessage = etree.SubElement(tdl, 'TDLMESSAGE')
collection = etree.SubElement(tdlmessage, 'COLLECTION', ISMODIFY='No',
NAME="All items under Groups")
colltype = etree.SubElement(collection, 'TYPE')
colltype.text = 'stock item'
fetchlist = ['Name', 'Parent', 'BaseUnits',
'ClosingBalance', 'ClosingRate', 'ClosingValue']
self._build_fetchlist(collection, fetchlist)
return etree.ElementTree(r)
_container = 'collection'
_content = {
'stockitems': ('stockitem', TallyStockItemPosition)
}
[docs]def get_position(company_name, dt=None, end_dt=None, force=False):
global _positions
if not force and company_name in _positions.keys() and not dt:
return _positions[company_name]
try:
_positions[company_name] = TallyStockPosition(company_name, dt=dt, end_dt=end_dt)
except TallyNotAvailable:
_positions[company_name] = None
return _positions[company_name]
_positions = {}