Hide keyboard shortcuts

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

# 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/>. 

""" 

This file is part of tendril 

See the COPYING, README, and INSTALL files for more information 

""" 

 

import os 

import yaml 

import re 

import csv 

import codecs 

 

import iec60063 

 

import vendors 

import customs 

 

from tendril.conventions.electronics import ident_transform 

from tendril.conventions.electronics import construct_resistor 

from tendril.conventions.electronics import construct_capacitor 

from tendril.utils.types import currency 

from tendril.utils.config import PRICELISTVENDORS_FOLDER 

from tendril.utils.config import INSTANCE_ROOT 

 

from tendril.utils import log 

logger = log.get_logger(__name__, log.DEFAULT) 

 

 

class VendorPricelist(vendors.VendorBase): 

    def __init__(self, name, dname, pclass, mappath=None, 

                 currency_code=None, currency_symbol=None, 

                 pricelistpath=None): 

        if pricelistpath is None: 

            pricelistpath = os.path.join(PRICELISTVENDORS_FOLDER, 

                                         name + '-pricelist.yaml') 

        with open(pricelistpath, 'r') as f: 

            self._pricelist = yaml.load(f) 

        if currency_code is None: 

            currency_code = self._pricelist["currency"]["code"].strip() 

        if currency_symbol is None: 

            currency_symbol = self._pricelist["currency"]["symbol"].strip() 

        self._currency = currency.CurrencyDefinition( 

            currency_code, currency_symbol 

        ) 

        super(VendorPricelist, self).__init__(name, dname, pclass, mappath, 

                                              currency_code, currency_symbol) 

        if 'vendorinfo' in self._pricelist: 

            if "effectivefactor" in self._pricelist["vendorinfo"]: 

                self.add_order_additional_cost_component( 

                    "Unspecified", 

                    self._pricelist["vendorinfo"]["effectivefactor"] * 100 

                ) 

        self._vpart_class = PricelistPart 

        try: 

            self.add_order_baseprice_component( 

                "Shipping Cost", 

                self._pricelist["vendorinfo"]["shippingcost"] 

            ) 

        except KeyError: 

            pass 

        if "prices" not in self._pricelist: 

            try: 

                pass 

            except: 

                logger.error("No prices found for " + self.name) 

        if "pricegens" in self._pricelist: 

            self._generate_insert_idents() 

        if "pricecsv" in self._pricelist: 

            self._load_pricecsv(self._pricelist["pricecsv"]) 

 

    def _load_pricecsv(self, fname): 

        pricecsvpath = os.path.join(PRICELISTVENDORS_FOLDER, fname) 

        with open(pricecsvpath, 'r') as f: 

            reader = csv.reader(f) 

            for line in reader: 

                line = [elem.strip() for elem in line] 

                if line[0] == '': 

                    continue 

                if line[0] == 'ident': 

                    continue 

                partdict = {'ident': line[0], 

                            'vpno': line[1], 

                            'unitp': float(line[2]), 

                            'moq': int(line[3]), 

                            'oqmultiple': int(line[4]), 

                            'pkgqty': int(line[5])} 

                try: 

                    partdict['avail'] = int(line[6]) 

                except ValueError: 

                    partdict['avail'] = None 

                self._pricelist["prices"].append(partdict) 

 

    def _generate_insert_idents(self): 

        for pricegen in self._pricelist['pricegens']: 

                if pricegen['vpno'] != 'ident': 

                    logger.error("Unknown VPNO transform : " + 

                                 pricegen['device'] + "" + pricegen['value']) 

                idents = self._get_generator_idents(pricegen) 

                for ident in idents: 

                    partdict = pricegen.copy() 

                    partdict['vpno'] = ident 

                    partdict['ident'] = ident 

                    self._pricelist["prices"].append(partdict) 

 

    def _get_generator_idents(self, pricegen): 

        return [ident_transform(pricegen['device'], x, pricegen['footprint']) 

                for x in self._get_generator_values(pricegen)] 

 

    @staticmethod 

    def _get_generator_values(pricegen): 

        """ 

        TODO This function should be parcelled out to conventions 

        :param gendata: 

        :return: 

        """ 

        if pricegen['type'] == 'simple': 

            return pricegen['values'] 

        values = [] 

 

        if pricegen['type'] == 'resistor': 

            if 'resistances' in pricegen.keys(): 

                for resistance in pricegen['resistances']: 

                    values.append(resistance) 

            if 'generators' in pricegen.keys(): 

                for generator in pricegen['generators']: 

                    if generator['std'] == 'iec60063': 

                        rvalues = iec60063.gen_vals(generator['series'], 

                                                    iec60063.res_ostrs, 

                                                    start=generator['start'], 

                                                    end=generator['end']) 

                        for rvalue in rvalues: 

                            values.append( 

                                construct_resistor(rvalue, 

                                                   generator['wattage']) 

                            ) 

                    else: 

                        raise ValueError 

            if 'values' in pricegen.keys(): 

                if pricegen['values'][0].strip() != '': 

                    values += pricegen['values'] 

            return values 

 

        if pricegen['type'] == 'capacitor': 

            if 'capacitances' in pricegen.keys(): 

                for capacitance in pricegen['capacitances']: 

                        values.append(capacitance) 

            if 'generators' in pricegen.keys(): 

                for generator in pricegen['generators']: 

                    if generator['std'] == 'iec60063': 

                        cvalues = iec60063.gen_vals(generator['series'], 

                                                    iec60063.cap_ostrs, 

                                                    start=generator['start'], 

                                                    end=generator['end']) 

                        for cvalue in cvalues: 

                            values.append( 

                                construct_capacitor(cvalue, 

                                                    generator['voltage']) 

                            ) 

                    else: 

                        raise ValueError 

            if 'values' in pricegen.keys(): 

                if pricegen['values'][0].strip() != '': 

                    values += pricegen['values'] 

            return values 

 

    def search_vpnos(self, ident): 

        vplist = [] 

        for part in self._pricelist['prices']: 

            if part['ident'] == ident: 

                vplist.append(part['vpno'].strip()) 

        if len(vplist) > 0: 

            return vplist, 'MANUAL' 

        else: 

            return vplist, 'UNDEF' 

 

    def get_vpart(self, vpartno, ident=None): 

        vp_dict = self._pl_get_vpart_dict(vpartno) 

        if ident is not None: 

            if vp_dict['ident'].strip() != ident: 

                logger.warning( 

                    'Specified Ident does not match ' 

                    'Pricelist Defined Ident : ' + vp_dict['ident'].strip() 

                ) 

        return PricelistPart(vp_dict, ident, self) 

 

    def _pl_get_vpart_dict(self, vpartno): 

        for part in self._pricelist["prices"]: 

            if part['vpno'].strip() == vpartno: 

                return part 

 

    def get_optimal_pricing(self, ident, rqty): 

        candidate_names = self.get_vpnos(ident) 

 

        candidates = [self.get_vpart(x) for x in candidate_names] 

        if len(candidates) == 0: 

            return self, None, None, None, None, None, None, None 

 

        selcandidate = candidates[0] 

        tcost, oqty, price = selcandidate.get_price_qty(rqty) 

        if len(candidates) > 1: 

            for candidate in candidates: 

                ltcost, loqty, lprice = candidate.get_price_qty(rqty) 

                if ltcost < tcost and ( 

                        selcandidate.vqtyavail is not None and 

                        selcandidate.vqtyavail < oqty 

                ): 

                    tcost = ltcost 

                    selcandidate = candidate 

                    oqty = loqty 

                    price = lprice 

        else: 

            if selcandidate.vqtyavail is not None and \ 

                    selcandidate.vqtyavail < oqty: 

                return self, None, None, None, None, None, None, None 

        effprice = self.get_effective_price(price) 

        return self, selcandidate.vpno, oqty, None, \ 

            price, effprice, "Vendor MOQ/GL", None 

 

 

