Skip to content

Commit

Permalink
Merge pull request #3 from cloudblue/feedback_20200929
Browse files Browse the repository at this point in the history
Feedback 20200929
  • Loading branch information
ffaraoneim authored Oct 2, 2020
2 parents 536ec90 + 81528ea commit 92fed7a
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 161 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ This command will generate a excel file named PRD-000-000-000.xlsx in the curren
To synchronize a product from Excel run:

```
$ ccli product sync --in my_products.xlsx
$ ccli product sync my_products.xlsx
```


Expand Down
2 changes: 1 addition & 1 deletion cnctcli/actions/products/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# Copyright (c) 2019-2020 Ingram Micro. All Rights Reserved.

from cnctcli.actions.products.export import dump_product # noqa: F401
from cnctcli.actions.products.sync import sync_product, validate_input_file # noqa: F401
from cnctcli.actions.products.sync import ProductSynchronizer # noqa: F401
16 changes: 10 additions & 6 deletions cnctcli/actions/products/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
# Copyright (c) 2019-2020 Ingram Micro. All Rights Reserved.

ITEMS_COLS_HEADERS = {
'A': 'Name',
'A': 'ID',
'B': 'MPN',
'C': 'Billing Period',
'D': 'Reservation',
'E': 'Description',
'F': 'Yearly Commitment',
'C': 'Name',
'D': 'Description',
'E': 'Type',
'F': 'Precision',
'G': 'Unit',
'H': 'Connect Item ID',
'H': 'Billing Period',
'I': 'Commitment',
'J': 'Status',
'K': 'Created',
'L': 'Modified',
}
54 changes: 42 additions & 12 deletions cnctcli/actions/products/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,57 @@ def _setup_cover_sheet(ws, product):
def _setup_items_header(ws):
color = Color('d3d3d3')
fill = PatternFill('solid', color)
cels = ws['A1': 'H1']
cels = ws['A1': 'L1']
for cel in cels[0]:
ws.column_dimensions[cel.column_letter].width = 25
ws.column_dimensions[cel.column_letter].auto_size = True
cel.fill = fill
cel.value = ITEMS_COLS_HEADERS[cel.column_letter]


def _calculate_commitment(item):
period = item.get('period')
if not period:
return '-'
commitment = item.get('commitment')
if not commitment:
return '-'
count = commitment['count']
if count == 1:
return '-'

multiplier = commitment['multiplier']

if multiplier == 'billing_period':
if period == 'monthly':
years = count // 12
return '{} year{}'.format(
years,
's' if years > 1 else '',
)
else:
return '{} years'.format(count)

# One-time
return '-'


def _fill_item_row(ws, row_idx, item):
ws.cell(row_idx, 1, value=item['display_name'])
ws.cell(row_idx, 1, value=item['id'])
ws.cell(row_idx, 2, value=item['mpn'])
ws.cell(row_idx, 3, value=item['period'])
ws.cell(row_idx, 4, value=item['type'] == 'reservation')
ws.cell(row_idx, 5, value=item['description'])
commitment = item['commitment']['count'] == 12 if item.get('commitment') else False
ws.cell(row_idx, 6, value=commitment)
ws.cell(row_idx, 3, value=item['display_name'])
ws.cell(row_idx, 4, value=item['description'])
ws.cell(row_idx, 5, value=item['type'])
ws.cell(row_idx, 6, value=item['precision'])
ws.cell(row_idx, 7, value=item['unit']['unit'])
ws.cell(row_idx, 8, value=item['id'])
ws.cell(row_idx, 8, value=item.get('period', 'monthly'))
ws.cell(row_idx, 9, value=_calculate_commitment(item))
ws.cell(row_idx, 10, value=item['status'])
ws.cell(row_idx, 11, value=item['events']['created']['at'])
ws.cell(row_idx, 12, value=item['events'].get('updated', {}).get('at'))


def _dump_items(ws, api_url, api_key, product_id):
def _dump_items(ws, api_url, api_key, product_id, silent):
_setup_items_header(ws)

