|
15 | 15 | from cnctcli.actions.products.constants import ITEMS_COLS_HEADERS |
16 | 16 | from cnctcli.api.products import ( |
17 | 17 | create_item, |
| 18 | + create_unit, |
18 | 19 | get_item, |
19 | 20 | get_item_by_mpn, |
20 | 21 | get_product, |
| 22 | + get_units, |
21 | 23 | update_item, |
22 | 24 | ) |
23 | 25 |
|
24 | 26 |
|
25 | | -def _open_workbook(input_file): |
26 | | - try: |
27 | | - return load_workbook(input_file) |
28 | | - except InvalidFileException as ife: |
29 | | - raise ClickException(str(ife)) |
30 | | - except BadZipFile: |
31 | | - raise ClickException(f'{input_file} is not a valid xlsx file.') |
32 | | - |
33 | | - |
34 | | -def _validate_item_sheet(ws): |
35 | | - cels = ws['A1': 'H1'] |
36 | | - for cel in cels[0]: |
37 | | - if cel.value != ITEMS_COLS_HEADERS[cel.column_letter]: |
38 | | - raise ClickException( |
39 | | - f'Invalid input file: column {cel.column_letter} ' |
40 | | - f'must be {ITEMS_COLS_HEADERS[cel.column_letter]}' |
41 | | - ) |
42 | | - |
| 27 | +class ProductSynchronizer: |
| 28 | + def __init__(self, endpoint, api_key, silent): |
| 29 | + self._endpoint = endpoint |
| 30 | + self._api_key = api_key |
| 31 | + self._silent = silent |
| 32 | + self._units = get_units(self._endpoint, self._api_key) |
| 33 | + self._product_id = None |
| 34 | + self._wb = None |
| 35 | + |
| 36 | + def _open_workbook(self, input_file): |
| 37 | + try: |
| 38 | + self._wb = load_workbook(input_file) |
| 39 | + except InvalidFileException as ife: |
| 40 | + raise ClickException(str(ife)) |
| 41 | + except BadZipFile: |
| 42 | + raise ClickException(f'{input_file} is not a valid xlsx file.') |
| 43 | + |
| 44 | + def _validate_item_sheet(self, ws): |
| 45 | + cels = ws['A1': 'H1'] |
| 46 | + for cel in cels[0]: |
| 47 | + if cel.value != ITEMS_COLS_HEADERS[cel.column_letter]: |
| 48 | + raise ClickException( |
| 49 | + f'Invalid input file: column {cel.column_letter} ' |
| 50 | + f'must be {ITEMS_COLS_HEADERS[cel.column_letter]}' |
| 51 | + ) |
| 52 | + |
| 53 | + def _get_commitment_count(self, data): |
| 54 | + period = data[7] |
| 55 | + if period == 'onetime': |
| 56 | + return 1 |
| 57 | + if data[8] == '-': |
| 58 | + return 1 |
| 59 | + try: |
| 60 | + years, _ = data[8].split() |
| 61 | + years = int(years) |
| 62 | + if period == 'monthly': |
| 63 | + return years * 12 |
| 64 | + return years |
| 65 | + except: # noqa |
| 66 | + return 1 |
| 67 | + |
| 68 | + def _get_or_create_unit(self, data): |
| 69 | + for unit in self._units: |
| 70 | + if unit['id'] == data[6]: |
| 71 | + return unit['id'] |
| 72 | + if unit['type'] == data[4] and unit['description'] == data[6]: |
| 73 | + return unit['id'] |
| 74 | + |
| 75 | + created = create_unit( |
| 76 | + self._endpoint, |
| 77 | + self._api_key, |
| 78 | + { |
| 79 | + 'description': data[6], |
| 80 | + 'type': data[4], |
| 81 | + 'unit': 'unit' if data[4] == 'reservation' else 'unit-h' |
| 82 | + }, |
| 83 | + ) |
| 84 | + return created['id'] |
43 | 85 |
|
44 | | -def _get_item_payload(data): |
45 | | - if data[3] is True: |
46 | | - # reservation |
47 | | - period = 'monthly' |
48 | | - if data[2].lower() == 'yearly': |
49 | | - period = 'yearly' |
50 | | - if data[2].lower() == 'onetime': |
51 | | - period = 'onetime' |
52 | | - return { |
53 | | - 'name': data[0], |
54 | | - 'mpn': data[1], |
55 | | - 'description': data[4], |
56 | | - 'ui': {'visibility': True}, |
| 86 | + def _get_item_payload(self, data): |
| 87 | + commitment = { |
57 | 88 | 'commitment': { |
58 | | - 'count': 12 if data[5] is True else 1, |
| 89 | + 'count': self._get_commitment_count(data), |
59 | 90 | }, |
60 | | - 'unit': {'id': data[6]}, |
61 | | - 'type': 'reservation', |
62 | | - 'period': period, |
63 | 91 | } |
64 | | - else: |
65 | | - # PPU |
66 | | - return { |
67 | | - 'name': data[0], |
| 92 | + payload = { |
68 | 93 | 'mpn': data[1], |
69 | | - 'description': data[4], |
| 94 | + 'name': data[2], |
| 95 | + 'description': data[3], |
| 96 | + 'type': data[4], |
| 97 | + 'precision': data[5], |
| 98 | + 'unit': {'id': self._get_or_create_unit(data)}, |
| 99 | + 'period': data[7], |
70 | 100 | 'ui': {'visibility': True}, |
71 | | - 'unit': {'id': data[6]}, |
72 | | - 'type': 'ppu', |
73 | | - 'precision': 'decimal(2)', |
74 | 101 | } |
75 | | - |
76 | | - |
77 | | -def validate_input_file(api_url, api_key, input_file): |
78 | | - wb = _open_workbook(input_file) |
79 | | - if len(wb.sheetnames) != 2: |
80 | | - raise ClickException('Invalid input file: not enough sheets.') |
81 | | - product_id = wb.active['B5'].value |
82 | | - get_product(api_url, api_key, product_id) |
83 | | - |
84 | | - ws = wb[wb.sheetnames[1]] |
85 | | - _validate_item_sheet(ws) |
86 | | - |
87 | | - return product_id, wb |
88 | | - |
89 | | - |
90 | | -def sync_product(api_url, api_key, product_id, wb): |
91 | | - ws = wb[wb.sheetnames[1]] |
92 | | - row_indexes = trange(2, ws.max_row + 1, position=0) |
93 | | - for row_idx in row_indexes: |
94 | | - data = [ws.cell(row_idx, col_idx).value for col_idx in range(1, 9)] |
95 | | - row_indexes.set_description(f'Processing item {data[7] or data[1]}') |
96 | | - if data[7]: |
97 | | - item = get_item(api_url, api_key, product_id, data[7]) |
98 | | - elif data[1]: |
99 | | - item = get_item_by_mpn(api_url, api_key, product_id, data[1]) |
100 | | - else: |
101 | | - raise ClickException( |
102 | | - f'Invalid item at row {row_idx}: ' |
103 | | - 'one between MPN or Connect Item ID must be specified.' |
| 102 | + if data[4] == 'reservation': |
| 103 | + payload.update(commitment) |
| 104 | + |
| 105 | + return payload |
| 106 | + |
| 107 | + def _update_sheet_row(self, ws, row_idx, item): |
| 108 | + ws.cell(row_idx, 1, value=item['id']) |
| 109 | + ws.cell(row_idx, 10, value=item['status']) |
| 110 | + ws.cell(row_idx, 11, value=item['events']['created']['at']) |
| 111 | + ws.cell(row_idx, 12, value=item['events'].get('updated', {}).get('at')) |
| 112 | + |
| 113 | + def validate_input_file(self, input_file): |
| 114 | + self._open_workbook(input_file) |
| 115 | + if len(self._wb.sheetnames) != 2: |
| 116 | + raise ClickException('Invalid input file: not enough sheets.') |
| 117 | + ws = self._wb[self._wb.sheetnames[0]] |
| 118 | + product_id = ws['B5'].value |
| 119 | + get_product(self._endpoint, self._api_key, product_id) |
| 120 | + ws = self._wb[self._wb.sheetnames[1]] |
| 121 | + self._validate_item_sheet(ws) |
| 122 | + |
| 123 | + self._product_id = product_id |
| 124 | + return self._product_id |
| 125 | + |
| 126 | + def sync_product(self): |
| 127 | + ws = self._wb[self._wb.sheetnames[1]] |
| 128 | + row_indexes = trange(2, ws.max_row + 1, position=0, disable=self._silent) |
| 129 | + for row_idx in row_indexes: |
| 130 | + data = [ws.cell(row_idx, col_idx).value for col_idx in range(1, 13)] |
| 131 | + row_indexes.set_description(f'Processing item {data[0] or data[1]}') |
| 132 | + if data[0]: |
| 133 | + item = get_item(self._endpoint, self._api_key, self._product_id, data[0]) |
| 134 | + elif data[1]: |
| 135 | + item = get_item_by_mpn(self._endpoint, self._api_key, self._product_id, data[1]) |
| 136 | + else: |
| 137 | + raise ClickException( |
| 138 | + f'Invalid item at row {row_idx}: ' |
| 139 | + 'one between MPN or Connect Item ID must be specified.' |
| 140 | + ) |
| 141 | + if item: |
| 142 | + row_indexes.set_description(f"Updating item {item['id']}") |
| 143 | + if item['status'] == 'published': |
| 144 | + payload = { |
| 145 | + 'name': data[2], |
| 146 | + 'mpn': data[1], |
| 147 | + 'description': data[3], |
| 148 | + 'ui': {'visibility': True}, |
| 149 | + } |
| 150 | + else: |
| 151 | + payload = self._get_item_payload(data) |
| 152 | + if item['type'] == 'ppu': |
| 153 | + del payload['period'] |
| 154 | + update_item( |
| 155 | + self._endpoint, |
| 156 | + self._api_key, |
| 157 | + self._product_id, |
| 158 | + item['id'], |
| 159 | + payload, |
| 160 | + ) |
| 161 | + self._update_sheet_row(ws, row_idx, item) |
| 162 | + continue |
| 163 | + row_indexes.set_description(f"Creating item {data[1]}") |
| 164 | + item = create_item( |
| 165 | + self._endpoint, |
| 166 | + self._api_key, |
| 167 | + self._product_id, |
| 168 | + self._get_item_payload(data), |
104 | 169 | ) |
105 | | - if item: |
106 | | - row_indexes.set_description(f"Updating item {item['id']}") |
107 | | - update_item( |
108 | | - api_url, |
109 | | - api_key, |
110 | | - product_id, |
111 | | - item['id'], |
112 | | - { |
113 | | - 'name': data[0], |
114 | | - 'mpn': data[1], |
115 | | - 'description': data[4], |
116 | | - 'ui': {'visibility': True}, |
117 | | - }, |
118 | | - ) |
119 | | - continue |
120 | | - row_indexes.set_description(f"Creating item {data[1]}") |
121 | | - item = create_item(api_url, api_key, product_id, _get_item_payload(data)) |
122 | | - ws.cell(row_idx, 8, value=item['id']) |
| 170 | + self._update_sheet_row(ws, row_idx, item) |
| 171 | + |
| 172 | + def save(self, output_file): |
| 173 | + self._wb.save(output_file) |
0 commit comments