class PricelistPart(vendors.VendorPartBase): 

    def __init__(self, vp_dict, ident, vendor): 

        self._vendor = vendor 

        super(PricelistPart, self).__init__( 

            vp_dict['vpno'].strip(), ident, vendor 

        ) 

        if 'pkgqty' in vp_dict.keys(): 

            self.pkgqty = vp_dict['pkgqty'] 

        if 'availqty' in vp_dict.keys(): 

            self._vqtyavail = vp_dict['availqty'] 

        else: 

            self._vqtyavail = None 

        self._get_prices(vp_dict) 

 

    def _get_prices(self, vp_dict): 

        rex = re.compile(ur'^unitp@(?P<moq>\d+)$') 

        for k, v in vp_dict.iteritems(): 

            try: 

                moq = rex.match(k).group(1) 

                price = vendors.VendorPrice(int(moq), float(v), 

                                            self._vendor.currency, 

                                            vp_dict['oqmultiple']) 

                self._prices.append(price) 

            except AttributeError: 

                pass 

        if len(self._prices) == 0: 

            price = vendors.VendorPrice(vp_dict['moq'], 

                                        vp_dict['unitp'], 

                                        self._vendor.currency, 

                                        vp_dict['oqmultiple']) 

            self._prices.append(price) 

 

    def get_price_qty(self, qty): 

        lcost = None 

        lprice = None 

        loqty = None 

        for price in self._prices: 

            tqty = 0 

            while tqty < qty or tqty < price.moq: 

                tqty += price.oqmultiple 

            tcost = price.extended_price(tqty).native_value 

            if lcost is None or tcost < lcost: 

                lcost = tcost 

                lprice = price 

                loqty = tqty 

        return lcost, loqty, lprice 

 

 

