|
| 1 | +import datetime |
| 2 | +import logging |
| 3 | + |
| 4 | +from pydifact.segmentcollection import Interchange, Message |
| 5 | +from pydifact.segments import Segment |
| 6 | + |
| 7 | +from odoo import _, api, exceptions, models |
| 8 | + |
| 9 | +from odoo.addons.edi_party_data_oca.utils import get_party_data_component |
| 10 | + |
| 11 | +logger = logging.getLogger(__name__) |
| 12 | + |
| 13 | + |
| 14 | +# https://github.com/nerdocs/pydifact |
| 15 | +class BasePydifact(models.AbstractModel): |
| 16 | + _name = "base.edifact" |
| 17 | + _description = "Generate and parse Edifact documents" |
| 18 | + |
| 19 | + MAP_AGENCY_CODE_2_RES_PARTNER_NAMEREF = {"9": "gln"} |
| 20 | + CURRENCY_SYMBOLS = { |
| 21 | + "EUR": "€", |
| 22 | + "USD": "$", |
| 23 | + } |
| 24 | + PRODUCT_CODE_TYPES = {"EN": "EAN", "UP": "UPC", "SRV": "GTIN"} |
| 25 | + |
| 26 | + @api.model |
| 27 | + def pydifact_import(self, names): |
| 28 | + classes = { |
| 29 | + "Segment": Segment, |
| 30 | + "Message": Message, |
| 31 | + "Interchange": Interchange, |
| 32 | + } |
| 33 | + return [classes.get(name, None) for name in names] |
| 34 | + |
| 35 | + @api.model |
| 36 | + def pydifact_obj(self, docu): |
| 37 | + obj = [] |
| 38 | + interchange = self._loads_edifact(docu) |
| 39 | + header_seg = interchange.get_header_segment() |
| 40 | + header_dict = dict() |
| 41 | + header_dict[header_seg.tag] = header_seg.elements |
| 42 | + obj.append(header_dict) |
| 43 | + for message in interchange.get_messages(): |
| 44 | + segms = [] |
| 45 | + msg = { |
| 46 | + "reference_number": message.reference_number, |
| 47 | + "type": message.type, |
| 48 | + "version": message.version, |
| 49 | + "identifier": message.identifier, |
| 50 | + "HEADER_TAG": message.HEADER_TAG, |
| 51 | + "FOOTER_TAG": message.FOOTER_TAG, |
| 52 | + "characters": str(message.characters), |
| 53 | + "extra_header_elements": message.extra_header_elements, |
| 54 | + "has_una_segment": message.has_una_segment, |
| 55 | + } |
| 56 | + logger.info(message) |
| 57 | + for segment in message.segments: |
| 58 | + logger.info( |
| 59 | + "Segment tag: {}, content: {}".format(segment.tag, segment.elements) |
| 60 | + ) |
| 61 | + # segms.append((segment.tag, segment.elements)) |
| 62 | + seg = dict() |
| 63 | + seg[segment.tag] = segment.elements |
| 64 | + segms.append(seg) |
| 65 | + msg["segments"] = segms |
| 66 | + obj.append(msg) |
| 67 | + return obj |
| 68 | + |
| 69 | + @api.model |
| 70 | + def get_party_data(self, exchange_record, partner, raise_if_not_found=True): |
| 71 | + provider = get_party_data_component(exchange_record, partner) |
| 72 | + if not provider and raise_if_not_found: |
| 73 | + raise exceptions.UserError(_("No info provider found for party data")) |
| 74 | + return provider.get_party() if provider else None |
| 75 | + |
| 76 | + @api.model |
| 77 | + def _loads_edifact(self, order_file): |
| 78 | + interchange = Interchange.from_str(order_file.decode()) |
| 79 | + return interchange |
| 80 | + |
| 81 | + @api.model |
| 82 | + def _get_msg_type(self, interchange): |
| 83 | + seg = interchange.get_segment("UNH") |
| 84 | + # MSG_TYPE, EDIFACT_MSG_TYPE_RELEASE |
| 85 | + return (seg[1][0], "{}{}".format(seg[1][1], seg[1][2])) |
| 86 | + |
| 87 | + @api.model |
| 88 | + def map2odoo_date(self, dt): |
| 89 | + # '102' |
| 90 | + dtt = datetime.datetime.strptime(dt[1], "%Y%m%d") |
| 91 | + return dtt.date() |
| 92 | + |
| 93 | + @api.model |
| 94 | + def map2odoo_partner(self, seg): |
| 95 | + """ |
| 96 | + BY. Party to which merchandise is sold. |
| 97 | + NAD+BY+5550534000017::9' |
| 98 | + NAD segment: ['BY', ['5550534001649', '', '9']] |
| 99 | +
|
| 100 | + SU. Party which manufactures or otherwise has possession of |
| 101 | + goods,and consigns or makes them available in trade. |
| 102 | + NAD+SU+<Supplier GLN>::9' |
| 103 | + """ |
| 104 | + |
| 105 | + partner_dict = {} |
| 106 | + codes = ["BY", "SU"] |
| 107 | + reference_code = seg[0] |
| 108 | + if reference_code not in codes: |
| 109 | + raise NotImplementedError(f"Code '{reference_code}' not implemented") |
| 110 | + # |
| 111 | + party_identification = seg[1] |
| 112 | + party_id = party_identification[0] |
| 113 | + agency_code = party_identification[2] |
| 114 | + nameref = self.MAP_AGENCY_CODE_2_RES_PARTNER_NAMEREF.get(agency_code, "gln") |
| 115 | + partner_dict[nameref] = party_id |
| 116 | + return partner_dict |
| 117 | + |
| 118 | + @api.model |
| 119 | + def map2odoo_address(self, seg): |
| 120 | + """ |
| 121 | + DP. Party to which goods should be delivered, if not identical with |
| 122 | + consignee. |
| 123 | + NAD+DP+5550534000086::9+++++++DE' |
| 124 | + NAD segment: ['DP', ['5550534022101', '', '9'], '', '', '', '', '', '', 'ES'] |
| 125 | + IV. Party to whom an invoice is issued. |
| 126 | + NAD+IV+5450534005838::9++AMAZON EU SARL:NIEDERLASSUNG |
| 127 | + DEUTSCHLAND+MARCEL-BREUER-STR. 12+MUENCHEN++80807+DE |
| 128 | +
|
| 129 | + :returns: { |
| 130 | + 'type': |
| 131 | + 'partner': {'gln':''} |
| 132 | + 'address': {...} |
| 133 | + } |
| 134 | + """ |
| 135 | + if seg[0] not in ("DP", "IV"): |
| 136 | + return False |
| 137 | + order_type = "delivery" if seg[0] == "DP" else "invoice" |
| 138 | + address = dict(type=order_type, partner={}, address={}) |
| 139 | + # PARTY IDENTIFICATION DETAILS |
| 140 | + iden = seg[1] |
| 141 | + party_id = iden[0] |
| 142 | + agency_code = iden[2] |
| 143 | + nameref = self.MAP_AGENCY_CODE_2_RES_PARTNER_NAMEREF.get(agency_code, "gln") |
| 144 | + address["partner"][nameref] = party_id |
| 145 | + d = address["address"] |
| 146 | + # PARTY NAME |
| 147 | + if bool(seg[2]): |
| 148 | + d["name"] = seg[2] |
| 149 | + if bool(seg[3]): |
| 150 | + d["name"] = "{}{}".format(f"{d['name']}. " if d.get("name") else "", seg[3]) |
| 151 | + if bool(seg[4]): |
| 152 | + # Street address and/or PO Box number in a structured address: one to three lines. |
| 153 | + d["street"] = seg[4] |
| 154 | + if bool(seg[5]): |
| 155 | + d["city"] = seg[5] |
| 156 | + if bool(seg[6]): |
| 157 | + # Country sub-entity identification |
| 158 | + d["state_code"] = seg[6] |
| 159 | + if bool(seg[7]): |
| 160 | + d["zip"] = seg[7] |
| 161 | + if bool(seg[8]): |
| 162 | + # Country, coded ISO 3166 |
| 163 | + d["country_code"] = seg[8] |
| 164 | + |
| 165 | + return address |
| 166 | + |
| 167 | + @api.model |
| 168 | + def map2odoo_currency(self, seg): |
| 169 | + """ |
| 170 | + ['2', 'EUR', '9'] |
| 171 | + """ |
| 172 | + # Identification of the name or symbol of the monetary unit involved in the transaction. |
| 173 | + currency_coded = seg[1] |
| 174 | + return { |
| 175 | + "iso": currency_coded, |
| 176 | + "symbol": self.CURRENCY_SYMBOLS.get(currency_coded, False), |
| 177 | + } |
| 178 | + |
| 179 | + @api.model |
| 180 | + def map2odoo_product(self, seg): |
| 181 | + """ |
| 182 | + :seg: LIN segment |
| 183 | + ['1', '', ['8885583503464', 'EN']] |
| 184 | + EN. International Article Numbering Association (EAN) |
| 185 | + UP. UPC (Universal product code) |
| 186 | + SRV. GTIN |
| 187 | + """ |
| 188 | + product = seg[2] |
| 189 | + pct = product[1] |
| 190 | + return dict(code=product[0]) if pct == "SRV" else dict(barcode=product[0]) |
| 191 | + |
| 192 | + @api.model |
| 193 | + def map2odoo_qty(self, seg): |
| 194 | + """ |
| 195 | + 'QTY' EDI segment: [['21', '2']] |
| 196 | + '21'. Ordered quantity |
| 197 | + """ |
| 198 | + return float(seg[0][1]) |
| 199 | + |
| 200 | + @api.model |
| 201 | + def map2odoo_unit_price(self, seg): |
| 202 | + """ |
| 203 | + 'PRI' EDI segment: [['AAA', '19.75']] |
| 204 | + Price qualifier: |
| 205 | + * 'AAA'. Calculation net |
| 206 | + * 'AAB'. Calculation gross |
| 207 | + """ |
| 208 | + pri = seg[0] |
| 209 | + if pri[0] == "AAA": |
| 210 | + return float(pri[1]) |
| 211 | + return 0.0 |
0 commit comments