processed_items = 0
Expand All @@ -81,7 +111,7 @@ def _dump_items(ws, api_url, api_key, product_id):

items = iter(items)

progress = trange(0, count, position=0)
progress = trange(0, count, position=0, disable=silent)

while True:
try:
Expand All @@ -100,7 +130,7 @@ def _dump_items(ws, api_url, api_key, product_id):
break


def dump_product(api_url, api_key, product_id, output_file):
def dump_product(api_url, api_key, product_id, output_file, silent):
if not output_file:
output_file = os.path.abspath(
os.path.join('.', f'{product_id}.xlsx'),
Expand All @@ -110,7 +140,7 @@ def dump_product(api_url, api_key, product_id, output_file):
wb = Workbook()
_setup_cover_sheet(wb.active, product)

_dump_items(wb.create_sheet('product_items'), api_url, api_key, product_id)
_dump_items(wb.create_sheet('product_items'), api_url, api_key, product_id, silent)
wb.save(output_file)

return output_file
231 changes: 141 additions & 90 deletions cnctcli/actions/products/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,108 +15,159 @@
from cnctcli.actions.products.constants import ITEMS_COLS_HEADERS
from cnctcli.api.products import (
create_item,
create_unit,
get_item,
get_item_by_mpn,
get_product,
get_units,
update_item,
)


def _open_workbook(input_file):
try:
return load_workbook(input_file)
except InvalidFileException as ife:
raise ClickException(str(ife))
except BadZipFile:
raise ClickException(f'{input_file} is not a valid xlsx file.')


def _validate_item_sheet(ws):
cels = ws['A1': 'H1']
for cel in cels[0]:
if cel.value != ITEMS_COLS_HEADERS[cel.column_letter]:
raise ClickException(
f'Invalid input file: column {cel.column_letter} '
f'must be {ITEMS_COLS_HEADERS[cel.column_letter]}'
)

class ProductSynchronizer:
def __init__(self, endpoint, api_key, silent):
self._endpoint = endpoint
self._api_key = api_key
self._silent = silent
self._units = get_units(self._endpoint, self._api_key)
self._product_id = None
self._wb = None

def _open_workbook(self, input_file):
try:
self._wb = load_workbook(input_file)
except InvalidFileException as ife:
raise ClickException(str(ife))
except BadZipFile:
raise ClickException(f'{input_file} is not a valid xlsx file.')

def _validate_item_sheet(self, ws):
cels = ws['A1': 'H1']
for cel in cels[0]:
if cel.value != ITEMS_COLS_HEADERS[cel.column_letter]:
raise ClickException(
f'Invalid input file: column {cel.column_letter} '
f'must be {ITEMS_COLS_HEADERS[cel.column_letter]}'
)

def _get_commitment_count(self, data):
period = data[7]
if period == 'onetime':
return 1
if data[8] == '-':
return 1
try:
years, _ = data[8].split()
years = int(years)
if period == 'monthly':
return years * 12
return years
except: # noqa
return 1

def _get_or_create_unit(self, data):
for unit in self._units:
if unit['id'] == data[6]:
return unit['id']
if unit['type'] == data[4] and unit['description'] == data[6]:
return unit['id']

created = create_unit(
self._endpoint,
self._api_key,
{
'description': data[6],
'type': data[4],
'unit': 'unit' if data[4] == 'reservation' else 'unit-h'
},
)
return created['id']

def _get_item_payload(data):
if data[3] is True:
# reservation
period = 'monthly'
if data[2].lower() == 'yearly':
period = 'yearly'
if data[2].lower() == 'onetime':
period = 'onetime'
return {
'name': data[0],
'mpn': data[1],
'description': data[4],
'ui': {'visibility': True},
def _get_item_payload(self, data):
commitment = {
'commitment': {
'count': 12 if data[5] is True else 1,
'count': self._get_commitment_count(data),
},
'unit': {'id': data[6]},
'type': 'reservation',
'period': period,
}
else:
# PPU
return {
'name': data[0],
payload = {
'mpn': data[1],
'description': data[4],
'name': data[2],
'description': data[3],
'type': data[4],
'precision': data[5],
'unit': {'id': self._get_or_create_unit(data)},
'period': data[7],
'ui': {'visibility': True},
'unit': {'id': data[6]},
'type': 'ppu',
'precision': 'decimal(2)',
}


def validate_input_file(api_url, api_key, input_file):
wb = _open_workbook(input_file)
if len(wb.sheetnames) != 2:
raise ClickException('Invalid input file: not enough sheets.')
product_id = wb.active['B5'].value
get_product(api_url, api_key, product_id)

ws = wb[wb.sheetnames[1]]
_validate_item_sheet(ws)

return product_id, wb


def sync_product(api_url, api_key, product_id, wb):
ws = wb[wb.sheetnames[1]]
row_indexes = trange(2, ws.max_row + 1, position=0)
for row_idx in row_indexes:
data = [ws.cell(row_idx, col_idx).value for col_idx in range(1, 9)]
row_indexes.set_description(f'Processing item {data[7] or data[1]}')
if data[7]:
item = get_item(api_url, api_key, product_id, data[7])
elif data[1]:
item = get_item_by_mpn(api_url, api_key, product_id, data[1])
else:
raise ClickException(
f'Invalid item at row {row_idx}: '
'one between MPN or Connect Item ID must be specified.'
if data[4] == 'reservation':
payload.update(commitment)

return payload

def _update_sheet_row(self, ws, row_idx, item):
ws.cell(row_idx, 1, value=item['id'])
ws.cell(row_idx, 10, value=item['status'])
ws.cell(row_idx, 11, value=item['events']['created']['at'])
ws.cell(row_idx, 12, value=item['events'].get('updated', {}).get('at'))

def validate_input_file(self, input_file):
self._open_workbook(input_file)
if len(self._wb.sheetnames) != 2:
raise ClickException('Invalid input file: not enough sheets.')
ws = self._wb[self._wb.sheetnames[0]]
product_id = ws['B5'].value
get_product(self._endpoint, self._api_key, product_id)
ws = self._wb[self._wb.sheetnames[1]]
self._validate_item_sheet(ws)

self._product_id = product_id
return self._product_id

def sync_product(self):
ws = self._wb[self._wb.sheetnames[1]]
row_indexes = trange(2, ws.max_row + 1, position=0, disable=self._silent)
for row_idx in row_indexes:
data = [ws.cell(row_idx, col_idx).value for col_idx in range(1, 13)]
row_indexes.set_description(f'Processing item {data[0] or data[1]}')
if data[0]:
item = get_item(self._endpoint, self._api_key, self._product_id, data[0])
elif data[1]:
item = get_item_by_mpn(self._endpoint, self._api_key, self._product_id, data[1])
else:
raise ClickException(
f'Invalid item at row {row_idx}: '
'one between MPN or Connect Item ID must be specified.'
)
if item:
row_indexes.set_description(f"Updating item {item['id']}")
if item['status'] == 'published':
payload = {
'name': data[2],
'mpn': data[1],
'description': data[3],
'ui': {'visibility': True},
}
else:
payload = self._get_item_payload(data)
if item['type'] == 'ppu':
del payload['period']
update_item(
self._endpoint,
self._api_key,
self._product_id,
item['id'],
payload,
)
self._update_sheet_row(ws, row_idx, item)
continue
row_indexes.set_description(f"Creating item {data[1]}")
item = create_item(
self._endpoint,
self._api_key,
self._product_id,
self._get_item_payload(data),
)
if item:
row_indexes.set_description(f"Updating item {item['id']}")
update_item(
api_url,
api_key,
product_id,
item['id'],
{
'name': data[0],
'mpn': data[1],
'description': data[4],
'ui': {'visibility': True},
},
)
continue
row_indexes.set_description(f"Creating item {data[1]}")
item = create_item(api_url, api_key, product_id, _get_item_payload(data))
ws.cell(row_idx, 8, value=item['id'])
self._update_sheet_row(ws, row_idx, item)

def save(self, output_file):
self._wb.save(output_file)
Loading

0 comments on commit 92fed7a

Please sign in to comment.