class AnalogDevicesInvoice(customs.CustomsInvoice): 

    def __init__(self, vendor=None, inv_yaml=None, working_folder=None): 

        if vendor is None: 

            vendor = VendorPricelist( 

                'analogdevices', 'Analog Devices Inc', 'electronics' 

            ) 

        if inv_yaml is None: 

            inv_yaml = os.path.join(INSTANCE_ROOT, 'scratch', 

                                    'customs', 'inv_data.yaml') 

 

        super(AnalogDevicesInvoice, self).__init__(vendor, inv_yaml, 

                                                   working_folder) 

 

    def _acquire_lines(self): 

        logger.info("Acquiring Lines") 

        invoice_file = os.path.join(self._source_folder, 

                                    self._data['invoice_file']) 

        with open(invoice_file) as f: 

            reader = csv.reader(f) 

            header = None 

            for line in reader: 

                if line[0].startswith(codecs.BOM_UTF8): 

                    line[0] = line[0][3:] 

                if line[0] == 'Index': 

                    header = line 

                    break 

            if header is None: 

                raise ValueError 

            for line in reader: 

                if line[0] != '': 

                    idx = line[header.index('Index')].strip() 

                    qty = int(line[header.index('Quantity')].strip()) 

                    vpno = line[header.index('Part Number')].strip() 

                    desc = line[header.index('Description')].strip() 

                    ident = line[header.index('Customer Reference')].strip() 

                    boqty = line[header.index('Backorder')].strip() 

                    try: 

                        if int(boqty) > 0: 

                            logger.warning( 

                                "Apparant backorder. " 

                                "Crosscheck customs treatment for: " + 

                                idx + ' ' + ident 

                            ) 

                    except ValueError: 

                        print line 

 

                    unitp_str = line[header.index('Unit Price')].strip() 

 

                    unitp = currency.CurrencyValue( 

                        float(unitp_str), self._vendor.currency 

                    ) 

                    lineobj = customs.CustomsInvoiceLine( 

                        self, ident, vpno, unitp, qty, idx=idx, desc=desc 

                    ) 

                    self._lines.append(lineobj)