diff --git a/connector_spscommerce/README.rst b/connector_spscommerce/README.rst new file mode 100644 index 0000000..abf312b --- /dev/null +++ b/connector_spscommerce/README.rst @@ -0,0 +1,21 @@ +**This file is going to be generated by oca-gen-addon-readme.** + +*Manual changes will be overwritten.* + +Please provide content in the ``readme`` directory: + +* **DESCRIPTION.rst** (required) +* INSTALL.rst (optional) +* CONFIGURE.rst (optional) +* **USAGE.rst** (optional, highly recommended) +* DEVELOP.rst (optional) +* ROADMAP.rst (optional) +* HISTORY.rst (optional, recommended) +* **CONTRIBUTORS.rst** (optional, highly recommended) +* CREDITS.rst (optional) + +Content of this README will also be drawn from the addon manifest, +from keys such as name, authors, maintainers, development_status, +and license. + +A good, one sentence summary in the manifest is also highly recommended. \ No newline at end of file diff --git a/connector_spscommerce/__init__.py b/connector_spscommerce/__init__.py new file mode 100644 index 0000000..69f7bab --- /dev/null +++ b/connector_spscommerce/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/connector_spscommerce/__manifest__.py b/connector_spscommerce/__manifest__.py new file mode 100644 index 0000000..c677531 --- /dev/null +++ b/connector_spscommerce/__manifest__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'EDI Integration with SPS Commerce', + 'summary': 'Integrate with retail stores using SPS Commerce', + 'version': '11.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Open Source Integrators, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/connector-spscommerce', + 'depends': [ + 'account', + 'delivery', + 'sale_stock', + 'purchase', + 'sale_automatic_workflow', + ], + 'data': [ + 'views/sale_view.xml', + 'views/stock_view.xml', + 'views/company_config_settings_view.xml', + 'views/res_company.xml', + 'views/res_partner.xml', + 'views/account_invoice_view.xml', + ], + 'installable': True, + 'auto_install': False, + 'development_status': 'beta', + 'maintainers': ['smangukiya'], +} diff --git a/connector_spscommerce/edi_scripts/connect_info.py b/connector_spscommerce/edi_scripts/connect_info.py new file mode 100644 index 0000000..188ca97 --- /dev/null +++ b/connector_spscommerce/edi_scripts/connect_info.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# Set the parameters appropriately before attempting EDI connection +# Odoo Constants +USERNAME = '' +PWD = '' +DBNAME = '' +ERP_WWW = "http://:" or "ERP Domain" +EOL_MARKER = '~' +DEBUG = True +IN_PATH = '/opt/local-addons/connector_spscommerce/test/' diff --git a/connector_spscommerce/edi_scripts/csv_parser.py b/connector_spscommerce/edi_scripts/csv_parser.py new file mode 100644 index 0000000..f6ced76 --- /dev/null +++ b/connector_spscommerce/edi_scripts/csv_parser.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import csv +import itertools + +CSV_PATH = '/tmp/csv_import/' +OUT_PATH = '/tmp/csv_export/' + + +def parse_csv(file): + + # CSV COLUMNS + cols = [ + 'partner', + 'po_num', + 'date', + 'ship_to_code', + 'sku', + 'upc', + 'product_desc', + 'product_qty', + 'product_uom', + 'price_unit', + 'third_party', + 'address1', + 'address2', + 'city', + 'state', + 'zip', + 'country', + 'billing_contact', + 'billing_address1', + 'billing_address2', + 'billing_city', + 'billing_state', + 'billing_zip', + 'billing_country', + 'ship_not_before', + 'cancel_after' + ] + + csv_obj = open(CSV_PATH + file, 'rb') + data = csv.reader(csv_obj, delimiter=',') + count = 1 + record = {} + record['sku'] = [] + record['upc'] = [] + + for col in itertools.islice(cols, 6, 9): + record[col] = [] + + for row in data: + + if count == 1 and row[0] != 'H': + + print("""INFO: ***** FAILURE - YOUR FILE DOES NOT HAVE THE REQUIRED + FIRST ROW WITH COLUMN HEADINGS: + EDI Loop | Order line | Partner | PO # | Date/UPC | + code?/product desc | quantity | UOM Price ***** + """) + break + + if count > 2 and row[0] == '': + # sale_id, sale_name = process_record(sock, uid, record) + break + + if row[0] == 'H': + + if record['sku']: + + sale_id, sale_name = process_record(sock, uid, record) + record['sku'] = [] + record['upc'] = [] + + for col in itertools.islice(cols, 6, 9): + record[col] = [] + + record['ship_to_code'] = row[14] + record['ship_not_before'] = row[31] + record['cancel_after'] = row[32] + + # get partner, PO#, date + i = 2 + for col in itertools.islice(cols, 0, 3): + record[col] = row[i] + i += 1 + + # get thirdparty info + i = 7 + for col in itertools.islice(cols, 10, 16): + record[col] = row[i] + i += 1 + + # get billing address info + i = 15 + for col in itertools.islice(cols, 17, 24): + record[col] = row[i] + i += 1 + + elif row[0] == 'I': + + record['sku'].append(row[2]) + record['upc'].append(row[4]) + + # get sale line data: sku, asin, product description, quantity, + # uom, price + i = 6 + for col in itertools.islice(cols, 6, 9): + record[col].append(row[i]) + i += 1 + + count += 1 + + sale_id, sale_name = process_record(sock, uid, record) + + csv_obj.close() diff --git a/connector_spscommerce/edi_scripts/edi_850.py b/connector_spscommerce/edi_scripts/edi_850.py new file mode 100644 index 0000000..18353f4 --- /dev/null +++ b/connector_spscommerce/edi_scripts/edi_850.py @@ -0,0 +1,642 @@ +#!/usr/bin/python +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import sys +from datetime import datetime +from connect_info import DBNAME, PWD + + +def get_state(sock, uid, state): + args = [('code', '=', state)] + ids = sock.execute(DBNAME, uid, PWD, 'res.country.state', 'search', args) + + try: + state_id = ids[0] + return state_id + + except Exception: + "There are no states matching the code" + pass + + +def get_country(sock, uid, country): + args = [('code', '=', country)] + ids = sock.execute(DBNAME, uid, PWD, 'res.country', 'search', args) + + try: + country_id = ids[0] + return country_id + + except Exception: + "There are no states matching the code" + pass + + +def get_current_company(sock, uid): + + args = [('partner_id', '=', 1)] + ids = sock.execute(DBNAME, uid, PWD, 'res.company', 'search', args) + + try: + company_id = ids[0] + return company_id + + except Exception: + "There is no company with partner_id = 1" + pass + + +def edi_config_id( + sock, + uid, + company_id, + partner_header_string, + vendor_header_string): + + args = [('partner_header_string', '=', partner_header_string), + ('vendor_header_string', '=', vendor_header_string)] + ids = sock.execute(DBNAME, uid, PWD, 'edi.config', 'search', args) + edi = [] + + try: + edi = ids[0] + + except Exception: + print("There are no trading partners to process") + pass + + return edi + + +def get_config(sock, uid, edi): + + fields = [ + 'partner_header_string', + 'vendor_header_string', + 'salesperson', + 'edi_company_id', + 'in_path', + 'out_path', + 'log_path', + 'archive_path', + 'trading_partner_id', + 'ack_855', + 'auto_workflow'] + record = sock.execute(DBNAME, uid, PWD, 'edi.config', 'read', edi, fields) + try: + rec = record['trading_partner_id'] + rec = record['out_path'] + rec = record['archive_path'] + return record + except Exception: + print("""There are no trading partners to process, paths not set or + EOL marker not defined.""") + pass + + +def get_partner_info(sock, uid, partner_id): + + fields = [ + 'is_company', + 'city', + 'country_id', + 'company_id', + 'email', + 'fax', + 'name', + 'phone', + 'state_id', + 'street', + 'street2', + 'website', + 'zip', + 'ship_to_code', + 'user_id', + 'property_product_pricelist'] + + try: + result = sock.execute( + DBNAME, uid, PWD, 'res.partner', 'read', partner_id, fields) + return result + + except Exception: + "There are no partners to process" + pass + + +def get_shipping_partner(sock, uid, partner_id, ship_to_code): + args = [('parent_id', '=', partner_id), + ('ship_to_code', '=', ship_to_code)] + ids = sock.execute(DBNAME, uid, PWD, 'res.partner', 'search', args) + shipping_id = False + try: + shipping_id = ids[0] + except Exception: + "There are no shipping partners to process" + pass + + return shipping_id + + +def get_billing_partner(sock, uid, partner_id): + args = [('parent_id', '=', partner_id), ('type', '=', 'invoice')] + ids = sock.execute(DBNAME, uid, PWD, 'res.partner', 'search', args) + billing_id = False + try: + billing_id = ids[0] + except Exception: + "There are no billing partners to process" + pass + + return billing_id + + +def search_uom(sock, uid, uom): + + uom_id = '' + + args = [('name', 'ilike', uom)] + ids = sock.execute(DBNAME, uid, PWD, 'product.uom', 'search', args) + try: + uom_id = ids[0] + except Exception: + pass + + return uom_id + + +def search_pmt_terms(sock, uid, payment_terms): + + pmt_id = '' + + args = [('name', 'ilike', payment_terms)] + ids = sock.execute( + DBNAME, uid, PWD, 'account.payment.term', 'search', args) + try: + pmt_id = ids[0] + except Exception: + pass + + return pmt_id + + +def search_incoterm(sock, uid, code): + + incoterm_id = '' + + args = [('code', 'ilike', code)] + ids = sock.execute(DBNAME, uid, PWD, 'stock.incoterms', 'search', args) + try: + incoterm_id = ids[0] + except Exception: + pass + + return incoterm_id + + +def get_sale_name(sock, uid, sale_id): + + fields = ['name'] + result = sock.execute(DBNAME, uid, PWD, 'sale.order', + 'read', sale_id, fields) + return result + + +def search_cust_id(sock, uid, partner_name): + + cust_id = '' + + args = [('name', 'ilike', partner_name)] + ids = sock.execute(DBNAME, uid, PWD, 'res.partner', 'search', args) + try: + cust_id = ids[0] + except Exception: + pass + return cust_id + + +def search_channel_id(sock, uid): + + channel_id = False + + args = [('code', '=', 'edi')] + ids = sock.execute(DBNAME, uid, PWD, 'sale.channel', 'search', args) + try: + channel_id = ids[0] + except Exception: + pass + return channel_id + + +def search_user_id(sock, uid, user_name): + + user_id = '' + + args = [('name', 'ilike', user_name)] + ids = sock.execute(DBNAME, uid, PWD, 'res.users', 'search', args) + try: + user_id = ids[0] + except Exception: + pass + return user_id + + +def get_shop_location(sock, uid, warehouse_id): + + src_location_id = sock.execute( + DBNAME, + uid, + PWD, + 'stock.warehouse', + 'read', + warehouse_id, + ['lot_stock_id']) + + print('shop stock loc: ' + str(src_location_id)) + + return src_location_id + + +def search_pricelist_id(sock, uid, pricelist): + + pricelist_id = '' + + args = [('name', '=', pricelist_id)] + ids = sock.execute(DBNAME, uid, PWD, 'product.pricelist', 'search', args) + try: + pricelist_id = ids[0] + except Exception: + pass + return pricelist_id + + +def rename_picking_pol(picking_policy): + + pick_pol = 'one' + if (picking_policy == 'Deliver each product when available'): + pick_pol = 'direct' + return pick_pol + + +def rename_order_pol(order_policy): + order_pol = '' + order_pol_1 = ['manual', 'picking', 'prepaid'] + order_pol_2 = ['On Demand', 'On Delivery Order', 'Before Delivery'] + i = 0 + for pol in order_pol_2: + if order_policy == pol: + order_pol = order_pol_1[i] + break + i += 1 + if order_pol: + return order_pol + else: + print("""FAILURE - ORDER POLICY MUST BE: On Demand, On Delivery Order, + OR Before Delivery""") + + +def check_po_number(sock, uid, client_order_ref, partner_id): + + result = False + # If PO number is found in EDI + if client_order_ref: + + sale_ids = sock.execute(DBNAME, + uid, + PWD, + 'sale.order', + 'search', + [('partner_id', + '=', + partner_id), + ('state', + '!=', + 'cancel'), + ('client_order_ref', + '=', + client_order_ref)]) + + # if sale_ids from the above search, then po number is duplicated, warn + # user, and do not input the sale order + if sale_ids: + print("""This is a duplicate PO and will not be pushed + into the system.""") + result = True + + return result + + +def create_sale_order( + sock, + uid, + date, + po_num, + partner_id, + partner_invoice_id, + partner_shipping_id, + third_party, + address1, + address2, + city, + state, + zip, + country, + ship_to_code, + trading_partner_id, + ack_bool, + automatic_workflow_id): + + partner_info = get_partner_info(sock, uid, partner_id) + + user_id = False + if partner_info['user_id']: + user_id = partner_info['user_id'][0] + + pricelist_id = False + if partner_info['property_product_pricelist']: + pricelist_id = partner_info['property_product_pricelist'][0] + + src_location_id = '' + + # based on partner's billing address, look up in partner record to find + # billing contact + if partner_id: + + if get_billing_partner(sock, uid, partner_id): + partner_invoice_id = get_billing_partner(sock, uid, partner_id) + + # based on ship_to_code, look up in partner record to find shipping contact + if ship_to_code: + + if get_shipping_partner(sock, uid, partner_id, ship_to_code): + partner_shipping_id = get_shipping_partner( + sock, uid, partner_id, ship_to_code) + sale_hash = { + 'incoterm': 14, # hardcoded to "Delivery at Place" + 'date_order': date, + 'client_order_ref': po_num, + 'origin': '', + 'note': '', + 'user_id': user_id, + 'partner_id': partner_id, + # 'carrier_id': carrier_id, + 'pricelist_id': pricelist_id, + # 'payment_term': payment_term, + 'company_id': 1, + 'order_policy': 'picking', # hardcoded to "On Delivery" + 'partner_invoice_id': partner_invoice_id, + 'partner_shipping_id': partner_shipping_id, + 'edi_yes': 'TRUE', + 'ack_yes': ack_bool, + 'ack_sent': 'FALSE', + 'ship_to_code': ship_to_code, + 'trading_partner_id': trading_partner_id, + 'auto_workflow': automatic_workflow_id[0], + 'edi_est_so_ship_date': datetime.now().strftime('%Y-%m-%d') + } + + print("Attempting to create %s" % sale_hash) + sale_id = sock.execute(DBNAME, uid, PWD, 'sale.order', 'create', sale_hash) + return sale_id, src_location_id, pricelist_id + + +def search_product(sock, uid, product_upc, product_sku): + + args = [('default_code', '=', product_sku)] + ids = sock.execute(DBNAME, uid, PWD, 'product.product', 'search', args) + try: + product_id = ids[0] + except Exception: + product_id = 'no_exist' + print('product does not exist') + + return product_id + + +def get_product(sock, uid, product_id): + + fields = ['available_sale', 'uom_id', + 'weight', 'standard_price', 'default_code'] + record = sock.execute( + DBNAME, uid, PWD, 'product.product', 'read', product_id, fields) + try: + return record + except Exception: + pass + + +def rename_order_type(order_type): + + order_type_1 = ['from stock', 'on order'] + order_type_2 = ['make_to_stock', 'make_to_order'] + i = 0 + for otype in order_type_1: + if order_type == otype: + order_type = order_type_2[i] + break + i += 1 + return order_type + + +def create_so_lines( + sock, + uid, + sale_id, + product_upcs, + product_skus, + product_descs, + qtys, + uoms, + prices, + src_location_id, + ack_bool, + po_lines, + config_rec, + pricelist_id): + + sale_line_ids = [] + p_ids = [] + products_not_found = '' + + product_codes = product_upcs + if product_skus: + product_codes = product_skus + + for x in xrange(len(product_codes)): + + qty = qtys[x] + uom = uoms[x] + # price = prices[x] + uom_id = search_uom(sock, uid, uom) + product_code = product_codes[x] + product_sku = product_skus[x] + product_upc = product_upcs[x] + po_line = po_lines[x] + p_id = search_product(sock, uid, product_upc, product_sku) + partner_id = config_rec['trading_partner_id'][0] + + if p_id is not 'no_exist': + + price = sock.execute( + DBNAME, + uid, + PWD, + 'product.pricelist', + 'price_get_wrapper', + pricelist_id, + p_id, + qty, + partner_id) + product_info = get_product(sock, uid, p_id) + uom_id = product_info['uom_id'] + weight = product_info['weight'] + p_ids.append(p_id) + + order_hash = { + 'name': product_info['default_code'], + 'edi_line_num': po_line, + 'product_uos_qty': int(qty), + 'product_uom_qty': int(qty), + 'edi_line_qty': int(qty), + 'state': 'draft', + 'order_id': sale_id, # from sale order we just created + 'invoiced': False, + 'th_weight': weight, # lookup from product + 'product_id': p_id, + 'edi_yes': 'TRUE', + 'ack_yes': ack_bool, + 'price_unit': price, # passed in from PO + # lookup from product + 'purchase_price': product_info['standard_price'], + 'product_uom': uom_id[0], # lookup from product + } + + try: + line_id = sock.execute( + DBNAME, uid, PWD, 'sale.order.line', 'create', order_hash) + sale_line_ids.append(line_id) + + except Exception: + print("Could not add the order line", sys.exc_info()[0]) + + else: + + print("""INFO: ***** FAILURE TO FIND PRODUCT WITH UPC CODE: + """ + str(product_code)) + products_not_found = products_not_found + '\n Line not input: ' + \ + qty + ' ' + uom + ' of ' + product_code + + if products_not_found: + sock.execute(DBNAME, uid, PWD, 'sale.order', 'write', + sale_id, {'edi_error': products_not_found}) + + return p_ids, sale_line_ids + + +def process_record(sock, uid, record, partner_rec, config_rec): + + ack_bool = config_rec['ack_855'] + + # We're using fullfillment_channel to store Sale Automatic Workflow ID per + # customer, this is attached to the order + print("Automatic workflow is: " + str(config_rec['auto_workflow'])) + automatic_workflow_id = config_rec['auto_workflow'] + # use the 850 PO info to create a sales order + sale_id, src_location_id, pricelist_id = \ + create_sale_order(sock, uid, + record['date'], + record['po_num'], + record['partner_id'], + record['partner_invoice_id'], + record['partner_shipping_id'], + record['third_party'], + record['address1'], + record['address2'], + record['city'], + record['state'], + record['zip'], + record['country'], + record['ship_to_code'], + config_rec['id'], + ack_bool, + automatic_workflow_id) + + # get the sale name from the order you just created + sale_name = get_sale_name(sock, uid, sale_id) + + # create SO LINE from dictionary we just created + product_ids, sale_line_ids = \ + create_so_lines(sock, uid, sale_id, + record['upc'], + record['sku'], + record['product_desc'], + record['product_qty'], + record['product_uom'], + record['price_unit'], + src_location_id, + ack_bool, + record['po_line'], + config_rec, + pricelist_id) + + return sale_id, sale_name, product_ids + + +def main(sock, uid, order_dict, in_path): + + sale_id = False + company_id = False + + if not company_id: + company_id = get_current_company(sock, uid) + + # get edi_config_id from company record + edi_id = edi_config_id(sock, uid, company_id, + order_dict['partner'], order_dict['vendor']) + # print 'EDI: ' + str(edi_id) + + # get configuration settings from trading_partner + + if edi_id: + + config = get_config(sock, uid, edi_id) + + # get edi trading partner info + partner_rec = get_partner_info( + sock, uid, config['trading_partner_id'][0]) + order_dict['partner_id'] = partner_rec['id'] + order_dict['partner_invoice_id'] = partner_rec['id'] + order_dict['partner_shipping_id'] = partner_rec['id'] + + # process record and input sale order with multiple order lines in + # OpenERP + + print("Processing PO#: " + str(order_dict['po_num'])) + if not check_po_number( + sock, + uid, + order_dict['po_num'], + order_dict['partner_id']): + + try: + sale_id, sale_name, product_ids = process_record( + sock, uid, order_dict, partner_rec, config) + + except Exception: + print("""Cannot process order.Sale order or order + lines not created. """, sys.exc_info()) + + print("Sale added id: " + str(sale_id)) + else: + print('''There is no trading partner that matches the partner header + string in the 850 file.''') + print('''Please verify that there are trading partners defined and + that the header strings are correctly defined on them.''') + + return sale_id + + +if __name__ == '__main__': + print('Process: EDI In - Starting') + main() + print('Process: EDI In - Ending') diff --git a/connector_spscommerce/edi_scripts/edi_process_in.py b/connector_spscommerce/edi_scripts/edi_process_in.py new file mode 100644 index 0000000..2243558 --- /dev/null +++ b/connector_spscommerce/edi_scripts/edi_process_in.py @@ -0,0 +1,885 @@ +#!/usr/bin/python +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os +import xmlrpclib +import edi_850 +import shutil +import xmltodict +from datetime import datetime +from connect_info import ERP_WWW, DBNAME, USERNAME, PWD, DEBUG, IN_PATH + + +test_me = """ + + + + testVersion + +
+ + 000ALLTESTID + 1010101010101 + 00 + OS + This is a test. Do not ship + XYZ999999 + 2013-08-29 + 21:05:49-06:00 + Y + testBuyersCurrency + 026 + 99999999 + Sam's Club + 55555555 + + + 09 + 8 + 5.62 + 2013-11-28 + 60 + 2013-11-15 + 90 + 1845.08 + Must be paid in full by end of + year + AB + 999888777 + + + 043 + 2012-10-31 + 16:13:03-05:00 + + + IC + SPS Commerce + 866-245-8100 + 612-435-9401 + info@spscommerce.com + +
+ FW + 9 + 11111 + SPS Commerce + ATTN: Marketing + 333 South 7th Street + ATTN: The President + Washington + MN + 55402 + USA + + BI + SPS Commerce + 866-245-8100 + 612-435-9401 + info@spscommerce.com + +
+ + RS + MI + Minnesota + + + SB + EMSY + ONTRAC + + + GD + testReferenceID + New products only. Do not reuse packaging + + + GEN + REPEAT LOGO PREVIOUS ORDER + + + EV + 1845.08 + 8.50 + CC + 07034 + 0 + 99990000 + + + A + F330 + 85.02 + 1 + 5.0 + 02 + This will cover the cost of shipping + + +
+ + + + 01 + 9999-SPS + 11155-559999 + 093597609541 + 1234567890123 + 12345678901234 + 98765432101 + 51456-299 + 999-0-555-22222-0 + + + VC + ABC-456789 + + 125 + DA + 5.85 + testBuyersCurrency + S-800 + Small + C-999 + Fire Truck Red + Faux Fabric + 026 + testClass + + 600 + 42-10651 + + + + 069 + 2012-10-31 + 16:13:03-05:00 + + + RPC + 5.48 + + + RE + Super comfortable + + + CP + 6 + 18 + BO + + + BY + testReferenceID + New products only. Do not reuse packaging + + + CAFA3 + REPEAT LOGO PREVIOUS ORDER + + + + N + S + Hanger with clips + A-112233 + + + + + 02 + 9999-SPS + 11155-559999 + 093597609541 + 1234567890123 + 12345678901234 + 98765432101 + 51456-299 + 999-0-555-22222-0 + + + N5 + ABC-456789 + + S-800 + Small + C-999 + Fire Truck Red + Faux Fabric + + 5 + PP + 5.85 + + 600 + 42-10651 + + + + RTL + 5.48 + + + 74 + Super comfortable + + + Y + H + Hanger with clips + A-112233 + + + + + 100 + P9 + + 097 + 2013-12-21 + 16:13:03-05:00 + + + + CA + 1845.08 + 8.50 + CC + 07034 + 2 + 99990000 + + + C + D550 + 85.02 + 1 + 5.0 + 01 + This will cover the cost of shipping + + + + + + 4058.92 + 3 + 90 + +
+
""" + + +def connect_oerp(): + ''' + Function that will make a connection to OpenERP using XML-RPC. + @return1: socket that is connected to OpenERP + @return2: An id that shows that you have been validated + ''' + sock = xmlrpclib.ServerProxy(ERP_WWW + '/xmlrpc/object') + sock_common = xmlrpclib.ServerProxy(ERP_WWW + '/xmlrpc/common') + uid = sock_common.login(DBNAME, USERNAME, PWD) + + print("XMLRPC Connection: SUCCESS - SERVER HAS AUTHENTICATED A LOGIN") + + return sock, uid + + +def parse_csv(sock, uid, file): + + # note xmltodict.unparse(file) will convert it back to xml. It will also + # convert any dict to xml. + orders = xmltodict.parse(test_me) +# order_dict = xmltodict.parse(test_me) + record = {} + + # improve process in + for order in orders['Orders']: + record['address_type_code'] =\ + order['Order']['Header']['Address']['AddressTypeCode'] + record['location_code_qualifier'] =\ + order['Order']['Header']['Address']['LocationCodeQualifier'] + record['address_location_number'] =\ + order['Order']['Header']['Address']['AddressLocationNumber'] + record['address_name'] =\ + order['Order']['Header']['Address']['AddressName'] + record['address_alternate_name'] =\ + order['Order']['Header']['Address']['AddressAlternateName'] + record['address1'] = order['Order']['Header']['Address']['Address1'] + record['address2'] = order['Order']['Header']['Address']['Address2'] + record['city'] = order['Order']['Header']['Address']['City'] + record['state'] = order['Order']['Header']['Address']['State'] + record['postal_code'] =\ + order['Order']['Header']['Address']['PostalCode'] + record['country'] = order['Order']['Header']['Address']['Country'] + record['contact'] = {} + record['contact']['contact_type_code'] =\ + order['Order']['Header']['Address']['Contact']['ContactTypeCode'] + record['contact']['contact_name'] =\ + order['Order']['Header']['Address']['Contact']['ContactName'] + record['contact']['primary_phone'] =\ + order['Order']['Header']['Address']['Contact']['PrimaryPhone'] + record['contact']['primary_fax'] =\ + order['Order']['Header']['Address']['Contact']['PrimaryFax'] + record['contact']['primary_email'] =\ + order['Order']['Header']['Address']['Contact']['PrimaryEmail'] + record['date'] = order['Order']['Header']['Date']['Date1'] + record['time'] = order['Order']['Header']['Date']['Time1'] + record['edi_error'] = False + record['edi_yes'] = False + record['ack_yes'] = True + record['replace'] = True + record['supplier_code'] = '123' + record['ship_not_before_date'] = datetime.now().strftime('%Y-%m-%d') + record['cancel_after_date'] = datetime.now().strftime('%Y-%m-%d') + record['trading_partner_id'] =\ + order['Order']['Header']['OrderHeader']['TradingPartnerId'] + record['scac_code'] = '' + record['bol_num'] = '' + record['asn_shipment'] = '' + record['tracking_num'] = '' + record['tset_purpose_code'] =\ + order['Order']['Header']['OrderHeader']['TsetPurposeCode'] + record['purchase_order_number'] =\ + order['Order']['Header']['OrderHeader']['PurchaseOrderNumber'] + record['purchase_order_type_code'] =\ + order['Order']['Header']['OrderHeader']['PurchaseOrderTypeCode'] + record['po_type_description'] =\ + order['Order']['Header']['OrderHeader']['POTypeDescription'] + record['ship_complete_code'] =\ + order['Order']['Header']['OrderHeader']['ShipCompleteCode'] + record['department'] =\ + order['Order']['Header']['OrderHeader']['Department'] + record['division'] =\ + order['Order']['Header']['OrderHeader']['Division'] + record['promotion_deal_number'] =\ + order['Order']['Header']['OrderHeader']['PromotionDealNumber'] + record['terms_type'] =\ + order['Order']['Header']['PaymentTerms']['TermsType'] + record['terms_basis_date_code'] =\ + order['Order']['Header']['PaymentTerms']['TermsBasisDateCode'] + record['terms_discount_percentage'] =\ + order['Order']['Header']['PaymentTerms']['TermsDiscountPercentage'] + record['terms_discount_due_days'] =\ + order['Order']['Header']['PaymentTerms']['TermsDiscountDueDays'] + record['terms_net_due_days'] =\ + order['Order']['Header']['PaymentTerms']['TermsNetDueDays'] + record['payment_method_code'] =\ + order['Order']['Header']['PaymentTerms']['PaymentMethodCode'] + record['fob_pay_code'] =\ + order['Order']['Header']['FOBRelatedInstruction']['FOBPayCode'] + record['fob_location_qualifier'] =\ + order['Order']['Header']['FOBRelatedInstruction' + ]['FOBLocationQualifier'] + record['fob_location_description'] =\ + order['Order']['Header']['FOBRelatedInstruction' + ]['FOBLocationDescription'] + record['fob_title_passage_code'] = '' + record['fob_title_passage_location'] = '' + record['carrier_trans_method_code'] =\ + order['Order']['Header']['CarrierInformation' + ]['CarrierTransMethodCode'] + record['carrier_alpha_code'] =\ + order['Order']['Header']['CarrierInformation']['CarrierAlphaCode'] + record['carrier_routing'] =\ + order['Order']['Header']['CarrierInformation']['CarrierRouting'] + record['routing_sequence_code'] = '' + record['service_level_code'] = '' + record['reference_qual'] =\ + order['Order']['Header']['Reference']['ReferenceQual'] + record['reference_id'] =\ + order['Order']['Header']['Reference']['ReferenceID'] + record['ref_description'] =\ + order['Order']['Header']['Reference']['Description'] + record['note_code'] = order['Order']['Header']['Notes']['NoteCode'] + record['note_information_field'] =\ + order['Order']['Header']['Notes']['NoteInformationField'] + record['allow_chrg_indicator'] =\ + order['Order']['Header']['ChargesAllowances']['AllowChrgIndicator'] + record['allow_chrg_code'] =\ + order['Order']['Header']['ChargesAllowances']['AllowChrgCode'] + record['allow_chrg_agency_code'] = '' + record['allow_chrg_agency'] = '' + record['allow_chrg_amt'] =\ + order['Order']['Header']['ChargesAllowances']['AllowChrgAmt'] + record['allow_chrg_percent_qual'] =\ + order['Order']['Header']['ChargesAllowances' + ]['AllowChrgPercentQual'] + record['allow_chrg_percent'] =\ + order['Order']['Header']['ChargesAllowances']['AllowChrgPercent'] + record['allow_chrg_handling_code'] =\ + order['Order']['Header']['ChargesAllowances' + ]['AllowChrgHandlingCode'] + record['reference_identification'] = '' + record['allow_chrg_handling_description'] =\ + order['Order']['Header']['ChargesAllowances' + ]['AllowChrgHandlingDescription'] + + record['order_line'] = {} + for line in orders['Orders']['Order']['LineItems']: + order_line_data = { + 'product_qty': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['OrderQty'], + 'product_uom': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['OrderQtyUOM'], + 'sku': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['VendorPartNumber'], + 'edi_yes': False, + 'asn_shipment': '123', + 'po_number': '123', + 'buyer_part_number': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['BuyerPartNumber'], + 'edi_line_num': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['LineSequenceNumber'], + 'edi_line_qty': '123', + 'ack_yes': '123', + 'edi_est_del_date': datetime.now().strftime('%Y-%m-%d'), + 'edi_est_ship_date': datetime.now().strftime('%Y-%m-%d'), + 'edi_line_msg': '123', + 'ship_not_before_date': datetime.now().strftime('%Y-%m-%d'), + 'cancel_after_date': datetime.now().strftime('%Y-%m-%d'), + 'trading_partner_id': orders['Orders']['Order']['Header'] + ['OrderHeader']['TradingPartnerId'], + 'edi_intransit_qty': 0, + 'edi_outgoing_qty': 0, + 'edi_avlforsale_qty': 0, + 'vendor_part_number': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['VendorPartNumber'], + 'consumer_package_code': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['ConsumerPackageCode'], + 'gtin': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['GTIN'], + 'upc_case_code': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['UPCCaseCode'], + 'purchase_price_basis': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['PurchasePrice'], + 'product_size_code': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['ProductSizeCode'], + 'product_size_description': orders['Orders']['Order'] + ['LineItems'][line]['OrderLine']['ProductSizeDescription'], + 'product_color_code': orders['Orders']['Order']['LineItems'] + [line]['OrderLine']['ProductColorCode'], + 'product_color_description': orders['Orders']['Order'] + ['LineItems'][line]['OrderLine']['ProductColorDescription'], + # 'product_material_code':orders['Orders']['Order'] + # ['LineItems'][line]['OrderLine']['ProductMaterialCode'], + 'product_material_description': orders['Orders']['Order'] + ['LineItems'][line]['OrderLine']['ProductMaterialDescription'], + 'department': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['Department'], + 'classs': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['Class'], + 'price_type_id_code': orders['Orders']['Order']['LineItems'] + [line]['PriceInformation']['PriceTypeIDCode'], + 'edi_line_num': orders['Orders']['Order']['LineItems'][line] + ['OrderLine']['LineSequenceNumber'], + # 'multiple_price_quantity':orders['Orders']['Order'] + # ['LineItems'][line]['PriceInformation'] + # ['MultiplePriceQuantity'], + # 'class_of_trade_code':orders['Orders']['Order']['LineItems' + # [line]['PriceInformation']['ClassOfTradeCode'], + 'item_description_type': orders['Orders']['Order']['LineItems'] + [line]['ProductOrItemDescription']['ItemDescriptionType'], + 'product_characteristic_code': '', + 'agency_qualifier_code': '', + 'product_description_code': '', + 'pack_qualifier': orders['Orders']['Order']['LineItems'] + [line]['PhysicalDetails']['PackQualifier'], + 'pack_value': orders['Orders']['Order']['LineItems'][line] + ['PhysicalDetails']['PackValue'], + 'pack_size': orders['Orders']['Order']['LineItems'][line] + ['PhysicalDetails']['PackSize'], + 'pack_uom': orders['Orders']['Order']['LineItems'][line] + ['PhysicalDetails']['PackUOM'], + 'packing_medium': '', + 'packing_material': '', + 'pack_weight': 0, + 'pack_weight_uom': '', + 'location_code_qualifier': '', + 'location': '', + 'allow_chrg_indicator': orders['Orders']['Order']['LineItems'] + [line]['ChargesAllowances']['AllowChrgIndicator'], + 'allow_chrg_code': orders['Orders']['Order']['LineItems'] + [line]['ChargesAllowances']['AllowChrgCode'], + 'allow_chrg_agency_code': '', + 'allow_chrg_agency': '', + 'allow_chrg_amt': orders['Orders']['Order']['LineItems'][line] + ['ChargesAllowances']['AllowChrgAmt'], + 'allow_chrg_percent': orders['Orders']['Order']['LineItems'] + [line]['ChargesAllowances']['AllowChrgPercent'], + 'percent_dollar_basis': 0, + 'allow_chrg_rate': 0, + 'allow_chrg_handling_code': orders['Orders']['Order'] + ['LineItems'][line]['ChargesAllowances'] + ['AllowChrgHandlingCode'], + 'allow_chrg_qty_uom': '', + # 'allow_chrg_handling_description':orders['Orders']['Order'] + # ['LineItems'][line]['ChargesAllowances']['allow_chrg_handling_description'], + } + record['order_line'].update(order_line_data) + print("Stop", record) + sale_id = create_sale_order(sock, uid, record) + print("Successfully created ORDER IMPORTED!!!!", sale_id) + line_ids = create_so_lines(sock, uid, sale_id, record['order_line']) + print("Successfully created ORDER LINES IMPORTED!!!!", line_ids) + return parse_csv + + +def get_order_header(order_header): + """Inputs: + order_header = " + + 000ALLTESTID + 1010101010101 + 00 + OS + This is a test. Do not ship + XYZ999999 + 2013-08-29 + 21:05:49-06:00 + Y + testBuyersCurrency + 026 + 99999999 + Sam's Club + 55555555 + " + Output: dictionary with values for header to be used in sale order + import + """ + return\ + order_header['Department'], \ + order_header['PurchaseOrderNumber'], \ + order_header['TradingPartnerId'], \ + order_header['PurchaseOrderDate'] + + +def get_state(sock, uid, state): + args = [('name', '=', state)] + ids = sock.execute(DBNAME, uid, PWD, 'res.country.state', 'search', args) + + try: + state_id = ids[0] + return state_id + + except Exception: + "There are no states matching the code" + return False + + +def get_country(sock, uid, country): + args = [('name', '=', country)] + ids = sock.execute(DBNAME, uid, PWD, 'res.country', 'search', args) + + try: + country_id = ids[0] + return country_id + + except Exception: + "There are no states matching the code" + return False + + +def get_partner_info( + sock, + uid, + partner_contact, + address1='', + address2='', + city='', + state='', + postal_code='', + country=''): + + fields = [ + 'is_company', + 'city', + 'country_id', + 'company_id', + 'email', + 'fax', + 'name', + 'phone', + 'state_id', + 'street', + 'street2', + 'website', + 'zip', + 'ship_to_code', + 'user_id', + 'property_product_pricelist'] + + try: + # check for existing partner if not then create + print("LOOK FOR THIS", partner_contact['contact_name']) + args = [('name', '=', partner_contact['contact_name'])] + ids = sock.execute(DBNAME, uid, PWD, 'res.partner', 'search', args) + if ids: + result = sock.execute( + DBNAME, uid, PWD, 'res.partner', 'read', ids[0], fields) + else: + partner_data = { + 'name': partner_contact['contact_name'], + 'phone': partner_contact['primary_phone'], + 'email': partner_contact['primary_email'], + 'fax': partner_contact['primary_fax'], + 'street': address1, + 'street2': address2, + 'city': city, + 'zip': postal_code, + 'state_id': get_state(sock, uid, state), + 'country_id': get_country(sock, uid, country), + 'user_id': uid, + } + partner_id = sock.execute( + DBNAME, uid, PWD, 'res.partner', 'create', partner_data) + result = sock.execute( + DBNAME, uid, PWD, 'res.partner', 'read', partner_id, fields) + return result + + except Exception: + "There are no partners to process" + pass + + +def search_product(sock, uid, product_sku): + + args = [('default_code', '=', product_sku)] + ids = sock.execute(DBNAME, uid, PWD, 'product.product', 'search', args) + try: + product_id = ids[0] + except Exception: + product_id = 'no_exist' + print('product does not exist') + + return product_id + + +def create_sale_order(sock, uid, order_data): + partner_info = get_partner_info( + sock, + uid, + order_data['contact'], + order_data['address1'], + order_data['address2'], + order_data['city'], + order_data['state'], + order_data['postal_code'], + order_data['country']) + user_id = False + if partner_info['user_id']: + user_id = partner_info['user_id'][0] + + pricelist_id = False + if partner_info['property_product_pricelist']: + pricelist_id = partner_info['property_product_pricelist'][0] + + # based on partner's billing address, look up in partner record to find + # billing contact + if partner_info['id']: + partner_invoice_id = partner_info['id'] + partner_shipping_id = partner_info['id'] + print("""datetime.now().strftime('%Y-%m-%d %H:%M:%S') + """, datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + sale_hash = { + 'incoterm': 14, # hardcoded to "Delivery at Place" + 'date_order': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'client_order_ref': order_data['purchase_order_number'], + 'origin': False, + 'note': '', + 'user_id': user_id or 1, + 'team_id': 1, + 'warehouse_id': 9, + 'partner_id': partner_info['id'] or False, + # 'carrier_id': carrier_id, + 'pricelist_id': pricelist_id or 1, + 'project_id': 1, + 'currency_id': 1, + # 'payment_term': payment_term, + 'company_id': 1, + 'order_policy': 'picking', # hardcoded to "On Delivery", + 'picking_policy': 'direct', + 'partner_invoice_id': partner_invoice_id, + 'partner_shipping_id': partner_shipping_id, + 'edi_yes': True, + 'ack_yes': True, + 'ack_sent': False, + 'ship_to_code': partner_info['ship_to_code'], + 'trading_partner_id': partner_info['id'], + 'edi_est_so_ship_date': datetime.now().strftime('%Y-%m-%d'), + 'tset_purpose_code': order_data['tset_purpose_code'], + 'purchase_order_type_code': order_data['purchase_order_type_code'], + 'po_type_description': order_data['po_type_description'], + 'ship_complete_code': order_data['ship_complete_code'], + 'department': order_data['department'], + 'division': order_data['division'], + 'promotion_deal_number': order_data['promotion_deal_number'], + 'terms_type': order_data['terms_type'], + 'terms_basis_date_code': order_data['terms_basis_date_code'], + 'terms_discount_percentage': order_data['terms_discount_percentage'], + 'terms_discount_due_days': order_data['terms_discount_due_days'], + 'terms_net_due_days': order_data['terms_net_due_days'], + 'payment_method_code': order_data['payment_method_code'], + 'fob_pay_code': order_data['fob_pay_code'], + 'fob_location_qualifier': order_data['fob_location_qualifier'], + 'fob_location_description': order_data['fob_location_description'], + 'fob_title_passage_code': order_data['fob_title_passage_code'], + 'fob_title_passage_location': order_data['fob_title_passage_location'], + 'carrier_trans_method_code': order_data['carrier_trans_method_code'], + 'carrier_alpha_code': order_data['carrier_alpha_code'], + 'carrier_routing': order_data['carrier_routing'], + 'routing_sequence_code': order_data['routing_sequence_code'], + 'service_level_code': order_data['service_level_code'], + 'reference_qual': order_data['reference_qual'], + 'reference_id': order_data['reference_id'], + 'ref_description': order_data['ref_description'], + 'note_code': order_data['note_code'], + 'note_information_field': order_data['note_information_field'], + 'allow_chrg_indicator': order_data['allow_chrg_indicator'], + 'allow_chrg_code': order_data['allow_chrg_code'], + 'allow_chrg_agency_code': order_data['allow_chrg_agency_code'], + 'allow_chrg_agency': order_data['allow_chrg_agency'], + 'allow_chrg_amt': order_data['allow_chrg_amt'], + 'allow_chrg_percent_qual': order_data['allow_chrg_percent_qual'], + 'allow_chrg_percent': order_data['allow_chrg_percent'], + 'allow_chrg_handling_code': order_data['allow_chrg_handling_code'], + 'reference_identification': order_data['reference_identification'], + 'allow_chrg_handling_description': + order_data['allow_chrg_handling_description'], + } + + print("Attempting to create %s" % sale_hash) + sale_id = sock.execute(DBNAME, uid, PWD, 'sale.order', 'create', sale_hash) + print("sale_idddddddddddddddddddddddd", sale_id) + return sale_id + + +def search_uom(sock, uid, uom): + + uom_id = '' + + args = [('name', 'ilike', uom)] + ids = sock.execute(DBNAME, uid, PWD, 'product.uom', 'search', args) + try: + uom_id = ids[0] + except Exception: + pass + + return uom_id + + +def create_so_lines(sock, uid, sale_id, order_lines): + + sale_line_ids = [] + + for line in order_lines: + p_id = search_product(sock, uid, order_lines['sku']) + + order_line_hash = { + 'name': p_id.default_code, + 'edi_line_num': line['edi_line_num'], + 'product_uos_qty': int(line['product_qty']), + 'product_uom_qty': int(line['product_qty']), + 'edi_line_qty': int(line['product_qty']), + 'state': 'draft', + 'order_id': sale_id, # from sale order we just created + 'invoiced': False, + 'th_weight': 0, # lookup from product + 'product_id': p_id, + # passed in from PO + 'price_unit': line['purchase_price_basis'], + # lookup from product + 'purchase_price': line['purchase_price_basis'], + 'product_uom': line['product_uom'], # lookup from product + 'product_qty': int(line['product_qty']), + 'sku': line['sku'], + 'edi_yes': line['edi_yes'], + 'asn_shipment': line['asn_shipment'], + 'po_number': line['po_number'], + 'buyer_part_number': line['buyer_part_number'], + 'ack_yes': line['ack_yes'], + 'edi_est_del_date': line['edi_est_del_date'], + 'edi_est_ship_date': line['edi_est_ship_date'], + 'edi_line_msg': line['edi_line_msg'], + 'ship_not_before_date': line['ship_not_before_date'], + 'cancel_after_date': line['cancel_after_date'], + 'trading_partner_id': line['trading_partner_id'], + 'edi_intransit_qty': line['edi_intransit_qty'], + 'edi_outgoing_qty': line['edi_outgoing_qty'], + 'edi_avlforsale_qty': line['edi_avlforsale_qty'], + 'vendor_part_number': line['vendor_part_number'], + 'consumer_package_code': line['consumer_package_code'], + 'gtin': line['gtin'], + 'upc_case_code': line['upc_case_code'], + 'purchase_price_basis': line['purchase_price_basis'], + 'product_size_code': line['product_size_code'], + 'product_size_description': line['product_size_description'], + 'product_color_code': line['product_color_code'], + 'product_color_description': line['product_color_description'], + 'product_material_description': + line['product_material_description'], + 'department': line['department'], + 'classs': line['classs'], + 'price_type_id_code': line['price_type_id_code'], + 'item_description_type': line['item_description_type'], + 'product_characteristic_code': line['product_characteristic_code'], + 'agency_qualifier_code': line['agency_qualifier_code'], + 'product_description_code': line['product_description_code'], + 'pack_qualifier': line['pack_qualifier'], + 'pack_value': line['pack_value'], + 'pack_size': line['pack_size'], + 'pack_uom': line['pack_uom'], + 'packing_medium': line['packing_medium'], + 'packing_material': line['packing_material'], + 'pack_weight': line['pack_weight'], + 'pack_weight_uom': line['pack_weight_uom'], + 'location_code_qualifier': line['location_code_qualifier'], + 'location': line['location'], + 'allow_chrg_indicator': line['allow_chrg_indicator'], + 'allow_chrg_code': line['allow_chrg_code'], + 'allow_chrg_agency_code': line['allow_chrg_agency_code'], + 'allow_chrg_agency': line['allow_chrg_agency'], + 'allow_chrg_amt': line['allow_chrg_amt'], + 'allow_chrg_percent': line['allow_chrg_percent'], + 'percent_dollar_basis': line['percent_dollar_basis'], + 'allow_chrg_rate': line['allow_chrg_rate'], + 'allow_chrg_handling_code': line['allow_chrg_handling_code'], + 'allow_chrg_qty_uom': line['allow_chrg_qty_uom'], + } + try: + line_id = sock.execute( + DBNAME, uid, PWD, 'sale.order.line', 'create', order_line_hash) + sale_line_ids.append(line_id) + except BaseException: + pass + return sale_line_ids + + +def main(): + # connect to openerp + sock, uid = connect_oerp() + company_id = False + + if not company_id: + company_id = edi_850.get_current_company(sock, uid) + # read in incoming EDI, parse through and create dictionary with EDI info + files = os.listdir(IN_PATH) + # loop through files + for fle in files: + status, partner = parse_csv(sock, uid, IN_PATH + fle) + # get edi_config_id from company record + edi_id = edi_850.edi_config_id(sock, uid, company_id, partner) + # print 'EDI: ' + str(edi_id) + config = edi_850.get_config(sock, uid, edi_id) + # move processed file to archive folder + if not DEBUG and status: + if config['archive_path']: + shutil.move(IN_PATH + fle, config['archive_path'] + '/' + fle) + + +if __name__ == '__main__': + print('Process: EDI Orders Read - Starting') + main() + print('Process: EDI Orders Read - Ending') diff --git a/connector_spscommerce/edi_scripts/monitor.py b/connector_spscommerce/edi_scripts/monitor.py new file mode 100644 index 0000000..9bd3b2e --- /dev/null +++ b/connector_spscommerce/edi_scripts/monitor.py @@ -0,0 +1,149 @@ +#!/usr/bin/python +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import ftplib +import re +import time +import logging +import os +import shutil + +HOST = '' # IP Address +PORT = 22 +UNAME = '' +PW = '' + +SOURCE_DIR = '/out/' +DEST_DIR = '/in/' +REMOTE_ARCHIVE = '/Archive/' +LOCAL_DEST_DIR = '/var/log/_edi/Inbox/' +LOCAL_SOURCE_DIR = '/var/log/_edi/Outbox/' +ARCHIVE = '/var/log/_edi/Archive' + +log_path = '/var/log/_edi/Logs' + +count = 0 + +print("Current date & time " + time.strftime("%c")) + + +def ftp_connect(HOST, UNAME, PW): + + ftp = ftplib.FTP(HOST) + try: + ftp.login(UNAME, PW) + logging.info('Successfully connected to the remote site.') + + except Exception: + logging.error('Error logging into ftp server.') + + return ftp + + +def process_files(ftp, SOURCE_DIR, LOCAL_SOURCE_DIR, DEST_DIR, LOCAL_DEST_DIR): + in_data = [] + out_data = [] + in_file_list = [] + out_file_list = [] + + ftp.dir(SOURCE_DIR, in_data.append) + + for in_file in in_data: + + file = re.split('\s+', in_file) + filename = file[len(file) - 1] + filename.replace(" ", "") + + if filename not in ['.', '..', 'Archive']: + in_file_list.append(filename) + + out_data = os.listdir(LOCAL_SOURCE_DIR) + + for out_file in out_data: + + file = re.split('\s+', out_file) + filename = file[len(file) - 1] + filename.replace(" ", "") + + if filename not in ['.', '..', 'Archive']: + out_file_list.append(filename) + + logging.info('Found this list of incoming files: %s' % str(in_file_list)) + logging.info('Found this list of outgoing files: %s' % str(out_file_list)) + + # Create lists of each file type for outgoing and incoming files + incoming_files = [fle for fle in in_file_list] + outgoing_files = [fle for fle in out_file_list] + + get = transfer_files(ftp, incoming_files, SOURCE_DIR, + LOCAL_DEST_DIR, 'RETR') + + if get: + for filename in in_file_list: + + try: + ftp.rename(SOURCE_DIR + filename, REMOTE_ARCHIVE + filename) + logging.info('Successfully transfered %s' % + filename + 'from remote host') + print('''Successful Transfer of: + ''' + filename + ' from remote host') + except Exception: + logging.error( + 'Failed to archive %s on remote server' % filename) + + send = transfer_files(ftp, outgoing_files, + LOCAL_SOURCE_DIR, DEST_DIR, 'STOR') + + if send: + for filename in out_file_list: + + try: + shutil.move(LOCAL_SOURCE_DIR + filename, ARCHIVE) + logging.info('Successfully transfered %s' % + filename + 'to remote host') + print('Successful Transfer of:' + filename + ' to remote host') + except Exception: + logging.error( + 'Failed to archive %s on local server' % filename) + + ftp.quit() + + +def transfer_files(ftp, files, SOURCE_DIR, DEST_DIR, transfer_type): + + res = True + + for filename in files: + + try: + + if transfer_type == 'RETR': + outfile = open(DEST_DIR + filename, 'w') + ftp.retrlines('RETR' + ' ' + SOURCE_DIR + filename, + lambda s, w=outfile.write: w(s + '\n')) + outfile.close() + if transfer_type == 'STOR': + ftp.cwd(DEST_DIR) + ftp.storbinary("STOR " + filename, + open(SOURCE_DIR + filename, "rb")) + + except Exception: + + res = False + logging.error('Error transferring ' + filename + + ' using transfer type: ' + transfer_type) + + return res + + +def main(): + + ftp = ftp_connect(HOST, UNAME, PW) + process_files(ftp, SOURCE_DIR, LOCAL_SOURCE_DIR, DEST_DIR, LOCAL_DEST_DIR) + + +if __name__ == '__main__': + print('Starting') + main() + print('Sleeping') + time.sleep(5) diff --git a/connector_spscommerce/edi_scripts/test.py b/connector_spscommerce/edi_scripts/test.py new file mode 100644 index 0000000..f6be1b9 --- /dev/null +++ b/connector_spscommerce/edi_scripts/test.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import csv +import itertools +import xmlrpclib +import edi_850 +from connect_info import ERP_WWW, DBNAME, USERNAME, PWD, IN_PATH + + +def connect_oerp(): + ''' + Function that will make a connection to OpenERP using XML-RPC. + @return1: socket that is connected to OpenERP + @return2: An id that shows that you have been validated + ''' + sock = xmlrpclib.ServerProxy(ERP_WWW + '/xmlrpc/object') + sock_common = xmlrpclib.ServerProxy(ERP_WWW + '/xmlrpc/common') + uid = sock_common.login(DBNAME, USERNAME, PWD) + + print("XMLRPC Connection: SUCCESS - SERVER HAS AUTHENTICATED A LOGIN") + + return sock, uid + + # CSV COLUMNS + cols = [ + 'partner', + 'po_num', + 'date', 'ship_to_code', + 'sku', + 'upc', + 'product_desc', + 'product_qty', + 'product_uom', + 'price_unit', + 'third_party', + 'address1', + 'address2', + 'city', + 'state', + 'zip', + 'country', + 'billing_contact', + 'billing_address1', + 'billing_address2', + 'billing_city', + 'billing_state', + 'billing_zip', + 'billing_country', + 'ship_not_before', + 'cancel_after', + 'po_line' + ] + + csv_obj = open(file, 'rb') + data = csv.reader(csv_obj, delimiter='\t') + count = 1 + record = {} + record['sku'] = [] + record['upc'] = [] + record['po_line'] = [] + + for col in itertools.islice(cols, 6, 10): + record[col] = [] + + for row in data: + + if count == 1 and row[0] != 'H': + + print("""INFO: FAILURE - YOUR FILE DOES NOT HAVE THE REQUIRED + FIRST ROW WITH COLUMN HEADINGS: EDI Loop | Order line | Partner | + PO # | Date/UPC | code?/product desc | quantity | UOM + Price *****""") + break + + if count > 2 and row[0] == '': + # sale_id, sale_name = process_record(sock, uid, record) + break + + if row[0] == 'H': + + if record['sku']: + print(record) + edi_850.main(sock, uid, record, IN_PATH) + record['sku'] = [] + record['upc'] = [] + record['po_line'] = [] + + for col in itertools.islice(cols, 6, 10): + record[col] = [] + + record['ship_to_code'] = row[14] + record['ship_not_before'] = row[31] + record['cancel_after'] = row[32] + + # get partner, PO#, date + i = 2 + for col in itertools.islice(cols, 0, 3): + record[col] = row[i] + i += 1 + + # get thirdparty info + i = 7 + for col in itertools.islice(cols, 10, 17): + record[col] = row[i] + i += 1 + + # get billing address info + i = 15 + for col in itertools.islice(cols, 17, 24): + record[col] = row[i] + i += 1 + + elif row[0] == 'I': + + record['sku'].append(row[2]) + record['upc'].append(row[4]) + record['po_line'].append(row[1]) + # get sale line data: sku, asin, product description, quantity, + # uom, price + i = 5 + for col in itertools.islice(cols, 6, 10): + record[col].append(row[i]) + i += 1 + + count += 1 + + status = edi_850.main(sock, uid, record, IN_PATH) + + csv_obj.close() + + return status, record['partner'] + + +def main(): + sock, uid = connect_oerp() + o_id = 33382 + orders = sock.execute(DBNAME, uid, PWD, 'stock.picking', 'read', o_id) + print(orders) + + +if __name__ == '__main__': + print('Process: EDI Orders Read - Starting') + main() + print('Process: EDI Orders Read - Ending') diff --git a/connector_spscommerce/models/__init__.py b/connector_spscommerce/models/__init__.py new file mode 100644 index 0000000..58be573 --- /dev/null +++ b/connector_spscommerce/models/__init__.py @@ -0,0 +1,11 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import edi_config +from . import res_company +from . import res_partner +from . import sale +from . import purchase +from . import stock +from . import stock_move +from . import account_invoice +from . import stock_picking diff --git a/connector_spscommerce/models/account_invoice.py b/connector_spscommerce/models/account_invoice.py new file mode 100644 index 0000000..d0e25b8 --- /dev/null +++ b/connector_spscommerce/models/account_invoice.py @@ -0,0 +1,671 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import re +import dicttoxml +from odoo import api, fields, models +from datetime import datetime +from odoo.addons import decimal_precision as dp +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DS +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DSD +from odoo.exceptions import UserError + + +class AccountInvoiceLine(models.Model): + _inherit = "account.invoice.line" + + asn_shipment = fields.Char('ASN Shipment Number from Converted 856') + po_number = fields.Char('Line Item PO Number from Converted 856') + buyer_part_number = fields.Char('Buyer Part Number') + edi_line_qty = fields.Float( + 'Original EDI Quantity', + digits=dp.get_precision('Product Unit of Measure') + ) + vendor_part_number = fields.Char('Vendor Part Number') + consumer_package_code = fields.Char('Consumer Package Code') + gtin = fields.Char('GTIN') + upc_case_code = fields.Char('UPC Case Code') + purchase_price_basis = fields.Char('Purchase PriceBasis') + product_size_code = fields.Char('Product Size Code') + product_size_description = fields.Char('Product Size Description') + product_color_code = fields.Char('Product Color Code') + product_color_description = fields.Char('Product Color Description') + product_material_code = fields.Char('Product Material Code') + product_material_description = fields.Char('Product Material Description') + department = fields.Char('Department') + classs = fields.Char('Class') + price_type_id_code = fields.Char('PriceType ID Code') + edi_line_num = fields.Integer('EDI PO line number') + multiple_price_quantity = fields.Float('Multiple PriceQuantity') + class_of_trade_code = fields.Char('Class Of Trade Code') + item_description_type = fields.Char('Item Description Type') + product_characteristic_code = fields.Char('Product Characteristic Code') + agency_qualifier_code = fields.Char('Agency Qualifier Code') + product_description_code = fields.Char('Product Description Code') + pack_qualifier = fields.Char('Pack Qualifier') + pack_value = fields.Integer('Pack Value') + pack_size = fields.Char('Pack Size') + pack_uom = fields.Char('Pack UOM') + packing_medium = fields.Char('Packing Medium') + packing_material = fields.Char('Packing Material') + pack_weight = fields.Float('Pack Weight') + pack_weight_uom = fields.Char('Pack Weight UOM') + location_code_qualifier = fields.Char( + 'Location Code Qualifier', + help="Code identifying the structure or format of the related" + "location number(s)." + ) + location = fields.Char( + 'Location', + help="For CrossDock, it's the marked for location. For MultiStore" + "[could also be DC] ship-to location." + ) + allow_chrg_indicator = fields.Char( + 'Allow Chrg Indicator', + help="Code which indicates an allowance or charge for the" + "service specified." + ) + allow_chrg_code = fields.Char( + 'Allow Chrg Code', + help="Code describing the type of allowance or charge for the" + "service specified." + ) + allow_chrg_agency_code = fields.Char( + 'Allow Chrg Agency Code', + help="Code identifying the agency assigning the code values." + ) + allow_chrg_agency = fields.Char( + 'Allow Chrg Agency', + help="Agency maintained code identifying the service, promotion," + "allowance, or charge." + ) + allow_chrg_amt = fields.Float( + 'Allow Chrg Amt', + help="Amount of the allowance or charge." + ) + allow_chrg_percent = fields.Float( + 'Allow Chrg Percent', + help='''Percentage of allowance or charge. Percentages should be + represented as real numbers[0% through 100% should be normalized to + 0.0 through 100.00]..''' + ) + percent_dollar_basis = fields.Float( + 'Percent Dollar Basis', + ) + allow_chrg_rate = fields.Float( + 'Allow Chrg Rate', + help="Amount of the allowance or charge." + ) + allow_chrg_handling_code = fields.Char( + 'Allow Chrg Handling Code', + help="Code indicating method of handling for an allowance or charge.." + ) + allow_chrg_qty_uom = fields.Char('Allow Chrg Qty UOM') + allow_chrg_handling_description = fields.Char( + 'Allow Chrg Handling Description', + help="Free-form textual description of the note." + ) + + +class AccountInvoice(models.Model): + _inherit = "account.invoice" + + asn_shipment = fields.Char('ASN Shipment Number from Converted 856') + client_order_ref = fields.Text( + 'Customer PO #', + help="Customer PO #" + ) + ship_to_code = fields.Char( + 'Ship To Warehouse', + help="Trading Partner Ship to location code." + ) + supplier_code = fields.Char( + 'Supplier Code', + help="This is the date from the 856." + ) + edi_yes = fields.Boolean( + 'From an EDI PO?', + # readonly=True, + help="Is this order from an EDI purchase order, 850 EDI doc." + ) + sent_timestamp = fields.Datetime( + '810 Sent Date', + help="The timestamp for when the 856 was sent." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + invoice_check = fields.Boolean( + '810 EDI Invoice Sent?', + readonly=True, + help="Is checked if EDI 810 invoice is sent." + ) + ship_not_before_date = fields.Date( + 'Estimated Shipping Date', + help="This is the date from the 856." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + sale_id = fields.Many2one( + 'sale.order', + 'Sale Order', + help='Sale Order from Whence this Invoice Was created' + ) + picking_id = fields.Many2one( + 'stock.picking', + 'Picking ID', + help='Stock Picking ID whence this invoice was created' + ) + scac_code = fields.Char( + 'SCAC Code', + help="This is the shipping alpha code from your carrier." + ) + bol_num = fields.Char( + 'BoL Number', + help="This is bill of lading number from your carrier/shipper." + ) + tracking_num = fields.Char( + 'Supplier Code', + help="This is the tracking number from your carrier." + ) + sender_id = fields.Char('EDI Sender Code') + tset_purpose_code = fields.Char( + 'TsetPurposeCode', + help="Code identifying purpose of the document.." + ) + purchase_order_type_code = fields.Char( + 'Purchase Order Type Code', + help="Code specifying the type of purchase order." + ) + po_type_description = fields.Char( + 'PO Type Description', + help="Free form text to describe the type of order." + ) + ship_complete_code = fields.Char( + 'Ship Complete Code', + help='''Code to identify a specific requirement or agreement of sale. + Should only be used to indicate if an item can be + placed on backorder.''' + ) + department = fields.Char( + 'Department', + help="Name or number identifying an area wherein merchandise is" + "categorized within a store." + ) + division = fields.Char( + 'Division', + help="Different entities belonging to the same parent company." + ) + promotion_deal_number = fields.Char( + 'Promotion Deal Number', + help="Number uniquely identifying an agreement for a" + "special offer or price." + ) + carrier_pro_number = fields.Char('Carrier Pro Number') + bill_of_lading_number = fields.Char('Bill Of Lading Number') + terms_type = fields.Char( + 'Terms Type', + help="Code identifying type of payment terms." + ) + terms_basis_date_code = fields.Char( + 'Terms Basis Date Code', + help="Code identifying the beginning of the terms period." + ) + terms_discount_percentage = fields.Char( + 'Terms Discount Percentage', + help="Terms discount percentage available to the purchaser" + ) + terms_discount_due_days = fields.Char( + 'Terms Discount Due Days', + help="Number of days by which payment or invoice must be received in" + "order to receive the discount noted." + ) + terms_net_due_days = fields.Char( + 'Terms Net Due Days', + help="Number of days until total invoice amount is due[discount" + "not applicable." + ) + payment_method_code = fields.Char( + 'Payment Method Code', + help="Indication of the instrument of payment." + ) + fob_pay_code = fields.Char( + 'FOB Pay Code', + help="Code identifying payment terms for transportation Charges." + ) + fob_location_qualifier = fields.Char( + 'FOB Location Qualifier', + help="Code identifying type of location at which ownership of" + "goods is transferred." + ) + fob_location_description = fields.Char( + 'FOB Location Description', + help="Free-form textual description of the location at which" + "ownership of goods is transferred." + ) + fob_title_passage_code = fields.Char( + 'FOB Title Passage Code', + help="Code describing the location of ownership of the goods." + ) + fob_title_passage_location = fields.Char( + 'FOB Title Passage Location', + help="Location of ownership of the goods." + ) + carrier_trans_method_code = fields.Char( + 'Carrier Trans Method Code', + help="Code specifying the method or type of transportation" + "for the shipment." + ) + carrier_alpha_code = fields.Char( + 'CarrierAlphaCode', + help="Standard Carrier Alpha Code[SCAC] - " + ) + carrier_routing = fields.Char( + 'CarrierRouting', + help="Free-form description of the routing/requested routing for" + "shipment or the originating carrier's identity." + ) + routing_sequence_code = fields.Char('Routing Sequence Code') + service_level_code = fields.Char( + 'Service Level Code', + help="Code indicating the level of transportation service or the" + "billing service offered by the transportation carrier." + ) + reference_qual = fields.Char( + 'Reference Qual', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + reference_id = fields.Char( + 'Reference ID', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + ref_description = fields.Char( + 'Description', + help="Free-form textual description to clarify the related data" + "elements and their content." + ) + note_code = fields.Char( + 'NoteCode', + help="Code specifying the type of note." + ) + note_information_field = fields.Char( + 'Note Information Field', + help="Free-form textual description of the note." + ) + allow_chrg_indicator = fields.Char( + 'Allow Chrg Indicator', + help="Code which indicates an allowance or Charge for the" + "service specified." + ) + allow_chrg_code = fields.Char( + 'Allow Chrg Code', + help="Code describing the type of allowance or Charge for" + "the service specified." + ) + allow_chrg_agency_code = fields.Char( + 'Allow Chrg Agency Code', + help="Code identifying the agency assigning the code values." + ) + allow_chrg_agency = fields.Char( + 'Allow Chrg Agency', + help="Agency maintained code identifying the service, promotion," + "allowance, or charge." + ) + allow_chrg_amt = fields.Float( + 'Allow Chrg Amt', + help="Amount of the allowance or Charge." + ) + allow_chrg_percent_qual = fields.Char( + 'Allow Chrg Percent Qual', + help="Code indicating on what basis an allowance or charge" + "percent is calculated.." + ) + allow_chrg_percent = fields.Float( + 'Allow Chrg Percent', + help='''Percentage of allowance or charge. Percentages should be + represented as real numbers[0% through 100% should be + normalized to 0.0 through 100.00]..''' + ) + allow_chrg_handling_code = fields.Char( + 'Allow Chrg Handling Code', + help="Code indicating method of handling for an allowance or charge.." + ) + reference_identification = fields.Char('Reference Identification') + allow_chrg_handling_description = fields.Char( + 'Allow Chrg Handling Description', + help="Free-form textual description of the note." + ) + + @api.multi + def create_text_810(self): + + today = str(datetime.now().strftime('%Y%m%d')) or '' + processed = 0 + num = '' + + # for each invoice + invoices_list = {'Invoices': []} + for invoice in self: + num += invoice.number or '1' + + # skip non customer invoice records +# if invoice.type not in ('out_invoice' +# ) or invoice.state in ('draft', 'cancel', 'paid'): +# continue + + # Grab the vendor_id and trading_partner_id for EDI + # in the edi_config. + trading_partner_code = \ + invoice.trading_partner_id.partner_header_string or 'Test' + vendor_code =\ + invoice.trading_partner_id.vendor_header_string or 'Test' + + # date_format = "%Y-%m-%d" + # invoice date + inv_date = invoice.date_invoice + dateinv_object = datetime.strptime(inv_date, DS) + inv_date = dateinv_object.date() + inv_date = str(inv_date) + + # purchase date + po_date = \ + invoice.purchase_id and invoice.purchase_id.date_order or '' + if po_date: + datepo_object = datetime.strptime(po_date, DS) + po_date = datepo_object.date() + po_date = str(po_date) + + # ship date + ship_date = \ + invoice.picking_id and invoice.picking_id.date_done or '' + if ship_date: + ship_object = datetime.strptime(ship_date, DS) + ship_date = ship_object.date() + ship_date = str(ship_date) + + # initialize + invoice_dict = {'Invoice': {}} + # 1. Header Line - one line for the order + meta_dict = {'Meta': {'Version': '1.0'}} + header_dict = {'Header': {'InvoiceHeader': { + 'TradingPartnerId': trading_partner_code, + 'InvoiceNumber': str(invoice.number), + 'InvoiceDate': inv_date, + 'PurchaseOrderDate': po_date, + 'PurchaseOrderNumber': + invoice.purchase_id and invoice.purchase_id.number or '', + 'ReleaseNumber': str(invoice.number), + 'InvoiceTypeCode': 'U5', + 'BuyersCurrency': + invoice.currency_id and invoice.currency_id.name or '', + 'Department': invoice.department or '', + 'Vendor': vendor_code, + 'PromotionDealNumber': invoice.promotion_deal_number or '', + 'CarrierProNumber': invoice.carrier_pro_number or '', + 'BillOfLadingNumber': invoice.bill_of_lading_number, + 'ShipDate': ship_date, + 'CustomerOrderNumber': + invoice.sale_id and invoice.sale_id.name or '', + }, + 'PaymentTerms': { + 'TermsType': invoice.terms_type or '', + 'TermsBasisDateCode': invoice.terms_basis_date_code or '', + 'TermsDiscountPercentage': + invoice.terms_discount_percentage or '', + 'TermsDiscountDate': inv_date, + 'TermsDiscountDueDays': + invoice.terms_discount_due_days or '', + 'TermsNetDueDate': inv_date, + 'TermsNetDueDays': invoice.terms_net_due_days or '', + 'TermsDiscountAmount': 0, + 'TermsDescription': invoice.payment_term_id.note or '', + }, + 'Date': { + 'DateTimeQualifier1': '196', + 'Date1': inv_date, + 'Time1': '', + }, + 'Contact': { + 'ContactTypeCode': 'BD', + 'ContactName': + invoice.picking_id and invoice.picking_id.partner_id and + invoice.picking_id.partner_id.name or '', + 'PrimaryPhone': + invoice.picking_id and invoice.picking_id.partner_id and + invoice.picking_id.partner_id.phone or '', + 'PrimaryFax': + invoice.picking_id and invoice.picking_id.partner_id and + invoice.picking_id.partner_id.fax or '', + 'PrimaryEmail': + invoice.picking_id and invoice.picking_id.partner_id and + invoice.picking_id.partner_id.email or '', + }, + 'Address': { + 'AddressTypeCode': 'Z7', + 'LocationCodeQualifier': '92', + 'AddressLocationNumber': '11111', + 'AddressName': invoice.partner_id.name, + 'Address1': invoice.partner_id.street, + 'Address2': invoice.partner_id.street2, + 'City': invoice.partner_id.city, + 'State': invoice.partner_id.state_id.name, + 'PostalCode': invoice.partner_id.zip, + 'Country': invoice.partner_id.country_id.name, + 'Contact': { + 'ContactTypeCode': 'BD', + 'ContactName': invoice.picking_id and + invoice.picking_id.partner_id and + invoice.partner_id.name or '', + 'PrimaryPhone': invoice.picking_id and + invoice.picking_id.partner_id and + invoice.partner_id.phone or '', + 'PrimaryFax': invoice.picking_id and + invoice.picking_id.partner_id and + invoice.partner_id.fax or '', + 'PrimaryEmail': invoice.picking_id and + invoice.picking_id.partner_id and invoice.email or '', + }, + }, + 'Reference': { + 'ReferenceQual': invoice.reference_qual or '', + 'ReferenceID': invoice.reference_id or '', + 'Description': invoice.ref_description or '', + }, + 'Notes': { + 'NoteCode': invoice.note_code or '', + 'NoteInformationField': + invoice.note_information_field or '', + }, + 'Tax': { + 'TaxTypeCode': 'S', + 'TaxAmount': '1845.08', + 'TaxPercent': '8.50', + 'JurisdictionQual': 'CC', + 'JurisdictionCode': '07034', + 'TaxExemptCode': '2', + 'TaxID': '99990000', + }, + 'ChargesAllowances': { + 'AllowChrgIndicator': invoice.allow_chrg_indicator or '', + 'AllowChrgCode': invoice.allow_chrg_code or '', + 'AllowChrgAmt': invoice.allow_chrg_amt or 0, + 'AllowChrgPercentQual': + invoice.allow_chrg_percent_qual or '', + 'AllowChrgPercent': invoice.allow_chrg_percent or 0, + 'AllowChrgHandlingCode': + invoice.allow_chrg_handling_code or '', + 'AllowChrgHandlingDescription': + invoice.allow_chrg_handling_description or '', + }, + 'FOBRelatedInstruction': { + 'FOBPayCode': invoice.fob_pay_code or '', + 'FOBLocationQualifier': + invoice.fob_location_qualifier or '', + 'FOBLocationDescription': + invoice.fob_location_description or '', + 'FOBTitlePassageCode': + invoice.fob_title_passage_code or '', + 'FOBTitlePassageLocation': + invoice.fob_title_passage_location or '', + }, + 'CarrierInformation': { + 'CarrierTransMethodCode': + invoice.carrier_trans_method_code or '', + 'CarrierAlphaCode': invoice.carrier_alpha_code or '', + 'CarrierRouting': invoice.carrier_routing or '', + 'CarrierEquipmentNumber': + invoice.routing_sequence_code or '', + }, + 'ServiceLevelCodes': { + 'ServiceLevelCode': invoice.service_level_code or '', + }, + } + } + invoice_dict.get('Invoice').update(meta_dict) + invoice_dict.get('Invoice').update(header_dict) + # LineItems + lineitems_list = {'LineItems': []} + total_lines = 0 + total_qty = 0 + total_weight = 0 + # for each item line + for line in invoice.invoice_line_ids: + total_lines += 1 + total_qty += line.quantity + total_weight += (line.product_id.weight * line.quantity) + lineitem_dict = {'LineItem': {}} + invoiceline_dict = {'InvoiceLine': { + 'LineSequenceNumber': int(line.id), + 'BuyerPartNumber': line.buyer_part_number or '', + 'VendorPartNumber': line.product_id.default_code or '', + 'ConsumerPackageCode': '093597609541', + 'EAN': line.product_id.barcode, + 'GTIN': line.gtin or '', + 'UPCCaseCode': line.upc_case_code or '', + 'NatlDrugCode': '51456-299', + 'InternationalStandardBookNumber': '999-0-555-22222-0', + 'ProductID': { + 'PartNumberQual': 'IS', + 'PartNumber': line.product_id.default_code, + }, + 'PurchasePrice': line.price_unit, + 'ShipQty': line.quantity, + 'ShipQtyUOM': 'P4', + 'ProductSizeCode': line.product_size_code or '', + 'ProductSizeDescription': + line.product_size_description or '', + 'ProductColorCode': line.product_color_code or '', + 'ProductColorDescription': + line.product_color_description or '', + 'ProductMaterialDescription': + line.product_material_description or '', + 'NRFStandardColorAndSize': { + 'NRFColorCode': '600', + 'NRFSizeCode': '42-10651', + } + }, + 'ProductOrItemDescription': { + 'ItemDescriptionType': + line.item_description_type or '', + 'AgencyQualifierCode': + line.agency_qualifier_code or '', + 'ProductDescriptionCode': + line.product_id.default_code, + 'ProductDescription': line.name, + }, + 'PhysicalDetails': { + 'PackQualifier': line.pack_qualifier or '', + 'PackValue': line.pack_value or 0, + 'PackSize': line.pack_size or '', + 'PackUOM': line.pack_uom or '', + }, + 'Tax': { + 'TaxTypeCode': 'S', + 'TaxAmount': '1845.08', + 'TaxPercent': '8.50', + 'JurisdictionQual': 'CC', + 'JurisdictionCode': '07034', + 'TaxExemptCode': '2', + 'TaxID': '99990000', + }, + 'ChargesAllowances': { + 'AllowChrgIndicator': line.allow_chrg_indicator or '', + 'AllowChrgCode': line.allow_chrg_code or '', + 'AllowChrgAmt': line.allow_chrg_amt or 0, + 'AllowChrgPercentQual': '', + 'AllowChrgPercent': line.allow_chrg_percent or 0, + 'AllowChrgHandlingCode': + line.allow_chrg_handling_code or '', + 'AllowChrgHandlingDescription': + line.allow_chrg_handling_description or '', + }, + } + lineitem_dict.get('LineItem').update(invoiceline_dict) + lineitems_list.get('LineItems').append(lineitem_dict) + invoice_dict.get('Invoice').update(lineitems_list) + + # Summary + summary_dict = {'Summary': { + 'TotalAmount': invoice.amount_total or '', + 'TotalNetSalesAmount': invoice.amount_untaxed, + 'TotalTermsDiscountAmount': 0, + 'TotalQtyInvoiced': total_qty, + 'TotalWeight': total_weight, + 'TotalLineItemNumber': total_lines, + 'InvoiceAmtDueByTermsDate': 0, + 'TotalQtyInvoicedUOM': 'P3', + 'TotalWeightUOM': 'HD', + } + } + invoice_dict.get('Invoice').update(summary_dict) + # update invoice + today = datetime.now().strftime(DSD) + invoice.write({'sent_timestamp': today}) + invoices_list.get('Invoices').append(invoice_dict) + processed += 1 + + # Convert dictionary to xml + xml = dicttoxml.dicttoxml(invoices_list, attr_type=False, root=False) + xml = xml.decode("utf-8") + xml = xml.replace('', '').replace('', '') + xml = xml.replace('', '').\ + replace('', + '' + + '') + print("***** SUCCESSFULLY STORED *****") + # Write ASN doc to text file + num = re.findall('\d+', num) + filename = '810_' + today + '%s.xml' % num + filename.replace('/', '_') + if not invoice.trading_partner_id.out_path: + raise UserError('Add out path in Trading Partner') + fd = open(invoice.trading_partner_id.out_path + filename, 'w') + fd.write(xml) + fd.close() + return processed + + @api.model + def _create_810_wrapper(self): + # search for invoices that are edi_yes = True + # and sent_timestamp = False. + eligible_invoices = self.search([('edi_yes', '=', True), + ('sent_timestamp', '=', False)]) + return eligible_invoices and\ + eligible_invoices.create_text_810() or False + + # done as a server action + @api.multi + def action_create_text_810(self): + """ Creates a new 810 and puts it into the outbox + """ + # number of orders to process + toprocess = len(self.ids) + # process orders to write 810 + processed = self.create_text_810() + return (toprocess - processed) diff --git a/connector_spscommerce/models/edi_config.py b/connector_spscommerce/models/edi_config.py new file mode 100644 index 0000000..de7c484 --- /dev/null +++ b/connector_spscommerce/models/edi_config.py @@ -0,0 +1,56 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class EdiConfig(models.Model): + _name = "edi.config" + _description = "EDI Configuration Systems" + + edi_company_id = fields.Many2one( + 'res.company', + string='EDI Company', + help="The Main EDI Company.", + ondelete='cascade' + ) + route_id = fields.Many2one( + 'stock.location.route', + string='Stock Transfer Route', + help="""Select Dropshipping if applicable or another route. This will + be the route used in sale order lines.""" + ) + vendor_header_string = fields.Char('EDI Vendor ID') + partner_header_string = fields.Char('Partner Header ID') + billto_header_string = fields.Char('Bill to Header ID') + salesperson = fields.Many2one( + 'res.partner', + string='Sales Person', + help="The Salesperson for EDI Trading Partner.", + ondelete='cascade' + ) + in_path = fields.Char('EDI In Path') + out_path = fields.Char('EDI Out Path') + log_path = fields.Char('EDI Logs Path') + archive_path = fields.Char('EDI Archive Path') + trading_partner_id = fields.Many2one( + 'res.partner', + string='Trading Partner', + help="The trading partner for EDI.", + ondelete='cascade' + ) + is_thirdparty = fields.Boolean('Ship Directly to Customer') + is_sku = fields.Boolean('Use SKU instead of UPC') + auto_workflow = fields.Many2one( + 'sale.workflow.process', + string='Automatic Workflow', + ondelete='restrict' + ) + ack_855 = fields.Boolean('EDI 855 Ack') + ack_997 = fields.Boolean('EDI 997 Ack') + + @api.multi + def name_get(self): + res = [] + for record in self: + res.append((record['id'], record.trading_partner_id.name)) + return res diff --git a/connector_spscommerce/models/procurement.py b/connector_spscommerce/models/procurement.py new file mode 100644 index 0000000..999460f --- /dev/null +++ b/connector_spscommerce/models/procurement.py @@ -0,0 +1,67 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProcurementRule(models.Model): + _inherit = 'procurement.rule' + + po_number = fields.Char('Line Item PO Number from Converted 856') + edi_line_num = fields.Integer('EDI PO Line #') + asn_shipment = fields.Char('ASN Shipment Number') + ship_to_code = fields.Char( + 'Ship To Warehouse', + help="Trading Partner Ship to location code." + ) + sale_line_id = fields.Many2one( + 'sale.order.line', + 'Sale Order Line', + help='Sale Order Line from whence this Stock Move Was created' + ) + so_id = fields.Many2one( + 'sale.order', + 'Sale Order', + help='Sale Order from Whence this Invoice was created' + ) + edi_yes = fields.Boolean( + 'From an EDI PO?', + readonly=True, + help="Is this order from an EDI purchase order, 850 EDI doc." + ) + est_del_date = fields.Date( + 'Estimated Delivery Date', + help="Calculated based on shipping method." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + ship_not_before_date = fields.Date( + 'Do Not Ship Before This Date', + help="Do Not Ship Before This Date." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + + def _get_stock_move_values(self, product_id, product_qty, product_uom, + location_id, name, origin, values, group_id): + res = super(ProcurementRule, self).\ + _get_stock_move_values(product_id, product_qty, product_uom, + location_id, name, origin, values, group_id) + res['po_number'] = self.po_number or '' + res['ship_to_code'] = self.ship_to_code or '' + res['asn_shipment'] = self.asn_shipment or '' + res['sale_line_id'] = self.sale_line_id.id or False + res['edi_line_num'] = self.edi_line_num or 0 + res['so_id'] = self.so_id.id or False + res['trading_partner_id'] = self.trading_partner_id and\ + self.trading_partner_id.id or False + res['edi_yes'] = self.trading_partner_id and self.edi_yes or False + res['ship_not_before_date'] = self.trading_partner_id and\ + self.ship_not_before_date or False + res['cancel_after_date'] = self.trading_partner_id and\ + self.cancel_after_date or False + return res diff --git a/connector_spscommerce/models/purchase.py b/connector_spscommerce/models/purchase.py new file mode 100644 index 0000000..30c4c27 --- /dev/null +++ b/connector_spscommerce/models/purchase.py @@ -0,0 +1,14 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class PurchaseOrderLine(models.Model): + _inherit = "purchase.order.line" + + asn_shipment = fields.Char('ASN Shipment Number from Converted 856') + po_number = fields.Char('Line Item PO Number from Converted 856') + edi_yes = fields.Boolean( + 'From an EDI PO?', + help="Is this order from an EDI purchase order, 850 EDI doc." + ) diff --git a/connector_spscommerce/models/res_company.py b/connector_spscommerce/models/res_company.py new file mode 100644 index 0000000..7a02159 --- /dev/null +++ b/connector_spscommerce/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + header_string = fields.Char('Company Header String') + trading_partner_id = fields.Many2many( + 'edi.config', + string='EDI Trading Partner', + ondelete='cascade' + ) diff --git a/connector_spscommerce/models/res_partner.py b/connector_spscommerce/models/res_partner.py new file mode 100644 index 0000000..cbfa1be --- /dev/null +++ b/connector_spscommerce/models/res_partner.py @@ -0,0 +1,21 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + trading_partner_res = fields.One2many( + 'edi.config', + 'trading_partner_id', + required=False, + ondelete='cascade', + ) + edi_ids = fields.One2many( + 'edi.config', + 'edi_company_id', + 'EDI Company ID' + ) + ship_to_code = fields.Char('Ship to Code') + sender_id = fields.Char('EDI Sender Code') diff --git a/connector_spscommerce/models/sale.py b/connector_spscommerce/models/sale.py new file mode 100644 index 0000000..c5d5c24 --- /dev/null +++ b/connector_spscommerce/models/sale.py @@ -0,0 +1,1066 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re +from datetime import datetime, timedelta +from random import randint +from odoo import api, fields, models +from odoo.addons import decimal_precision as dp +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DS +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + asn_shipment = fields.Char('ASN Number from 856') + edi_error = fields.Text( + 'EDI Errors', + help="Text that will describe failures to add sale order lines" + "because of failing product lookups." + ) + edi_yes = fields.Boolean( + 'From an EDI PO?', + help="Is this order from an EDI purchase order, 850 EDI doc." + ) + ack_yes = fields.Boolean( + '855', + help="Will this order have an 855?" + ) + replace = fields.Boolean( + 'Send 855', + readonly=True, + help="Shall we send an 855 replacement?" + ) + ship_to_code = fields.Char( + 'Ship To Warehouse', + help="Trading Partner Ship to location code." + ) + supplier_code = fields.Char( + 'Supplier Code', + help="Supplier code from the 856." + ) + ship_not_before_date = fields.Date( + 'Estimated Shipping Date', + help="This is the date from the 856." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + sent_timestamp = fields.Datetime( + '855 Sent Date', + help="The timestamp for when the 855 was sent." + ) + check = fields.Boolean( + '855 Sent Already', + help="A check to see if 855 has been sent already." + ) + scac_code = fields.Char( + 'SCAC Code', + help="This is the shipping alpha code from your carrier." + ) + bol_num = fields.Char( + 'BoL Number', + help="This is bill of lading number from your carrier/shipper." + ) + tracking_num = fields.Char( + 'Tracking Number', + help="This is the tracking number from your carrier." + ) + tset_purpose_code = fields.Char( + 'Tset Purpose Code', + help="Code identifying purpose of the document.." + ) + purchase_order_type_code = fields.Char( + 'Purchase Order Type Code', + help="Code specifying the type of purchase order." + ) + po_type_description = fields.Char( + 'PO Type Description', + help="Free form text to describe the type of order." + ) + ship_complete_code = fields.Char( + 'Ship Complete Code', + help='''Code to identify a specific requirement or agreement of sale. + Should only be used to indicate if an item can be + placed on backorder.''' + ) + department = fields.Char( + 'Department', + help="Name or number identifying an area wherein merchandise is" + "categorized within a store." + ) + division = fields.Char( + 'Division', + help="Different entities belonging to the same parent company." + ) + promotion_deal_number = fields.Char( + 'Promotion Deal Number', + help="Number uniquely identifying an agreement for a special" + "offer or price." + ) + terms_type = fields.Char( + 'Terms Type', + help="Code identifying type of payment terms." + ) + terms_basis_date_code = fields.Char( + 'Terms Basis Date Code', + help="Code identifying the beginning of the terms period." + ) + terms_discount_percentage = fields.Char( + 'Terms Discount Percentage', + help="Terms discount percentage available to the purchaser" + ) + terms_discount_due_days = fields.Char( + 'Terms Discount Due Days', + help="Number of days by which payment or invoice must be received in" + "order to receive the discount noted." + ) + terms_net_due_days = fields.Char( + 'Terms Net Due Days', + help="Number of days until total invoice amount is" + "due[discount not applicable." + ) + payment_method_code = fields.Char( + 'Payment Method Code', + help="Indication of the instrument of payment." + ) + fob_pay_code = fields.Char( + 'FOB Pay Code', + help="Code identifying payment terms for transportation Charges." + ) + fob_location_qualifier = fields.Char( + 'FOB Location Qualifier', + help="Code identifying type of location at which ownership of goods" + "is transferred." + ) + fob_location_description = fields.Char( + 'FOB Location Description', + help="Free-form textual description of the location at which" + "ownership of goods is transferred." + ) + fob_title_passage_code = fields.Char( + 'FOB Title Passage Code', + help="Code describing the location of ownership of the goods." + ) + fob_title_passage_location = fields.Char( + 'FOB Title Passage Location', + help="Location of ownership of the goods." + ) + carrier_trans_method_code = fields.Char( + 'Carrier Trans Method Code', + help="Code specifying the method or type of transportation for" + "the shipment." + ) + carrier_alpha_code = fields.Char( + 'Carrier Alpha Code', + help="Standard Carrier Alpha Code[SCAC] - " + ) + carrier_routing = fields.Char( + 'Carrier Routing', + help="Free-form description of the routing/requested routing for" + "shipment or the originating carrier's identity." + ) + routing_sequence_code = fields.Char('Routing Sequence Code') + service_level_code = fields.Char( + 'Service Level Code', + help="Code indicating the level of transportation service or the" + "billing service offered by the transportation carrier." + ) + reference_qual = fields.Char( + 'Reference Qual', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + reference_id = fields.Char( + 'Reference ID', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + ref_description = fields.Char( + 'Description', + help="Free-form textual description to clarify the related" + "data elements and their content." + ) + note_code = fields.Char( + 'Note Code', + help="Code specifying the type of note." + ) + note_information_field = fields.Char( + 'Note Information Field', + help="Free-form textual description of the note." + ) + allow_chrg_indicator = fields.Char( + 'Allow Chrg Indicator', + help="Code which indicates an allowance or charge for" + "the service specified." + ) + allow_chrg_code = fields.Char( + 'Allow Chrg Code', + help="Code describing the type of allowance or charge" + "for the service specified." + ) + allow_chrg_agency_code = fields.Char( + 'Allow Chrg Agency Code', + help="Code identifying the agency assigning the code values." + ) + allow_chrg_agency = fields.Char( + 'Allow Chrg Agency', + help="Agency maintained code identifying the service," + "promotion, allowance, or charge." + ) + allow_chrg_amt = fields.Float( + 'Allow Chrg Amt', + help="Amount of the allowance or charge." + ) + allow_chrg_percent_qual = fields.Char( + 'Allow Chrg Percent Qual', + help="Code indicating on what basis an allowance or charge" + "percent is calculated.." + ) + allow_chrg_percent = fields.Float( + 'Allow Chrg Percent', + help='''Percentage of allowance or charge. Percentages should be + represented as real numbers[0% through 100% should be normalized + to 0.0 through 100.00]..''' + ) + allow_chrg_handling_code = fields.Char( + 'Allow Chrg Handling Code', + help="Code indicating method of handling for an allowance or charge.." + ) + reference_identification = fields.Char('ReferenceIdentification') + allow_chrg_handling_description = fields.Char( + 'Allow Chrg Handling Description', + help="Free-form textual description of the note." + ) + + @api.multi + def create_855(self): + xml_output = ''' + + testVersion
''' + for sale_id in self: + + # initialize + # csvwriter = None + # sale_obj = self.browse(cr, uid, sale_id, context=context) + config_obj = sale_id.trading_partner_id + OUT_PATH = config_obj.out_path + sale_lines = sale_id.order_line + sale_name = sale_id.name + num = re.findall('\d+', sale_name)[0] + po_num = '' + if sale_id.client_order_ref: + po_num = sale_id.client_order_ref + + # create and format dates for ACK + now = datetime.now() + today = now.strftime('%Y%m%d') + # time = datetime.now().strftime('%H%M%S') + po_date = datetime.strptime(sale_id.date_order, DS) + po_date = po_date.strftime('%m/%d/%Y') + + # generate random numbers for the 3 header id numbers in 855 + ISA_num = randint(100000000, 999999999) + GS_code = randint(1000000, 9999999) + st_trans_num = randint(100000000, 999999999) + + # Grab the vendor_id and trading_partner_id for EDI + # in the edi_config. + trading_partner_code =\ + sale_id.trading_partner_id.partner_header_string or 'Test' + if trading_partner_code: + trading_partner_padded = trading_partner_code.ljust(15) + + vendor_code =\ + sale_id.trading_partner_id.vendor_header_string or 'Test' + if vendor_code: + vendor_padded = vendor_code.ljust(15) + OUT_PATH = str(sale_id.trading_partner_id.out_path) + + # header, payment and date info + xml_output += '' +\ + trading_partner_code + '' + xml_output += '' +\ + po_num + '' + xml_output += '' +\ + str(sale_id.tset_purpose_code) + '' + xml_output += '' +\ + po_date + '' + xml_output += '' +\ + str(sale_id.purchase_order_type_code + ) + '' + xml_output += '' +\ + str(sale_id.po_type_description) + '' + xml_output += '' + po_num + '' + xml_output += 'RJ' + xml_output += '' +\ + today + '' + xml_output += '' +\ + str(sale_id.ship_complete_code) + '' + xml_output += '' +\ + str(sale_id.pricelist_id.currency_id.name + ) + '' + xml_output += '' +\ + str(sale_id.department) + '' + xml_output += '' +\ + str(sale_id.division) + '' + xml_output += '' +\ + str(sale_id.name) + '' + xml_output += '' +\ + str(sale_id.promotion_deal_number) + '' + xml_output += '' + vendor_code + '' + xml_output += '' +\ + str(sale_id.terms_type) + '' + xml_output += '' +\ + str(sale_id.terms_basis_date_code) + '' + xml_output += '' +\ + str(sale_id.terms_discount_percentage + ) + '' + xml_output += '' +\ + str(sale_id.terms_discount_due_days + ) + '' + xml_output += '' +\ + str(sale_id.terms_net_due_days + ) + '' + xml_output += '' +\ + str(sale_id.payment_term_id.note) + '' + xml_output += '' +\ + str(sale_id.payment_method_code + ) + '' + xml_output += 'ORS' + xml_output += '' + today + '' + cust_rec = sale_id.partner_id + ship_address_rec = sale_id.partner_shipping_id + + # customer contact details + xml_output += 'CH' + xml_output += '' +\ + str(cust_rec.name) + '' + xml_output += '' +\ + str(cust_rec.phone) + '' +# xml_output += '' + str(cust_rec.fax) + '' + xml_output += '' +\ + str(cust_rec.email) + '' + + # contact address details + xml_output += '
FW' + xml_output += '1' + xml_output +=\ + '11111' + xml_output += '' +\ + str(ship_address_rec.name) + '' + xml_output += '' +\ + str(ship_address_rec.street) + '' + xml_output += '' +\ + str(ship_address_rec.street2) + '' + xml_output += '' +\ + str(ship_address_rec.city) + '' + xml_output += '' +\ + str(ship_address_rec.state_id.code) + '' + xml_output += '' +\ + str(ship_address_rec.zip) + '' + xml_output += '' +\ + str(ship_address_rec.country_id.code) + '' + xml_output += 'CH' + xml_output += '' +\ + str(cust_rec.name) + '' + xml_output += '' +\ + str(cust_rec.phone) + '' +# xml_output += '' + str(cust_rec.fax) + '' + xml_output += '' +\ + str(cust_rec.email) + '
' + + # FOBRelatedInstruction + xml_output += '' +\ + str(sale_id.fob_pay_code) + '' + xml_output += '' +\ + str(sale_id.fob_location_qualifier) + '' + xml_output += '' +\ + str(sale_id.fob_location_description + ) + '' + xml_output += '' +\ + str(sale_id.fob_title_passage_code) + '' + xml_output += '' +\ + str(sale_id.fob_title_passage_location + ) + '' + + # CarrierInformation + xml_output += '' +\ + str(sale_id.carrier_trans_method_code + ) + '' + xml_output += '' +\ + str(sale_id.carrier_alpha_code) + '' + xml_output += '' +\ + str(sale_id.carrier_routing) + '' + xml_output += '' +\ + str(sale_id.routing_sequence_code) + '' +# xml_output += '' +\ +# str(sale_id.service_leve_code +# ) + ''' +# ''' + + # Reference + xml_output += '' +\ + str(sale_id.reference_qual) + '' + xml_output += '' +\ + str(sale_id.reference_id) + '' + xml_output += '' +\ + str(sale_id.ref_description) + '' + + # Notes + xml_output += '' +\ + str(sale_id.note_code) + '' + xml_output += '' +\ + str(sale_id.note_information_field + ) + '' + + # ChargesAllowances + xml_output += '' +\ + str(sale_id.allow_chrg_indicator) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_code) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_agency_code) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_agency) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_amt) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_percent_qual + ) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_percent) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_handling_code + ) + '' + xml_output += '' +\ + str(sale_id.reference_identification + ) + '' + xml_output += '' +\ + str(sale_id.allow_chrg_handling_description + ) + '' +\ + '
' + + total_lines = 0 + total_qty = 0 + total_weight = 0 + # Create the text string for 855 order acknowledgement + for line in sale_lines: + total_lines += 1 + est_del_date = '' + est_ship_date = '' + accept_code = '' + + if line.edi_est_ship_date: + est_ship_date =\ + datetime.strptime(line.edi_est_ship_date, '%Y-%m-%d') + est_ship_date = est_ship_date.strftime('%m/%d/%Y') + + if line.edi_line_msg == 'reject': + accept_code = 'CC' + + elif line.edi_line_msg == 'backorder': + accept_code = 'IB' + else: + accept_code = 'IA' + + uom = line.product_uom.name + quantity = int(line.product_uom_qty) + total_qty += quantity + total_weight += (line.product_id.weight * quantity) + original_po_qty = int(line.edi_line_qty) + + if uom == 'Unit(s)': + uom = 'EA' + else: + print("***** UOM is Not EA (Each) *****") + + # line items + xml_output += '' + xml_output += '' +\ + str(line.id) + '' + xml_output += '' +\ + str(line.buyer_part_number) + '' + xml_output += '' +\ + str(line.vendor_part_number) + '' + xml_output += '' +\ + str(line.consumer_package_code) + '' + xml_output += '' + str(line.gtin) + '' + xml_output += '' +\ + str(line.upc_case_code) + '' + xml_output += 'MN' + xml_output += '' +\ + str(line.product_id.default_code + ) + '' + xml_output += '' + str(uom) + '' + xml_output += '' +\ + str(line.edi_line_qty) + '' + xml_output += '' + str(line.price_unit + ) + '' + xml_output += '' +\ + str(line.purchase_price_basis) + '' + xml_output += '' +\ + str(sale_id.pricelist_id.currency_id.name + ) + '' + xml_output += '' +\ + str(line.product_size_code) + '' + xml_output += '' +\ + str(line.product_size_description + ) + '' + xml_output += '' +\ + str(line.product_color_code) + '' + xml_output += '' +\ + str(line.product_color_description + ) + '' + xml_output += '' +\ + str(line.product_material_code) + '' + xml_output += '' +\ + str(line.product_material_description + ) + '' + xml_output += '' + str(line.department + ) + '' + xml_output += '' + str(line.classs + ) + '' + + # date + xml_output += '' +\ + str(line.price_unit) + '' + xml_output += '' + today + '' + + # PriceInformation + xml_output += '' +\ + str(line.price_unit) + '' + xml_output += '' + str(line.price_unit + ) + '' + xml_output += '' + str(quantity) + '' + xml_output += '' +\ + str(line.multiple_price_quantity + ) + '' + xml_output += '' +\ + str(line.class_of_trade_code + ) + '' + + # ProductOrItemDescription + xml_output +=\ + '' +\ + str(line.item_description_type) + '' + xml_output += '' +\ + str(line.product_characteristic_code + ) + '' + xml_output += '' +\ + str(line.agency_qualifier_code) + '' + xml_output += '' +\ + str(line.product_description_code + ) + '' + xml_output += '' +\ + str(line.name + ) + '' + + # PhysicalDetails + xml_output += '' +\ + str(line.pack_qualifier) + '' + xml_output += '' + str(line.pack_value + ) + '' + xml_output += '' + str(line.pack_size + ) + '' + xml_output += '' + str(line.pack_uom) + '' + xml_output += '' + str(line.packing_medium + ) + '' + xml_output += '' +\ + str(line.packing_material) + '' + xml_output += '' + str(line.pack_weight + ) + '' + xml_output += '' +\ + str(line.pack_weight_uom + ) + '' + + # Reference + xml_output += '' +\ + str(sale_id.reference_qual) + '' + xml_output += '' + str(sale_id.reference_id + ) + '' + xml_output += '' +\ + str(sale_id.ref_description) + '' + + # Notes + xml_output += '' + str(sale_id.note_code + ) + '' + xml_output += '' +\ + str(sale_id.note_information_field + ) + '' + + # contact address details + xml_output += '
FW' + xml_output += '' + str(ship_address_rec.name + ) + '' + xml_output += '' + str(ship_address_rec.street + ) + '' + xml_output += '' + str(ship_address_rec.city) + '' + xml_output += '' + str(ship_address_rec.state_id.code + ) + '' + xml_output += '' + str(ship_address_rec.zip + ) + '' + xml_output += '' +\ + str(ship_address_rec.country_id.code + ) + '
' + + # ChargesAllowances + xml_output += '' +\ + str(line.allow_chrg_indicator) + '' + xml_output += '' + str(line.allow_chrg_code + ) + '' + xml_output += '' +\ + str(line.allow_chrg_agency_code) + '' + xml_output += '' +\ + str(line.allow_chrg_agency) + '' + xml_output += '' +\ + str(line.allow_chrg_amt) + '' + xml_output += '' +\ + str(line.allow_chrg_percent) + '' + xml_output += '' +\ + str(line.percent_dollar_basis) + '' + xml_output += '' +\ + str(line.allow_chrg_rate) + '' + xml_output += '' +\ + str(line.allow_chrg_qty_uom) + '' + xml_output += '' +\ + str(line.allow_chrg_handling_code + ) + '' + xml_output += '' +\ + str(line.allow_chrg_handling_description) +\ + '' + + # line item acknowledgement + xml_output += '' + xml_output += '' +\ + str(line.edi_line_msg) + '' + xml_output += '' +\ + str(quantity) + '' + xml_output += '' +\ + str(uom) + '' + xml_output +=\ + '002' + xml_output += '' +\ + str(line.edi_est_ship_date + ) + '' + + xml_output +=\ + 'FCP' + xml_output += '' +\ + str(line.price_unit) + '' + + for tax in line.tax_id: + xml_output += 'H780' + xml_output += '' +\ + str(1 + tax.amount / 100 * line.price_unit + ) + '' + xml_output += '' +\ + str(tax.amount / 100) +\ + '0' +\ + '99990000' + xml_output += '
' + + xml_output += '
' + + # Summary + xml_output += '' + str(sale_id.amount_total + ) + '' + xml_output += '' +\ + str(total_lines) + '' + xml_output += '' + str(total_qty + ) + '' + xml_output += '' + str(total_weight + ) + '' + xml_output += '0' + xml_output += '0' + xml_output += '0' + + date_format = "%Y-%m-%d %H:%M:%S" + now = datetime.now() + today = now.strftime(date_format) + sale_id.write({'check': True, 'sent_timestamp': today}) + print("***** SUCCESSFULLY STORED *****") + + xml_output += '
' + + # Write ASN doc to text file + filename = '855_' + today + '%s.txt' % num + filename.replace('/', '_') + if not OUT_PATH: + raise UserError('Add out path in Trading Partner') + fd = open(OUT_PATH + '/' + filename, 'w') + fd.write(xml_output) + fd.close() + return True + + def _create_855_wrapper(self): + # search for invoices that are ack_yes = True, + # edi_yes = True and sent_timestamp = False + eligible_orders = self.search([ + ('ack_yes', '=', True), ('edi_yes', '=', True), + ('sent_timestamp', '=', False) + ]) + return eligible_orders and eligible_orders.create_855() or False + + @api.multi + def action_send_855(self): + """ Creates and new 855 and puts it into the outbox + """ + # execute the create_855 method + self.create_855() + return True + +# @api.model +# def _prepare_procurement_group(self): +# res = super(SaleOrder, self)._prepare_procurement_group() +# res['trading_partner_id'] =\ +# self.trading_partner_id and self.trading_partner_id.id or False +# # res['edi_yes'] = self.trading_partner_id and self.edi_yes or False +# res['edi_yes'] = True +# res['asn_shipment'] = self.asn_shipment or 'asn_id' +# res['ship_to_code'] = self.ship_to_code or '' +# res['ship_not_before_date'] =\ +# self.trading_partner_id and self.ship_not_before_date or False +# res['cancel_after_date'] =\ +# self.trading_partner_id and self.cancel_after_date or False +# res['po_number'] = self.client_order_ref or '' +# return res + + @api.multi + def test_edi_status(self): + for line in self.order_line: + if line.edi_yes and not line.edi_line_msg and line.ack_yes: + return False + return True + + @api.multi + def _prepare_invoice(self): + """ + Prepare the dict of values to create the new invoice for a sales order. + This method may be overridden to implement custom invoice generation + (making sure to call super() to establish a clean extension chain). + """ + res = super(SaleOrder, self)._prepare_invoice() + for picking_id in self.picking_ids: + if picking_id.picking_type_id.id == 2: + break + + res['sale_id'] = self.id or False + res['bol_num'] =\ + self.picking_ids and picking_id and picking_id.bol_num or\ + self.bol_num or '' + res['picking_ids'] = self.picking_ids + res['scac_code'] = self.picking_ids and picking_id and\ + picking_id.carrier_id.scac_code or self.scac_code or False + res['tracking_num'] = self.picking_ids and picking_id and\ + picking_id.tracking_number or '' + res['sender_id'] = self.partner_id.sender_id or '' + res['asn_shipment'] = picking_id and picking_id.name or '' + res['trading_partner_id'] = self.trading_partner_id.id or False + res['edi_yes'] = self.edi_yes or False + res['ship_not_before_date'] = self.ship_not_before_date or False + res['cancel_after_date'] = self.cancel_after_date or False + res['supplier_code'] = self.supplier_code or False + res['ship_to_code'] = self.ship_to_code or False + res['client_order_ref'] = self.client_order_ref or False + res['tset_purpose_code'] = self.tset_purpose_code or '' + res['purchase_order_type_code'] =\ + self.purchase_order_type_code or '' + res['po_type_description'] = self.po_type_description or '' + res['ship_complete_code'] = self.ship_complete_code or '' + res['department'] = self.department or '' + res['division'] = self.division or '' + res['promotion_deal_number'] = self.promotion_deal_number or '' + res['terms_type'] = self.terms_type or '' + res['terms_basis_date_code'] = self.terms_basis_date_code or '' + res['terms_discount_percentage'] =\ + self.terms_discount_percentage or '' + res['terms_discount_due_days'] = self.terms_discount_due_days or '' + res['terms_net_due_days'] = self.terms_net_due_days or '' + res['payment_method_code'] = self.payment_method_code or '' + res['fob_pay_code'] = self.fob_pay_code or '' + res['fob_location_qualifier'] = self.fob_location_qualifier or '' + res['fob_location_description'] =\ + self.fob_location_description or '' + res['fob_title_passage_code'] = self.fob_title_passage_code or '' + res['fob_title_passage_location'] =\ + self.fob_title_passage_location or '' + res['carrier_trans_method_code'] =\ + self.carrier_trans_method_code or '' + res['carrier_alpha_code'] = self.carrier_alpha_code or '' + res['carrier_routing'] = self.carrier_routing or '' + res['routing_sequence_code'] = self.routing_sequence_code or '' + res['service_level_code'] = self.service_level_code or '' + res['reference_qual'] = self.reference_qual or '' + res['reference_id'] = self.reference_id or '' + res['ref_description'] = self.ref_description or '' + res['note_code'] = self.note_code or '' + res['note_information_field'] = self.note_information_field or '' + res['allow_chrg_indicator'] = self.allow_chrg_indicator or '' + res['allow_chrg_code'] = self.allow_chrg_code or '' + res['allow_chrg_agency_code'] = self.allow_chrg_agency_code or '' + res['allow_chrg_agency'] = self.allow_chrg_agency or '' + res['allow_chrg_amt'] = float(self.allow_chrg_amt) or 0 + res['allow_chrg_percent_qual'] = self.allow_chrg_percent_qual or '' + res['allow_chrg_percent'] = float(self.allow_chrg_percent) or 0 + res['allow_chrg_handling_code'] =\ + self.allow_chrg_handling_code or '' + res['reference_identification'] =\ + self.reference_identification or '' + res['allow_chrg_handling_description'] =\ + self.allow_chrg_handling_description or '' + return res + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + @api.multi + def _prepare_procurement_values(self, group_id=False): + res = super(SaleOrderLine, self).\ + _prepare_procurement_values(group_id) + res['po_number'] = self.po_number or '' + res['asn_shipment'] = self.asn_shipment or '' + res['sale_line_id'] = self.id or False + res['edi_line_num'] = self.edi_line_num or 0 + res['so_id'] = self.order_id.id or False + res['ship_to_code'] = self.order_id.ship_to_code or '' + res['trading_partner_id'] = self.order_id.trading_partner_id and\ + self.order_id.trading_partner_id.id or False + res['edi_yes'] = self.order_id.trading_partner_id and\ + self.order_id.edi_yes or False + res['ship_not_before_date'] = self.order_id.trading_partner_id and\ + self.order_id.ship_not_before_date or False + res['cancel_after_date'] = self.order_id.trading_partner_id and\ + self.order_id.cancel_after_date or False + return res + + @api.multi + def _prepare_invoice_line(self, qty): + """ + Prepare the dict of values to create the new invoice line for a + sales order line. + + :param qty: float quantity to invoice + """ + res = super(SaleOrderLine, self)._prepare_invoice_line(qty=qty) + res['edi_line_qty'] = self.edi_line_qty + res['edi_line_num'] = self.edi_line_num + res['asn_shipment'] = self.asn_shipment + res['po_number'] = self.po_number + res['buyer_part_number'] = self.buyer_part_number + res['vendor_part_number'] = self.vendor_part_number or '' + res['consumer_package_code'] = self.consumer_package_code or '' + res['gtin'] = self.gtin or '' + res['upc_case_code'] = self.upc_case_code or '' + res['purchase_price_basis'] = self.purchase_price_basis or '' + res['product_size_code'] = self.product_size_code or '' + res['product_size_description'] = self.product_size_description or '' + res['product_color_code'] = self.product_color_code or '' + res['product_color_description'] = self.product_color_description or '' + res['product_material_code'] = self.product_material_code or '' + res['product_material_description'] =\ + self.product_material_description or '' + res['department'] = self.department or '' + res['classs'] = self.classs or '' + res['price_type_id_code'] = self.price_type_id_code or '' + res['multiple_price_quantity'] = self.multiple_price_quantity or '' + res['class_of_trade_code'] = self.class_of_trade_code or '' + res['item_description_type'] = self.item_description_type or '' + res['product_characteristic_code'] =\ + self.product_characteristic_code or '' + res['agency_qualifier_code'] = self.agency_qualifier_code or '' + res['product_description_code'] = self.product_description_code or '' + res['pack_qualifier'] = self.pack_qualifier or '' + res['pack_value'] = self.pack_value or '' + res['pack_size'] = self.pack_size or '' + res['pack_uom'] = self.pack_uom or '' + res['packing_medium'] = self.packing_medium or '' + res['packing_material'] = self.packing_material or '' + res['pack_weight'] = self.pack_weight or '' + res['pack_weight_uom'] = self.pack_weight_uom or '' + res['location_code_qualifier'] = self.location_code_qualifier or '' + res['location'] = self.location or '' + res['allow_chrg_indicator'] = self.allow_chrg_indicator or '' + res['allow_chrg_code'] = self.allow_chrg_code or '' + res['allow_chrg_agency_code'] = self.allow_chrg_agency_code or '' + res['allow_chrg_agency'] = self.allow_chrg_agency or '' + res['allow_chrg_amt'] = float(self.allow_chrg_amt) or 0 + res['allow_chrg_percent'] = self.allow_chrg_percent or '' + res['percent_dollar_basis'] = self.percent_dollar_basis or '' + res['allow_chrg_rate'] = self.allow_chrg_rate or '' + res['allow_chrg_handling_code'] = self.allow_chrg_handling_code or '' + res['allow_chrg_qty_uom'] = self.allow_chrg_qty_uom or '' + res['allow_chrg_handling_description'] =\ + self.allow_chrg_handling_description or '' + return res + + @api.multi + def edi_line_status_change(self, order_id, edi_line_msg, + order_edi_last_date): + res = { + 'value': { + 'state': 'draft', + 'edi_est_ship_date': order_edi_last_date + } + } + if self.ids: + if edi_line_msg == 'reject': + res['value']['state'] = 'cancel' + res['value']['edi_est_ship_date'] = '' + return res + + edi_yes = fields.Boolean( + 'From an EDI PO?', + help="Is this order from an EDI purchase order, 850 EDI doc?" + ) + asn_shipment = fields.Char( + 'ASN Shipment Number from Converted 856' + ) + po_number = fields.Char('Line Item PO Number from Converted 856') + buyer_part_number = fields.Char('Buyer Part Number') + edi_line_num = fields.Integer('EDI PO line number') + edi_line_qty = fields.Float( + 'Original EDI Quantity', + digits=dp.get_precision('Product Unit of Measure') + ) + ack_yes = fields.Boolean( + '855', + readonly=True, + help="Will this order have an 855?" + ) + edi_est_del_date = fields.Date( + 'Est. Del. Date', + help="Line Item Estimated Delivery Date" + ) + edi_est_ship_date = fields.Date( + 'Est. Ship Date', + help="Line Item Estimated Shipping Date" + ) + edi_line_msg = fields.Selection( + [('reject', 'Reject'), + ('accept', 'Accept'), + ('backorder', 'Backorder')], + 'EDI Line Status' + ) + ship_not_before_date = fields.Date( + 'Do Not Ship Before This Date', + help="Do Not Ship Before This Date." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + edi_intransit_qty = fields.Float( + 'Incoming Qty', + digits=dp.get_precision('Product Unit of Measure') + ) + edi_outgoing_qty = fields.Float( + 'Reserved Qty', + digits=dp.get_precision('Product Unit of Measure') + ) + edi_avlforsale_qty = fields.Float( + 'Available for Sale', + digits=dp.get_precision('Product Unit of Measure') + ) + vendor_part_number = fields.Char('Vendor Part Number') + consumer_package_code = fields.Char('Consumer Package Code') + gtin = fields.Char('GTIN') + upc_case_code = fields.Char('UPC Case Code') + purchase_price_basis = fields.Char('Purchase Price Basis') + product_size_code = fields.Char('Product Size Code') + product_size_description = fields.Char('Product Size Description') + product_color_code = fields.Char('Product Color Code') + product_color_description = fields.Char('Product Color Description') + product_material_code = fields.Char('Product Material Code') + product_material_description = fields.Char('Product Material Description') + department = fields.Char('Department') + classs = fields.Char('Class') + price_type_id_code = fields.Char('Price Type ID Code') + multiple_price_quantity = fields.Float('Multiple Price Quantity') + class_of_trade_code = fields.Char('Class Of Trade Code') + item_description_type = fields.Char('Item Description Type') + product_characteristic_code = fields.Char('Product Characteristic Code') + agency_qualifier_code = fields.Char('Agency Qualifier Code') + product_description_code = fields.Char('Product Description Code') + pack_qualifier = fields.Char('Pack Qualifier') + pack_value = fields.Integer('Pack Value') + pack_size = fields.Char('Pack Size') + pack_uom = fields.Char('Pack UOM') + packing_medium = fields.Char('Packing Medium') + packing_material = fields.Char('Packing Material') + pack_weight = fields.Float('Pack Weight') + pack_weight_uom = fields.Char('Pack Weight UOM') + location_code_qualifier = fields.Char( + 'Location Code Qualifier', + help="Code identifying the structure or format of the" + "related location number(s)." + ) + location = fields.Char( + 'Location', + help="For CrossDock, it's the marked for location." + "For MultiStore[could also be DC] ship-to location." + ) + allow_chrg_indicator = fields.Char( + 'Allow Chrg Indicator', + help="Code which indicates an allowance or" + "charge for the service specified." + ) + allow_chrg_code = fields.Char( + 'Allow Chrg Code', + help="Code describing the type of allowance or charge for" + "the service specified." + ) + allow_chrg_agency_code = fields.Char( + 'Allow Chrg Agency Code', + help="Code identifying the agency assigning the code values." + ) + allow_chrg_agency = fields.Char( + 'Allow Chrg Agency', + help="Agency maintained code identifying the service," + "promotion, allowance, or charge." + ) + allow_chrg_amt = fields.Float( + 'Allow Chrg Amt', + help="Amount of the allowance or charge." + ) + allow_chrg_percent = fields.Float( + 'Allow Chrg Percent', + help='''Percentage of allowance or charge. Percentages should be + represented as real numbers[0% through 100% should be + normalized to 0.0 through 100.00]..''' + ) + percent_dollar_basis = fields.Float('Percent Dollar Basis') + allow_chrg_rate = fields.Float( + 'Allow Chrg Rate', + help="Amount of the allowance or charge." + ) + allow_chrg_handling_code = fields.Char( + 'Allow Chrg Handling Code', + help="Code indicating method of handling for an allowance or charge.." + ) + allow_chrg_qty_uom = fields.Char('Allow Chrg Qty UOM') + allow_chrg_handling_description = fields.Char( + 'Allow Chrg Handling Description', + help="Free-form textual description of the note." + ) + + @api.multi + def _get_commitment_date(self): + for line in self: + order = line.order_id + order_datetime = datetime.strptime(order.date_order, DS) + dt = order_datetime + timedelta(days=line.delay or 0.0) + est_del_date = dt.strftime(DS) + return est_del_date diff --git a/connector_spscommerce/models/stock.py b/connector_spscommerce/models/stock.py new file mode 100644 index 0000000..0772032 --- /dev/null +++ b/connector_spscommerce/models/stock.py @@ -0,0 +1,57 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductPricelist(models.Model): + _inherit = 'product.pricelist' + + def price_get_wrapper(self, prod_id, qty, partner=None): + return self.price_rule_get(prod_id, qty, partner=partner) + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + tracking_number = fields.Char( + 'Tracking Number', + help="Tracking Number" + ) + bol = fields.Char( + 'Bill of Lading Number', + help="Bill of Lading Number" + ) + package_code = fields.Selection( + [('PLT71', 'PLT71'), ('CTN25', 'CTN25')], + 'Package Code', + help="Pkg Code Qualifier.", + default="PLT71" + ) + + +class StockQuantPackage(models.Model): + _inherit = "stock.quant.package" + + tracking_number = fields.Char( + 'Tracking Number', + help="Tracking Number" + ) + bol = fields.Char( + 'Bill of Lading Number', + help="Bill of Lading Number" + ) + package_code = fields.Selection( + [('PLT71', 'PLT71'), ('CTN25', 'CTN25')], + 'Package Code', + help="Pkg Code Qualifier.", + default="PLT71" + ) + + +class DeliveryCarrier(models.Model): + _inherit = "delivery.carrier" + + scac_code = fields.Char( + 'SCAC Ship Method Code', + help="Shipping carrier code." + ) diff --git a/connector_spscommerce/models/stock_move.py b/connector_spscommerce/models/stock_move.py new file mode 100644 index 0000000..19ce72b --- /dev/null +++ b/connector_spscommerce/models/stock_move.py @@ -0,0 +1,151 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + asn_shipment = fields.Char('ASN Shipment Number from 856') + po_number = fields.Char('Line Item PO Number from 856') + edi_line_num = fields.Integer('EDI PO line number') + so_id = fields.Many2one( + 'sale.order', + 'Sale Order', + help='Sale Order from when this Invoice was created' + ) + edi_yes = fields.Boolean( + 'From an EDI PO?', + help="Is this order from an EDI purchase order, 850 EDI doc." + ) + ship_not_before_date = fields.Date( + 'Do Not Ship Before This Date', + help="Do Not Ship Before This Date." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + package_code = fields.Selection( + [('PLT71', 'PLT71'), + ('CTN25', 'CTN25')], + 'Package Code', + help="Pkg Code Qualifier.", + default="PLT71" + ) + product_material_description = fields.Char( + 'Product Material Description', + help="ProductMaterialDescription" + ) + consumer_package_code = fields.Char( + 'Consumer Package Code', + help="ConsumerPackageCode" + ) + gtin = fields.Char( + 'GTIN', + help="GTIN" + ) + upc_case_code = fields.Char( + 'UPC Case Code', + help="UPCCaseCode" + ) + natl_drug_code = fields.Char( + 'Natl Drug Code', + help="NatlDrugCode" + ) + international_standard_book_number = fields.Char( + 'International Standard Book Number', + help="InternationalStandardBookNumber" + ) + product_size_description = fields.Char( + 'Product Size Description', + help="ProductSizeDescription" + ) + product_color_description = fields.Char( + 'Product Color Description', + help="ProductColorDescription" + ) + + tracking_number = fields.Char( + 'Tracking Number', + help="Tracking Number" + ) + bol = fields.Char( + 'Bill of Lading Number', + help="Bill of Lading Number" + ) + package_code = fields.Selection( + [('PLT71', 'PLT71'), ('CTN25', 'CTN25')], + 'Package Code', + help="Pkg Code Qualifier.", + default="PLT71" + ) + po_number = fields.Char( + 'PO Number from EDI 850', + help="PO Number from EDI 850." + ) + edi_line_num = fields.Char( + 'Line Number from EDI 850', + help="Line Number from EDI 850." + ) + reference_qual = fields.Char( + 'Reference Qual', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + reference_id = fields.Char( + 'Reference ID', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + ref_description = fields.Char( + 'Description', + help="Free-form textual description to clarify the" + "related data elements and their content." + ) + note_code = fields.Char( + 'Note Code', + help="Code specifying the type of note." + ) + note_information_field = fields.Char( + 'Note Information Field', + help="Free-form textual description of the note." + ) + pack_qualifier = fields.Char('Pack Qualifier') + pack_value = fields.Char('Pack Value') + pack_size = fields.Char('Pack Size') + pack_uom = fields.Char('Pack UOM') + packing_medium = fields.Char('Packing Medium') + packing_material = fields.Char('Packing Material') + + @api.multi + def _assign_picking(self): + result = super(StockMove, self)._assign_picking() + pick_ids = {} + + for move in self: + picking = move.picking_id + if not picking or pick_ids.get(picking.id, False): + continue + pick_ids[picking.id] = move.id + edi_yes = move.procurement_id.edi_yes + if edi_yes: + res = {} + res['trading_partner_id'] = \ + move.procurement_id.trading_partner_id and\ + move.procurement_id.trading_partner_id.id or False + res['edi_yes'] = move.procurement_id.trading_partner_id\ + and edi_yes or False + res['ship_not_before_date'] = \ + move.procurement_id.trading_partner_id and\ + move.procurement_id.ship_not_before_date or False + res['cancel_after_date'] = \ + move.procurement_id.trading_partner_id and\ + move.procurement_id.cancel_after_date or False + picking.write(res) + return result diff --git a/connector_spscommerce/models/stock_picking.py b/connector_spscommerce/models/stock_picking.py new file mode 100644 index 0000000..b6a913b --- /dev/null +++ b/connector_spscommerce/models/stock_picking.py @@ -0,0 +1,760 @@ +# Copyright (c) Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re +import dicttoxml +from datetime import datetime +from odoo import api, fields, models +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DSD +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DS +from odoo.exceptions import UserError + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + ship_not_before_date = fields.Date( + 'Do Not Ship Before This Date', + help="Do Not Ship Before This Date." + ) + cancel_after_date = fields.Date( + 'Cancel if Shipped After This Date', + help="Cancel if Shipped After This Date." + ) + client_order_ref = fields.Text( + 'Customer PO #', + help="Customer PO #" + ) + edi_yes = fields.Boolean( + 'From an EDI PO', + readonly=True, + help="Is this order from an EDI purchase order, 850 EDI doc." + ) + est_del_date = fields.Date( + 'Estimated Delivery Date', + help="Calculated based on shipping method." + ) + trading_partner_id = fields.Many2one( + 'edi.config', + 'Trading Partner', + help='EDI Configuration information for partner' + ) + sent_timestamp = fields.Datetime( + '856 Sent Date', + help="The timestamp for when the 856 was sent." + ) + check = fields.Boolean( + '856 Created', + help="A check to see if 856 has been sent." + ) + bol_num = fields.Char( + 'BoL', + help="BoL Number." + ) + tracking_number = fields.Char( + 'Tracking Number', + help="Tracking Number" + ) + ship_to_code = fields.Char( + 'Ship To Warehouse', + help="Trading Partner Ship to location code." + ) + package_code = fields.Selection( + [('PLT71', 'PLT71'), ('CTN25', 'CTN25')], + 'Package Code', + help="Pkg Code Qualifier.", + default="PLT71" + ) + tset_purpose_code = fields.Char( + 'Tset Purpose Code', + help="Code identifying purpose of the document.." + ) + purchase_order_type_code = fields.Char( + 'Purchase Order TypeCode', + help="Code specifying the type of purchase order." + ) + po_type_description = fields.Char( + 'PO Type Description', + help="Free form text to describe the type of order." + ) + ship_complete_code = fields.Char( + 'Ship Complete Code', + help='''Code to identify a specific requirement or agreement of sale. + Should only be used to indicate if an item can be + placed on backorder.''' + ) + department = fields.Char( + 'Department', + help="Name or number identifying an area wherein merchandise is" + "categorized within a store." + ) + division = fields.Char( + 'Division', + help="Different entities belonging to the same parent company." + ) + promotion_deal_number = fields.Char( + 'Promotion Deal Number', + help="Number uniquely identifying an agreement for" + "a special offer or price." + ) + terms_type = fields.Char( + 'Terms Type', + help="Code identifying type of payment terms." + ) + terms_basis_date_code = fields.Char( + 'Terms Basis Date Code', + help="Code identifying the beginning of the terms period." + ) + terms_discount_percentage = fields.Char( + 'Terms Discount Percentage', + help="Terms discount percentage available to the purchaser" + ) + terms_discount_due_days = fields.Char( + 'Terms Discount Due Days', + help="Number of days by which payment or invoice must be received in" + "order to receive the discount noted." + ) + terms_net_due_days = fields.Char( + 'Terms Net Due Days', + help="Number of days until total invoice amount is" + "due[discount not applicable." + ) + payment_method_code = fields.Char( + 'Payment Method Code', + help="Indication of the instrument of payment." + ) + fob_pay_code = fields.Char( + 'FOB Pay Code', + help="Code identifying payment terms for transportation charges." + ) + fob_location_qualifier = fields.Char( + 'FOB Location Qualifier', + help="Code identifying type of location at which ownership of" + "goods is transferred." + ) + fob_location_description = fields.Char( + 'FOB Location Description', + help="Free-form textual description of the location at which" + "ownership of goods is transferred." + ) + fob_title_passage_code = fields.Char( + 'FOB Title Passage Code', + help="Code describing the location of ownership of the goods." + ) + fob_title_passage_location = fields.Char( + 'FOB Title Passage Location', + help="Location of ownership of the goods." + ) + carrier_trans_method_code = fields.Char( + 'Carrier Trans Method Code', + help="Code specifying the method or type of transportation" + "for the shipment." + ) + carrier_alpha_code = fields.Char( + 'Carrier Alpha Code', + help="Standard Carrier Alpha Code[SCAC] - " + ) + carrier_routing = fields.Char( + 'Carrier Routing', + help="Free-form description of the routing/requested routing for" + "shipment or the originating carrier's identity." + ) + routing_sequence_code = fields.Char('Routing Sequence Code') + service_level_code = fields.Char( + 'Service Level Code', + help="Code indicating the level of transportation service or the" + "billing service offered by the transportation carrier." + ) + reference_qual = fields.Char( + 'Reference Qual', + help="Code specifying the type of data in the" + "ReferenceID/ReferenceDescription." + ) + reference_id = fields.Char( + 'Reference ID', + help="Code specifying the type of data in the " + "ReferenceID/ReferenceDescription." + ) + ref_description = fields.Char( + 'Description', + help="Free-form textual description to clarify the related data" + "elements and their content." + ) + note_code = fields.Char( + 'Note Code', + help="Code specifying the type of note." + ) + note_information_field = fields.Char( + 'Note Information Field', + help="Free-form textual description of the note." + ) + allow_chrg_indicator = fields.Char( + 'Allow Chrg Indicator', + help="Code which indicates an allowance or charge for" + "the service specified." + ) + allow_chrg_code = fields.Char( + 'Allow Chrg Code', + help="Code describing the type of allowance or charge for the" + "service specified." + ) + allow_chrg_agency_code = fields.Char( + 'Allow Chrg Agency Code', + help="Code identifying the agency assigning the code values." + ) + allow_chrg_agency = fields.Char( + 'Allow Chrg Agency', + help="Agency maintained code identifying the service, promotion," + "allowance, or charge." + ) + allow_chrg_amt = fields.Float( + 'Allow Chrg Amt', + help="Amount of the allowance or charge." + ) + allow_chrg_percent_qual = fields.Char( + 'Allow Chrg Percent Qual', + help="Code indicating on what basis an allowance or charge percent" + "is calculated.." + ) + allow_chrg_percent = fields.Float( + 'Allow Chrg Percent', + help='''Percentage of allowance or charge. Percentages should be + represented as real numbers[0% through 100% should be normalized to + 0.0 through 100.00]..''' + ) + allow_chrg_handling_code = fields.Char( + 'Allow Chrg Handling Code', + help="Code indicating method of handling for an allowance or charge.." + ) + reference_identification = fields.Char('Reference Identification') + allow_chrg_handling_description = fields.Char( + 'Allow Chrg Handling Description', + help="Free-form textual description of the note." + ) + appointment_number = fields.Char('Appointment Number') + asn_structure_code = fields.Char('ASN Structure Code') + address_type_code = fields.Char('Address Type Code') + location_code_qualifier = fields.Char('Location Code Qualifier') + address_location_number = fields.Char('Address Location Number') + status_code = fields.Char('Status Code') + equipment_description_code = fields.Char('Equipment Description Code') + carrier_equipment_initial = fields.Char('Carrier Equipment Initial') + carrier_equipment_number = fields.Char('Carrier Equipment Number') + seal_number = fields.Char('Seal Number') + allow_chrg_rate = fields.Char('Allow Chrg Rate') + + def put_in_pack(self): + stock_move_line_obj = self.env['stock.move.line'] + for pick in self: + operations = pick.move_line_ids.\ + filtered(lambda o: o.qty_done > 0 and not o.result_package_id) + for operation in operations: + + # assign edi_line_num and po_number to + # pack operation, move by move. + link_ids = stock_move_line_obj.\ + search([('picking_id', '=', pick.id)]) or [] + move = False + for link_id in link_ids: + if link_id and link_id.move_id: + move = link_id.move_id + break + op_write = move and link_id and link_id.operation_id and\ + link_id.move_id.write({ + 'edi_line_num': move.edi_line_num, + 'po_number': move.po_number + }) or False + return super(StockPicking, self).put_in_pack() + + @api.model + def create(self, vals): + res = super(StockPicking, self).create(vals) + if res.origin: + sale_obj = self.env['sale.order'] + sale_id = sale_obj.search([('name', '=', res.origin)]) + + # look back to the sale order and get the edi field values and + # write them to the picking + if sale_id: + data = { + 'ship_not_before_date': sale_id.ship_not_before_date, + 'cancel_after_date': sale_id.cancel_after_date, + 'client_order_ref': sale_id.client_order_ref, + 'edi_yes': sale_id.edi_yes, + 'trading_partner_id': sale_id.trading_partner_id.id, + 'bol_num': sale_id.bol_num, + # 'tracking_number': sale_id.tracking_number, + 'scac_code': sale_id.scac_code, + 'ship_to_code': sale_id.ship_to_code, + } + res.write(data) + return res + +# def _get_invoice_vals(self, key, inv_type, journal_id, move): +# res = super(StockPicking, self +# )._get_invoice_vals(key, inv_type, journal_id, move) +# res['so_id'] = move.picking_id.sale_id or False +# res['trading_partner_id'] =\ +# move.picking_id.trading_partner_id.id or False +# res['edi_yes'] = move.picking_id.edi_yes or False +# res['ship_not_before_date'] =\ +# move.picking_id.ship_not_before_date or False +# res['cancel_after_date'] = move.picking_id.cancel_after_date or False +# res['bol_num'] = move.picking_id.bol_num or '' +# res['tracking_number'] = move.picking_id.carrier_tracking_ref or '' +# res['scac_code'] = move.picking_id.carrier_id.scac_code or '' +# res['ship_to_code'] = move.picking_id.ship_to_code or '' +# return res + + @api.multi + def get_operations(self): + operations_dict = [] + for move in self.move_lines: + if move.picking_id: + operations_dict.append(move or False) + for line in move.move_line_ids: + if not line.result_package_id: + continue + package = line.result_package_id + return operations_dict + + @api.multi + def create_text_856(self): + today = str(datetime.now().strftime(DS)) or '' + processed = 0 + name = '' + + # for each picking + shipments_list = {'Shipments': []} + for picking in self: + name += picking.name + + # Grab the vendor_id and trading_partner_id for + # EDI in the edi_config. + trading_partner_code =\ + picking.trading_partner_id.partner_header_string + vendor_code = picking.trading_partner_id.vendor_header_string + + # ship_date + if picking.scheduled_date: + ship_date = picking.scheduled_date + dateship_object = datetime.strptime(ship_date, DSD) + ship_date = dateship_object.date() + ship_date = str(ship_date) + + # ship_time + ship_time = picking.scheduled_date + dateship_object = datetime.strptime(ship_time, DSD) + ship_time = dateship_object.time() + ship_time = str(ship_time) + + # Schedule_date + schedule_date = picking.est_del_date + if not schedule_date: + schedule_date = picking.scheduled_date + dateship_object = datetime.strptime(schedule_date, DSD) + else: + dateship_object = datetime.strptime(schedule_date, DS) + schedule_date = dateship_object.date() + schedule_date = str(schedule_date) + + # Schedule_time + schedule_time = picking.est_del_date + if not schedule_time: + schedule_time = picking.scheduled_date + dateship_object = datetime.strptime(schedule_time, DSD) + else: + dateship_object = datetime.strptime(schedule_time, DS) + schedule_time = dateship_object.time() + schedule_time = str(schedule_time) + + # Notice_date + notice_date = picking.ship_not_before_date + if not notice_date: + notice_date = picking.date + dateship_object = datetime.strptime(notice_date, DSD) + else: + dateship_object = datetime.strptime(notice_date, DS) + notice_date = dateship_object.date() + notice_date = str(notice_date) + + # get total packages and weight for the entire order + total_weight = 0 + total_qty = 0 + + for move in picking.move_lines: + total_qty += move.product_qty + total_weight += move.product_id.weight * move.product_qty + + # initialize + shipment_dict = {'Shipment': {}} + # 1. Header Line - one line for the order + meta_dict = {'Meta': {'Version': '1.0'}} + header_dict = {'Header': {'ShipmentHeader': { + 'TradingPartnerId': trading_partner_code, + 'ShipmentIdentification': str(picking.name), + 'ShipmentDate': ship_date, + 'TsetPurposeCode': picking.tset_purpose_code, + 'ShipNoticeDate': notice_date, + 'ShipNoticeTime': '00:00:00', + 'ASNStructureCode': picking.asn_structure_code, + 'BillOfLadingNumber': picking.bol_num, + 'CarrierProNumber': picking.carrier_tracking_ref, + 'AppointmentNumber': picking.appointment_number, + 'CurrentScheduledDeliveryDate': schedule_date, + 'CurrentScheduledDeliveryTime': schedule_time, + }, + 'Date': { + 'DateTimeQualifier1': '945', + 'Date1': ship_date, + 'Time1': ship_time, + 'DateTimePeriod': '', + }, + 'Reference': { + 'ReferenceQual': picking.reference_qual, + 'ReferenceID': picking.reference_id, + 'Description': picking.ref_description, + }, + 'Notes': { + 'NoteCode': picking.note_code, + 'NoteInformationField': picking.note_information_field, + }, + 'Contact': { + 'ContactTypeCode': 'BD', + 'ContactName': + picking.partner_id and picking.partner_id.name or '', + 'PrimaryPhone': + picking.partner_id and picking.partner_id.phone or '', + # 'PrimaryFax': + # picking.partner_id and picking.partner_id.fax or '', + 'PrimaryEmail': + picking.partner_id and picking.partner_id.email or '', + }, + 'Address': { + 'AddressTypeCode': picking.address_type_code, + 'LocationCodeQualifier': picking.location_code_qualifier, + 'AddressLocationNumber': picking.address_location_number, + 'AddressName': picking.partner_id.name, + 'Address1': picking.partner_id.street, + 'Address2': picking.partner_id.street2, + 'City': picking.partner_id.city, + 'State': picking.partner_id.state_id.name, + 'PostalCode': picking.partner_id.zip, + 'Country': picking.partner_id.country_id.name, + 'Contact': { + 'ContactTypeCode': 'BD', + 'ContactName': + picking.partner_id and picking.partner_id.name or '', + 'PrimaryPhone': + picking.partner_id and picking.partner_id.phone or '', + # 'PrimaryFax': + # picking.partner_id and picking.partner_id.fax or '', + 'PrimaryEmail': + picking.partner_id and picking.partner_id.email or '', + }, + }, + 'CarrierInformation': { + 'StatusCode': picking.status_code, + 'CarrierTransMethodCode': + picking.carrier_trans_method_code, + 'CarrierAlphaCode': picking.carrier_alpha_code, + 'CarrierRouting': picking.carrier_routing, + 'EquipmentDescriptionCode': + picking.equipment_description_code, + 'CarrierEquipmentInitial': + picking.carrier_equipment_initial, + 'CarrierEquipmentNumber': + picking.carrier_equipment_number, + 'SealNumber': picking.seal_number, + 'RoutingSequenceCode': picking.routing_sequence_code + }, + 'QuantityAndWeight': { + 'LadingQuantity': total_qty, + 'WeightQualifier': 'G', + 'Weight': total_weight, + 'WeightUOM': 'AD', + }, + 'ChargesAllowances': { + 'AllowChrgIndicator': picking.allow_chrg_indicator, + 'AllowChrgCode': picking.allow_chrg_code, + 'AllowChrgAmt': picking.allow_chrg_amt, + 'AllowChrgPercentQual': picking.allow_chrg_percent_qual, + 'AllowChrgPercent': picking.allow_chrg_percent, + 'AllowChrgHandlingCode': picking.allow_chrg_handling_code, + 'AllowChrgHandlingDescription': + picking.allow_chrg_handling_description, + 'AllowChrgRate': picking.allow_chrg_rate + }, + 'FOBRelatedInstruction': { + 'FOBPayCode': picking.fob_pay_code, + 'FOBLocationQualifier': picking.fob_location_qualifier, + 'FOBLocationDescription': picking.fob_location_description, + }, + } + } + orderlevel_dict = {'OrderLevel': {'OrderHeader': { + 'InternalOrderNumber': picking.name, + 'InternalOrderDate': ship_date, + 'InvoiceNumber': '99999-123', + 'InvoiceDate': '', + 'PurchaseOrderNumber': picking.origin, + 'ReleaseNumber': picking.name, + 'PurchaseOrderDate': ship_date, + 'Department': picking.department, + 'Vendor': picking.partner_id.id, + 'CustomerOrderNumber': '' + }, + 'QuantityAndWeight': { + 'LadingQuantity': total_qty, + 'WeightQualifier': 'G', + 'Weight': total_weight, + 'WeightUOM': 'AD', + }, + 'CarrierInformation': { + 'StatusCode': picking.status_code, + 'CarrierTransMethodCode': + picking.carrier_trans_method_code, + 'CarrierAlphaCode': picking.carrier_alpha_code, + 'CarrierRouting': picking.carrier_routing, + 'EquipmentDescriptionCode': + picking.equipment_description_code, + 'CarrierEquipmentInitial': + picking.carrier_equipment_initial, + 'CarrierEquipmentNumber': picking.carrier_equipment_number, + 'SealNumber': picking.seal_number, + 'RoutingSequenceCode': picking.routing_sequence_code + }, + 'Reference': { + 'ReferenceQual': picking.reference_qual, + 'ReferenceID': picking.reference_id, + 'Description': picking.ref_description, + }, + 'Notes': { + 'NoteCode': picking.note_code, + 'NoteInformationField': picking.note_information_field, + }, + 'ChargesAllowances': { + 'AllowChrgIndicator': picking.allow_chrg_indicator, + 'AllowChrgCode': picking.allow_chrg_code, + 'AllowChrgAmt': picking.allow_chrg_amt, + 'AllowChrgPercentQual': picking.allow_chrg_percent_qual, + 'AllowChrgPercent': picking.allow_chrg_percent, + 'AllowChrgHandlingCode': picking.allow_chrg_handling_code, + 'AllowChrgHandlingDescription': + picking.allow_chrg_handling_description, + 'AllowChrgRate': picking.allow_chrg_rate + }, + } + } + packlevel_dict = { + 'PackLevel': {}, + 'Itemlevel': {'ShipmentLine': {}}} + + for operation in self.get_operations(): + package_id = operation.move_line_ids.\ + mapped('package_id') | operation.move_line_ids.\ + mapped('result_package_id') + packlevel_dict['PackLevel'] = {'Pack': { + 'PackLevelType': 'P', + 'ShippingSerialID': '9996999', + 'CarrierPackageID': package_id or False, + }, + 'PhysicalDetails': { + 'PackQualifier': operation.pack_qualifier, + 'PackValue': operation.pack_value, + 'PackSize': operation.pack_size, + 'PackUOM': operation.pack_uom, + 'PackingMedium': operation.packing_medium, + 'PackingMaterial': operation.packing_material, + }, + 'Date': { + 'DateTimeQualifier1': '619', + 'Date1': ship_date, + 'Time1': ship_time, + }, + 'Reference': { + 'ReferenceQual': operation.reference_qual, + 'ReferenceID': operation.reference_id, + 'Description': operation.ref_description, + }, + 'Notes': { + 'NoteCode': operation.note_code, + 'NoteInformationField': + operation.note_information_field, + }, + } + """packlevel_dict['ItemLevel']['ShipmentLine'] = { + 'LineSequenceNumber': '01', + 'BuyerPartNumber': '9999-SPS', + 'VendorPartNumber': '', + 'ConsumerPackageCode': 'FW', + 'EAN': 'testReferenceID', + 'GTIN': 'New products only. Do not reuse packaging', + 'UPCCaseCode': 'FW', + 'NatlDrugCode': 'testReferenceID', + 'InternationalStandardBookNumber': '', + 'ProductID': { + 'PartNumberQual': 'STAEV', + 'PartNumber': 'REPEAT LOGO PREVIOUS ORDER', + }, + 'OrderQty': '01', + 'OrderQtyUOM': 'P', + 'PurchasePrice': '9999-SPS', + 'ItemStatusCode': '', + 'ShipQty': '', + 'ShipQtyUOM': 'H1', + 'ProductSizeCode': 'S-800', + 'ProductSizeDescription': 'Small', + 'ProductColorCode': 'C-999', + 'ProductColorDescription': 'Fire Truck Red', + 'ProductMaterialDescription': '', + 'NRFStandardColorAndSize': { + 'NRFColorCode': '600', + 'NRFSizeCode': '42-10651', + }, + 'PhysicalDetails': { + 'PackQualifier': 'Small', + 'PackValue': 'C-999', + 'PackSize': '', + 'PackUOM': '', + }, + 'PriceInformation': { + 'PriceTypeIDCode': 'PRP', + 'UnitPrice': '5.48', + }, + 'ProductOrItemDescription': { + 'ItemDescriptionType': '74', + 'ProductDescription': 'Super comfortable', + }, + 'Date': { + 'DateTimeQualifier1': '945', + 'Date1': ship_date, + 'Time1': ship_time, + }, + 'Reference': { + 'ReferenceQual': 'PJ', + 'ReferenceID': 'testReferenceID', + 'Description': + 'New products only. Do not reuse packaging', + }, + 'Notes':{ + 'NoteCode': 'DSCSA', + 'NoteInformationField': 'REPEAT LOGO PREVIOUS ORDER', + }, + 'ChargesAllowances': { + 'AllowChrgIndicator': 'C', + 'AllowChrgCode': 'C310', + 'AllowChrgAmt': '85.02', + 'AllowChrgPercentQual': '4', + 'AllowChrgPercent': '5.0', + 'AllowChrgHandlingCode': '02', + 'AllowChrgHandlingDescription': + 'This will cover the cost of shipping', + }, + }""" + shipment_dict.get('Shipment').update(meta_dict) + shipment_dict.get('Shipment').update(header_dict) + shipment_dict.get('Shipment').update(orderlevel_dict) + shipment_dict.get('Shipment' + ).get('OrderLevel').update(packlevel_dict) + + # Sublines + sublines_list = {'Sublines': []} + total_lines = 0 + total_qty = 0 + total_weight = 0 + + # for each sub line + for line in picking.move_lines: + total_lines += 1 + total_qty += line.product_qty + total_weight += (line.product_id.weight * line.product_qty) + subline_dict = {'Subline': {}} + pickingline_dict = {'ShipmentLine': { + 'LineSequenceNumber': int(line.id), + 'BuyerPartNumber': line.product_id.default_code or '', + 'VendorPartNumber': line.product_id.default_code or '', + 'ConsumerPackageCode': line.consumer_package_code, + 'EAN': line.product_id.barcode, + 'GTIN': line.gtin, + 'UPCCaseCode': line.upc_case_code, + 'NatlDrugCode': line.natl_drug_code, + 'InternationalStandardBookNumber': + line.international_standard_book_number, + 'ProductID': { + 'PartNumberQual': 'IT', + 'PartNumber': line.product_id.default_code, + }, + 'PurchasePrice': line.price_unit, + 'ShipQty': line.product_qty, + 'ShipQtyUOM': 'P4', + 'ProductSizeDescription': line.product_size_description, + 'ProductColorDescription': line.product_color_description, + 'ProductMaterialDescription': + line.product_material_description, + } + } + subline_dict.get('Subline').update(pickingline_dict) + sublines_list.get('Sublines').append(subline_dict) + + # shipment_dict.get('Shipment').get('OrderLevel').get('PackLevel' + # ).get('ItemLevel').update(sublines_list) + shipment_dict.get('Shipment' + ).get('OrderLevel').update(sublines_list) + + # summary + summary_dict = { + 'Summary': { + 'TotalLineItems': total_lines or '', + 'TotalQuantity': total_qty, + } + } + shipment_dict.get('Shipment').update(summary_dict) + + # update invoice + today = datetime.now().strftime(DSD) + picking.write({'sent_timestamp': today}) + shipments_list.get('Shipments').append(shipment_dict) + processed += 1 + + # convert dictionary to xml + xml = dicttoxml.dicttoxml(shipments_list, attr_type=False, root=False) + xml = xml.decode("utf-8") + xml = xml.replace('', '').replace('', '') + xml = xml.replace('', '').\ + replace('', + '' + + '') + + print("***** SUCCESSFULLY STORED *****") + # Write ASN doc to text file + name = re.findall('\d+', name)[0] + filename = '856_' + today + '%s.xml' % name + filename.replace('/', '_') + if not picking.trading_partner_id.out_path: + raise UserError('Add out path in Trading Partner') + fd = open(picking.trading_partner_id.out_path + filename, + 'w') + fd.write(xml) + fd.close() + return processed + + def _create_856_wrapper(self): + # search for invoices that are edi_yes = True + # and sent_timestamp = False + eligible_pickings = self.search([ + ('edi_yes', '=', True), + ('sent_timestamp', '=', False) + ]) + return eligible_pickings and self.create_text_856() or False + + # done as a server action + @api.multi + def action_create_text_856(self): + """ Creates and new 856, ASN and puts it into the outbox + """ + # number of orders to process + toprocess = len(self.ids) + + # process orders to write 856 + processed = self.create_text_856() + return (toprocess - processed) diff --git a/connector_spscommerce/readme/CONFIGURE.rst b/connector_spscommerce/readme/CONFIGURE.rst new file mode 100644 index 0000000..9b281a0 --- /dev/null +++ b/connector_spscommerce/readme/CONFIGURE.rst @@ -0,0 +1,8 @@ + +To configure this module, you need to add a cron job: + +`*/15 * * * * python /opt/local-addons/connector_spscommerce/edi_scripts/edi_process_in.py` + +Set the parameters appropriately before attempting EDI connection at below: + +`*/15 * * * * python /opt/local-addons/connector_spscommerce/edi_scripts/connect_info.py` \ No newline at end of file diff --git a/connector_spscommerce/readme/CONTRIBUTORS.rst b/connector_spscommerce/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..c0b6bfe --- /dev/null +++ b/connector_spscommerce/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ + +* Adam O'Connor +* Sandip Mangukiya +* Maxime Chambreuil +* Serpent Consulting Services Pvt. Ltd. +* Nikul Chaudhary + diff --git a/connector_spscommerce/readme/CREDITS.rst b/connector_spscommerce/readme/CREDITS.rst new file mode 100644 index 0000000..6fd11e3 --- /dev/null +++ b/connector_spscommerce/readme/CREDITS.rst @@ -0,0 +1,3 @@ + +* Open Source Integrators + diff --git a/connector_spscommerce/readme/DESCRIPTION.rst b/connector_spscommerce/readme/DESCRIPTION.rst new file mode 100644 index 0000000..cc848bf --- /dev/null +++ b/connector_spscommerce/readme/DESCRIPTION.rst @@ -0,0 +1,11 @@ +This module provides EDI integration using SPS Commerce (https://www.spscommerce.com). +It adds a new tree view to the Partner record for EDI settings. +EDI documents will interface with this module to pull its settings. +It adds a few flags to interact with EDI conversion scripts to import and export EDI documents. + +List of supported EDI documents: + +* 810 Customer Invoice - Export +* 850 Customer PO - Import +* 855 Order Acknowledgement - Export +* 856 Advance Shipment Notice - Export diff --git a/connector_spscommerce/readme/INSTALL.rst b/connector_spscommerce/readme/INSTALL.rst new file mode 100644 index 0000000..247501e --- /dev/null +++ b/connector_spscommerce/readme/INSTALL.rst @@ -0,0 +1,4 @@ + +To install this module, you need to: + +`$ pip install XX` \ No newline at end of file diff --git a/connector_spscommerce/readme/USAGE.rst b/connector_spscommerce/readme/USAGE.rst new file mode 100644 index 0000000..e69de29 diff --git a/connector_spscommerce/static/src/img/icon.png b/connector_spscommerce/static/src/img/icon.png new file mode 100644 index 0000000..1622a17 Binary files /dev/null and b/connector_spscommerce/static/src/img/icon.png differ diff --git a/connector_spscommerce/views/account_invoice_view.xml b/connector_spscommerce/views/account_invoice_view.xml new file mode 100644 index 0000000..fea585c --- /dev/null +++ b/connector_spscommerce/views/account_invoice_view.xml @@ -0,0 +1,76 @@ + + + + + + invoice.edi.view2 + account.invoice + form + + + + +