Coverage for /media/ldata/code/tendril/tendril/sourcing/vendors.py : 0%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Copyright (C) 2015 Chintalagiri Shashank # # This file is part of Tendril. # # 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/>. """ Vendors module documentation (:mod:`sourcing.vendors`) ====================================================== """
import os import csv import time
import tendril.entityhub.maps from tendril.utils.types import currency from tendril.utils import config
from tendril.utils import log logger = log.get_logger(__name__, log.INFO)
class VendorInvoiceLine(object): def __init__(self, invoice, ident, vpno, unitp, qty, desc=None): self._invoice = invoice self._ident = ident self._vpno = vpno self._unitp = unitp self._qty = qty self._desc = desc
@property def desc(self): return self._desc
@property def ident(self): return self._ident
@property def vpno(self): return self._vpno
@property def unitprice(self): return self._unitp
@property def extendedprice(self): return currency.CurrencyValue(self._unitp._val * self._qty, self._unitp._currency_def)
@property def effectiveprice(self): return self.extendedprice
@property def qty(self): return self._qty
class VendorInvoice(object): def __init__(self, vendor, inv_no, inv_date): self._vendor = vendor self._inv_no = inv_no self._inv_date = inv_date self._lines = [] if self._linetype is None: self._linetype = VendorInvoiceLine self._acquire_lines()
@property def vendor_name(self): return self._vendor.name
@property def currency(self): return self._vendor.currency
@property def inv_no(self): return self._inv_no
@property def inv_date(self): return self._inv_date
@property def linecount(self): return len(self._lines)
@property def lines(self): return self._lines
@property def extendedtotal(self): return sum([x.extendedprice for x in self._lines])
@property def effectivetotal(self): return sum([x.effectiveprice for x in self._lines])
def _acquire_lines(self): raise NotImplementedError
class VendorOrder(object): def __init__(self, vendor, orderref): self._vendor = vendor self._lines = [] self._basecost = self._vendor.order_baseprice self._orderref = orderref
def add(self, line): self._lines.append(line)
def __len__(self): return len(self._lines)
@property def lines(self): return self._lines
@property def orderref(self): return self._orderref
class VendorBase(object): def __init__(self, name, dname, pclass, mappath=None, currency_code=config.BASE_CURRENCY, currency_symbol=config.BASE_CURRENCY_SYMBOL): self._name = name self._mappath = None self._map = None self._dname = dname self._currency = currency.CurrencyDefinition(currency_code, currency_symbol) self._pclass = pclass self._order = None self._orderbasecosts = [] self._orderadditionalcosts = [] self._vpart_class = VendorPartBase if mappath is not None: self.map = mappath
@property def name(self): return self._dname
@property def pclass(self): return self._pclass
@property def mappath(self): return self._mappath
@property def map(self): return self._map
@map.setter def map(self, mappath): self._mappath = mappath if os.path.isfile(mappath) is False: if 'electronics' in self._pclass: import electronics electronics.gen_vendor_mapfile(self) else: raise AttributeError self._map = tendril.entityhub.maps.MapFile(mappath)
@property def currency(self): return self._currency
@currency.setter def currency(self, currency_def): """
:type currency_def: utils.currency.CurrencyDefinition """ self._currency = currency_def
def get_idents(self): for ident in self._map.get_idents(): yield ident
def get_all_vpnos(self): for ident in self.get_idents(): for vpno in self._map.get_all_partnos(ident): yield ident, vpno
def get_all_vparts(self): for ident, vpno in self.get_all_vpnos(): yield self.get_vpart(vpartno=vpno, ident=ident)
def get_vpnos(self, ident): return self._map.get_partnos(ident)
def search_vpnos(self, ident): raise NotImplementedError
def get_vpart(self, vpartno, ident=None): raise NotImplementedError
def get_optimal_pricing(self, ident, rqty): candidate_names = self.get_vpnos(ident)
candidates = [self.get_vpart(x) for x in candidate_names]
candidates = [x for x in candidates if x.abs_moq <= rqty] candidates = [x for x in candidates if x.vqtyavail is None or x.vqtyavail > rqty or x.vqtyavail == -2]
oqty = rqty
# vobj, vpno, oqty, nbprice, ubprice, effprice if len(candidates) == 0: return self, None, None, None, None, None
selcandidate = candidates[0] tcost = self.get_effective_price( selcandidate.get_price(rqty)[0] ).extended_price(rqty).native_value
for candidate in candidates: ubprice, nbprice = candidate.get_price(oqty) effprice = self.get_effective_price(ubprice) ntcost = effprice.extended_price(oqty).native_value if ntcost < tcost: tcost = ntcost selcandidate = candidate
if selcandidate.vqtyavail == -2: logger.warning( "Vendor available quantity could not be confirmed. " "Verify manually : " + self.name + " " + selcandidate.vpno + os.linesep + os.linesep + os.linesep )
ubprice, nbprice = selcandidate.get_price(oqty) effprice = self.get_effective_price(ubprice) urationale = None olduprice = None if nbprice is not None: nubprice, nnbprice = selcandidate.get_price(nbprice.moq) neffprice = self.get_effective_price(nubprice) ntcost = neffprice.extended_price(nbprice.moq).native_value # bump_excess_qty = nubprice.moq - rqty
if ntcost < tcost * 1.4: urationale = "TC Increase < 40%" oqty = nbprice.moq olduprice = ubprice ubprice = nubprice nbprice = nnbprice effprice = neffprice elif nubprice.unit_price.native_value < \ ubprice.unit_price.native_value * 0.5: urationale = "UP Decrease > 40%" olduprice = ubprice oqty = nbprice.moq ubprice = nubprice nbprice = nnbprice effprice = neffprice return self, selcandidate.vpno, oqty, nbprice, \ ubprice, effprice, urationale, olduprice
def add_order_additional_cost_component(self, desc, percent): self._orderadditionalcosts.append((desc, percent))
def get_effective_price(self, price): effective_unitp = price.unit_price.source_value for additional_cost in self._orderadditionalcosts: effective_unitp += price.unit_price.source_value * float(additional_cost[1]) / 100 # noqa return VendorPrice( price.moq, effective_unitp, self.currency, price.oqmultiple )
def get_additional_costs(self, price): rval = [] for desc, percent in self._orderadditionalcosts: rval.append((desc, price.source_value * percent / 100)) return rval
@property def order_baseprice(self): t = 0 for price in self._orderbasecosts: t += price[1].native_value return currency.CurrencyValue(t, currency.native_currency_defn)
def add_order_baseprice_component(self, desc, value): if isinstance(value, currency.CurrencyValue): self._orderbasecosts.append((desc, value)) else: self._orderbasecosts.append( (desc, currency.CurrencyValue(value, self.currency)) )
def add_to_order(self, line, orderref=None): if self._order is None: self._order = VendorOrder(self, orderref) logger.info("Adding to " + self._name + " order : " + line[0] + " : " + str(line[3]) ) self._order.add(line)
def _dump_open_order(self, path): orderfile = os.path.join(path, self._name + '-order.csv') with open(orderfile, 'w') as orderf: w = csv.writer(orderf) w.writerow( [self._dname + " Order", None, None, None, None, time.strftime("%c")] ) w.writerow( ["Ident", "Vendor Part No", "Quantity", "Unit Price (" + self._currency.symbol + ")", "Extended Price (" + self._currency.symbol + ")", "Effective Price (" + currency.native_currency_defn.symbol + ")" ] ) for line in self._order.lines: w.writerow([line[0], line[2], line[3], line[5].unit_price.source_value, line[5].extended_price(line[3]).source_value, line[6].extended_price(line[3]).native_value]) for basecost in self._orderbasecosts: w.writerow( [None, basecost[0], None, None, basecost[1].source_value, basecost[1].native_value] )
def _generate_purchase_order(self, path): stagebase = {} return stagebase
def finalize_order(self, path): if self._order is None or len(self._order) == 0: logger.debug("Nothing in the order, " "not generating order file : " + self._name) return logger.info("Writing " + self._dname + " order to Folder : " + path) self._dump_open_order(path) self._generate_purchase_order(path) self._order = None
class VendorPrice(object): def __init__(self, moq, price, currency_def, oqmultiple=1): self._moq = moq self._price = currency.CurrencyValue(price, currency_def) self._oqmulitple = oqmultiple
@property def moq(self): return self._moq
@property def oqmultiple(self): return self._oqmulitple
@property def unit_price(self): return self._price
def extended_price(self, qty): if qty < self.moq: raise ValueError if qty % self.oqmultiple != 0: pass return currency.CurrencyValue(self.unit_price._val * qty, self.unit_price._currency_def)
def __repr__(self): return '<VendorPrice {2} @{0}({1})>'.format( self.moq, self.oqmultiple, self.unit_price)
class VendorPartBase(object): def __init__(self, vpno, ident, vendor): self._vpno = vpno self._vqtyavail = None self._manufacturer = None self._mpartno = None self._vpartdesc = None self._canonical_repr = ident self._prices = [] self._vendor = vendor self._pkgqty = 1
def add_price(self, price): self._prices.append(price)
@property def vpno(self): return self._vpno
@vpno.setter def vpno(self, value): self._vpno = value
@property def vqtyavail(self): return self._vqtyavail
@vqtyavail.setter def vqtyavail(self, value): self._vqtyavail = value
@property def manufacturer(self): return self._manufacturer
@manufacturer.setter def manufacturer(self, value): self._manufacturer = value
@property def mpartno(self): return self._mpartno
@mpartno.setter def mpartno(self, value): self._mpartno = value
@property def vpartdesc(self): return self._vpartdesc
@vpartdesc.setter def vpartdesc(self, value): self._vpartdesc = value
@property def ident(self): return self._canonical_repr
@property def pkgqty(self): return self._pkgqty
@pkgqty.setter def pkgqty(self, value): self._pkgqty = value
@property def abs_moq(self): if len(self._prices) == 0: return 0 rval = self._prices[0].moq for price in self._prices: if price.moq < rval: rval = price.moq return rval
def get_price(self, qty): rprice = None rnextprice = None for price in self._prices: if price.moq <= qty: if rprice is not None: if price.moq > rprice.moq: rprice = price else: rprice = price if price.moq > qty: if rnextprice is not None: if price.moq < rnextprice.moq: rnextprice = price else: rnextprice = price return rprice, rnextprice
def __repr__(self): return '<{0} {1} {2}>'.format( self.__class__, self.vpno, str(self._vpartdesc) )
class VendorElnPartBase(VendorPartBase): def __init__(self, vpno, ident, vendor): super(VendorElnPartBase, self).__init__(vpno, ident, vendor) self._package = None self._datasheet = None
@property def package(self): return self._package
@package.setter def package(self, value): self._package = value
@property def datasheet(self): return self._datasheet
@datasheet.setter def datasheet(self, value): self._datasheet = value |