diff --git a/rattail_corepos/batch/pos.py b/rattail_corepos/batch/pos.py index 040399c..08de88e 100644 --- a/rattail_corepos/batch/pos.py +++ b/rattail_corepos/batch/pos.py @@ -65,30 +65,67 @@ class POSBatchHandler(base.POSBatchHandler): def normalize_dtransactions(self, batch, rows, progress=None): dtransactions = [] + self.made_subtotal = False def add(row, i): - # TODO: row types ugh - - if row.row_type == 'sell': + if row.row_type == self.enum.POS_ROW_TYPE_SELL: d = self.make_d_item(row) dtransactions.append(d) - elif row.row_type == 'badscan': + elif row.row_type == self.enum.POS_ROW_TYPE_BADSCAN: d = self.make_d_badscan(row) dtransactions.append(d) - elif row.row_type in ('set_customer', 'swap_customer'): + elif row.row_type in (self.enum.POS_ROW_TYPE_SET_CUSTOMER, + self.enum.POS_ROW_TYPE_SWAP_CUSTOMER): d = self.make_d_customer(row) dtransactions.append(d) - elif row.row_type == 'tender': + elif row.row_type == self.enum.POS_ROW_TYPE_TENDER: + + if not self.made_subtotal: + d = self.make_d_subtotal(row, dtransactions) + dtransactions.append(d) + self.made_subtotal = True + d = self.make_d_tender(row) dtransactions.append(d) + elif row.row_type == self.enum.POS_ROW_TYPE_CHANGE_BACK: + + d = self.make_d_change(row) + dtransactions.append(d) + + d = self.make_d_discount(batch, dtransactions) + dtransactions.append(d) + + d = self.make_d_tax(batch, dtransactions) + dtransactions.append(d) + self.progress_loop(add, rows, progress, message="Normalizing items for CORE-POS transaction") + # now that we have all records, fill in some more values + session = self.app.get_session(batch) + store = self.config.get_store(session) + store_id = store.corepos_id if store else None + register_number = int(batch.terminal_id) + employee_number = batch.cashier.corepos_number + member = self.app.get_member(batch.customer) + member_type = member.membership_type.number if member else None + pos_row_id = f'corepos_pos_row_id_term_{batch.terminal_id}' + self.app.make_counter(session, pos_row_id) + for i, d in enumerate(dtransactions, 1): + d.store_id = store_id + d.register_number = register_number + d.employee_number = employee_number + d.card_number = batch.customer.number + d.member_type = member_type + d.staff = batch.customer_is_employee + d.transaction_id = i + d.pos_row_id = self.app.next_counter_value(session, pos_row_id) + return dtransactions def make_d_basic(self, batch=None, row=None): @@ -108,21 +145,38 @@ class POSBatchHandler(base.POSBatchHandler): # nb. batch.created *should* have a value..if not this would be "now" d.date_time = self.app.localtime(batch.created, from_utc=True) - if batch.terminal_id and batch.terminal_id.isdigit(): - d.register_number = int(batch.terminal_id) - - if batch.customer: - d.card_number = batch.customer.number - - d.quantity = 0 + # TODO: i *think* all these are safe defaults, and can + # override per line item as needed + d.transaction_status = '' + d.department_number = 0 d.unit_price = 0 + d.reg_price = 0 + d.tax_rate_id = 0 + d.food_stamp = False + d.member_discount = 0 + d.discount_type = 0 + d.percent_discount = 0 + d.quantity = 0 + d.item_quantity = 0 + d.volume_discount_type = 0 + d.volume_special = 0 + d.mix_match = 0 + d.upc = '0' + d.num_flag = 0 + d.char_flag = '' + d.cost = 0 d.discount = 0 + d.discountable = False d.total = 0 - # d.voided = False # TODO + d.voided = 0 + d.volume = 0 + d.matched = False + return d def make_d_badscan(self, row): d = self.make_d_basic(row=row) + d.description = 'BADSCAN' d.upc = row.item_entry @@ -135,17 +189,31 @@ class POSBatchHandler(base.POSBatchHandler): return d def make_d_customer(self, row): + batch = row.batch d = self.make_d_basic(row=row) + d.upc = 'MEMENTRY' d.description = 'CARDNO IN NUMFLAG' + + # TODO: what do these mean? are they correct? + d.transaction_type = 'L' + d.transaction_subtype = 'OG' + d.transaction_status = 'D' + d.num_flag = batch.customer.number + d.char_flag = '1' + return d def make_d_item(self, row): batch = row.batch + session = self.app.get_session(batch) d = self.make_d_basic(batch, row) + d.transaction_type = 'I' + d.transaction_subtype = 'NA' d.upc = row.product.item_id d.department_number = row.department_number + d.food_stamp = row.foodstamp_eligible d.description = row.product.description if d.description and len(d.description) > self.maxlen_description: @@ -153,18 +221,91 @@ class POSBatchHandler(base.POSBatchHandler): self.maxlen_description, len(d.description), d.description) d.description = d.description[:self.maxlen_description] + # TODO: should item_quantity ever differ? see also + # https://github.com/CORE-POS/IS4C/wiki/Office-Transaction-Database#dtransactions d.quantity = row.quantity + d.item_quantity = row.quantity + + if row.product.cost: + d.cost = row.product.cost.unit_cost + d.unit_price = row.txn_price d.reg_price = row.reg_price + d.discountable = row.product.discountable + + d.tax_rate_id = 0 + if row.tax_code: + tax = self.get_tax(session, row.tax_code) + d.tax_rate_id = tax.corepos_id + if not d.tax_rate_id: + log.error("tax not found in CORE-POS: %s", row.tax_code) + d.tax_rate_id = 0 + d.total = row.sales_total - # d.voided = False # TODO + # TODO: if void, should the above change too? + d.voided = 1 if row.void else 0 + + return d + + def make_d_subtotal(self, row, dtransactions): + batch = row.batch + d = self.make_d_basic(batch, row) + + d.transaction_type = 'C' + d.transaction_status = 'D' + d.voided = 3 # TODO (?) + + d.unit_price = sum([detail.total + for detail in dtransactions]) + + # TODO + tax = 0 + + d.description = f"Subtotal {d.unit_price:0.2f}, Tax{tax:0.2f} #{batch.customer.number}" + return d def make_d_tender(self, row): batch = row.batch d = self.make_d_basic(batch, row) + d.transaction_type = 'T' d.transaction_subtype = row.item_entry d.description = row.description d.total = row.tender_total + + return d + + def make_d_change(self, row): + batch = row.batch + d = self.make_d_basic(batch, row) + + d.transaction_type = 'T' + d.transaction_subtype = row.item_entry + d.description = "Change" + d.total = row.tender_total + + if not d.total: + d.voided = 8 # TODO (?) + + return d + + def make_d_discount(self, batch, dtransactions): + d = self.make_d_basic(batch) + + d.transaction_type = 'S' + d.upc = 'DISCOUNT' + d.quantity = 1 + d.item_quantity = 1 + d.description = "Discount" + + return d + + def make_d_tax(self, batch, dtransactions): + d = self.make_d_basic(batch) + + d.transaction_type = 'A' + d.upc = 'TAX' + d.description = "Tax" + return d