├── README.md └── sale_order_sync ├── __init__.py ├── models ├── __init__.py └── sale_order_sync.py ├── __manifest__.py └── views └── sale_order_sync.xml /README.md: -------------------------------------------------------------------------------- 1 | # odoo-odoosync -------------------------------------------------------------------------------- /sale_order_sync/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models 2 | -------------------------------------------------------------------------------- /sale_order_sync/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import sale_order_sync 2 | -------------------------------------------------------------------------------- /sale_order_sync/__manifest__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Odoo, Open Source Enterprise Management Solution, third party addon 5 | # Copyright (C) 2021 Vertel AB (). 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################## 21 | { 22 | "name": "Sale Order Sync", 23 | "version": "14.0.1.3.0", 24 | "author": "Vertel", 25 | "category": "Sales", 26 | "description": """ 27 | 14.0.1.4.0 - Refactoring and added dependency 28 | 14.0.1.2.0 - Added sync of multiple adresses for res.partners. 29 | 14.0.1.1.0 - Added sync of res.partners. 30 | 14.0.1.0.1 - Added a call to check_order_stock 31 | Synchronizes sale orders from Odoo 14 to Odoo 8 32 | using triggers. 33 | """, 34 | "depends": ["sale", "base_automation", 'sale_commission'], 35 | "external_dependencies": { 36 | "python": ["odoorpc"], 37 | }, 38 | "data": [ 39 | "views/sale_order_sync.xml", 40 | ], 41 | "installable": True, 42 | "application": False, 43 | } 44 | -------------------------------------------------------------------------------- /sale_order_sync/views/sale_order_sync.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sale Order Sync Action 6 | 7 | 8 | 9 | code 10 | 11 | if records: 12 | records.env.context["sync_catch_exceptions"] = True 13 | records.sync_sale_order() 14 | else: 15 | raise Warning("Run this action on at least one record.") 16 | 17 | 18 | 19 | 20 | Manual Sale Order Sync Action 21 | 22 | 23 | form,list 24 | code 25 | 26 | if records: 27 | records.sync_sale_order() 28 | else: 29 | raise Warning("Run this action on at least one record.") 30 | 31 | 32 | 33 | 34 | Sync Sales Order Sync Trigger 35 | True 36 | on_write 37 | 38 | 39 | [["state","=","draft"]] 40 | [["state","=","sale"]] 41 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sale_order_sync/models/sale_order_sync.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import random 4 | import logging 5 | 6 | from odoo import api, fields, models, _ 7 | from odoo.exceptions import ValidationError 8 | 9 | _logger = logging.getLogger(__name__) 10 | try: 11 | import odoorpc 12 | except ImportError: 13 | raise Warning( 14 | "odoorpc library missing. Please install the library. Eg: pip3 install odoorpc" 15 | ) 16 | 17 | PREFIX = "__ma_import__" 18 | 19 | def get_remote_id_from_xid(xid): 20 | ''' 21 | Get remote id from synked external id. 22 | 23 | Parameters 24 | ========== 25 | xid : str 26 | External ID as used by ref(...) 27 | 28 | Returns 29 | ======= 30 | int : 31 | Extracted remote ID from synced external id. 32 | 33 | ''' 34 | if not xid.startswith(PREFIX): 35 | raise ValueError(f"External id: {xid} doesn't start with {PREFIX}") 36 | return int(xid.split('_')[-1]) 37 | 38 | def get_remote_ids_from_rs(env,recordset,remote_model=None): 39 | ''' 40 | Dev-note: Rationale for returning dict; Not all entries in the Recordset 41 | might have remote id's. 42 | 43 | Parameters 44 | ========== 45 | env : Environment 46 | Odoo Environment to use. Eg self.env, recordset.env 47 | recordset : RecordSet 48 | RecordSet to get id's from. 49 | remote_model : str 50 | (Optional) Override for recordset._name . Useful if the model different 51 | names on the remote Odoo installation. 52 | 53 | Returns 54 | ======= 55 | Dict[int->int] : 56 | Dict mapping local id's to ids on remote Odoo. Records with no remote 57 | record are ignored. 58 | ''' 59 | model = recordset._name 60 | if remote_model: 61 | model = remote_model 62 | ids = recordset.mapped('id') 63 | imd = env["ir.model.data"].search([('module',"=",PREFIX), 64 | ("model",'=',model), 65 | ("res_id","in",ids) 66 | ]) 67 | _logger.debug("O2O-sync: Recordset {} has external IDs {}".format( 68 | recordset,imd)) 69 | if not imd: 70 | return {} 71 | else: 72 | imd = imd.mapped( lambda r: (r.res_id, int(r.name.split('_')[-1])) ) 73 | idmap = { local:remote for local, remote in imd } 74 | _logger.info(f"O2O-sync: Model: {model} ID-map: {idmap}") 75 | return idmap 76 | # Shouldn't get here. 77 | return {} 78 | 79 | def get_remote_id_from_rs(env,recordset,remote_model=None): 80 | ''' 81 | Parameters 82 | ========== 83 | env : Environment 84 | Odoo Environment to use. 85 | recordset : RecordSet 86 | RecordSet of length 1 to get an id from. 87 | 88 | Returns 89 | ======= 90 | int : 91 | id on remote Odoo or None if no corresponding id is found. 92 | ''' 93 | if len(recordset) == 1: 94 | i = get_remote_ids_from_rs(env,recordset,remote_model) 95 | return i[recordset.id] if i else None 96 | else: 97 | raise ValueError("RecordSet need to be of length 1." 98 | " For longer RS us get_remote_ids_from_rs") 99 | class ResUsers(models.Model): 100 | _inherit = 'res.users' 101 | 102 | def _connect_to_host(self): 103 | try: 104 | host = self.env["ir.config_parameter"].sudo().get_param("rpc_host") 105 | port = self.env["ir.config_parameter"].sudo().get_param("rpc_port") 106 | db = self.env["ir.config_parameter"].sudo().get_param("rpc_db") 107 | user = self.env["ir.config_parameter"].sudo().get_param("rpc_user") 108 | password = self.env["ir.config_parameter"].sudo( 109 | ).get_param("rpc_password") 110 | conn = odoorpc.ODOO(host=host, port=port) 111 | conn.login(db, login=user, password=password) 112 | return conn 113 | 114 | except Exception as e: 115 | _logger.warning(f"O2O-sync: Could not connect to remote Odoo {e}") 116 | 117 | def create_external_id(self, model, partner_id, remote_id): 118 | """Expected external ID(odoo14) of remote model record(odoo8).""" 119 | ext_id_vals = { 120 | "module": PREFIX, # __ma_import__ 121 | "model": "res.partner", # Modelnamn på Odoo 14 122 | "res_id": partner_id, # ID på Odoo 14 123 | # Sträng med modelnamn i Odoo 14 och ID på Odoo 8 på formen: res_partner_41820 124 | "name": model.replace(".", '_') + "_" + str(remote_id), 125 | } 126 | 127 | self.env["ir.model.data"].create(ext_id_vals) 128 | return PREFIX + "." + model.replace(".", '_') + "_" + str(remote_id) 129 | 130 | def signup(self, values, token=None): 131 | """Connects to a remote odoo8 server and syncronize/create account""" 132 | _logger.info("O2O-sync: Syncronizing res.users...") 133 | 134 | odoo8_conn = self._connect_to_host() 135 | 136 | if odoo8_conn: 137 | db, login, password = super().signup(values=values, token=token) 138 | partner = self.env['res.users'].search([ 139 | ("login", "=", login), 140 | ]).partner_id 141 | model = self.env["ir.model.data"] 142 | # Create a new partner in target Odoo. 143 | target_country = odoo8_conn.env["res.country"].search( 144 | [("code", "=", partner.country_id.code)], limit=1 145 | ) 146 | partner_name = model.search( 147 | [("res_id", "=", partner.id), 148 | ("model", "=", "res.partner")] 149 | ).name 150 | 151 | target_partner_vals = { 152 | "name": partner.name, 153 | "type": partner.type, 154 | "mobile": partner.phone, 155 | "email": partner.email, 156 | "street": partner.street, 157 | "street2": partner.street2, 158 | "zip": partner.zip, 159 | "city": partner.city, 160 | "country_id": target_country[0] if target_country else False, 161 | "category_id": [(4, 233, 0)], # slutkonsument 162 | "lang": partner.lang, 163 | } 164 | if partner_name: 165 | partner_name = partner_name.split('_')[-1] 166 | target_partner = False 167 | 168 | target_partner = odoo8_conn.env['res.partner'].browse( 169 | int(partner_name) 170 | ) 171 | 172 | target_partner.write(target_partner_vals) 173 | if target_partner: 174 | _logger.warning("UPDATING A PARTNER: DANLOF: EKSVIC 3") 175 | #_logger.warning(f"partner is : {partner.read()}") 176 | # sync adresses for the customer 177 | # if partner.child_ids: 178 | # for adress in partner.child_ids.filtered( 179 | # lambda r: r.type in ["delivery", "invoice"] 180 | # ): 181 | # _logger.warning("UPDATING A PARTNER: DANLOF: EKSVIC 4") 182 | # target_adress_vals = { 183 | # "name": adress.name, 184 | # "type": adress.type, 185 | # "mobile": adress.phone, 186 | # "email": adress.email, 187 | # "street": adress.street, 188 | # "street2": adress.street2, 189 | # "zip": adress.zip, 190 | # "city": adress.city, 191 | # "country_id": target_country[0] 192 | # if target_country 193 | # else False, 194 | # "category_id": [(4, 233, 0)], # slutkonsument 195 | # "lang": adress.lang, 196 | # } 197 | # domain = model.search([ 198 | # ('module', '=', PREFIX), 199 | # ('res_id', '=', adress.id), 200 | # ('model', '=', 'res.partner') 201 | # ]) 202 | # types = [self.env['res.partner'].browse(item.res_id).type for item in domain] 203 | # _logger.warning(f"Types: {types} DANLOF: EKSVIC") 204 | # if adress.type in types: 205 | # _logger.warning("UPDATING A PARTNER ADRESS: DANLOF: EKSVIC") 206 | # for child in target_partner.child_ids: 207 | # if child.type == adress.type: 208 | # child.write(target_adress_vals) 209 | # else: 210 | # _logger.warning("CREATING A PARTNER ADRESS: DANLOF: EKSVIC") 211 | # target_adress_vals.update({ 212 | # "parent_id": target_partner.id, 213 | # }) 214 | # _logger.warning(f"TARGET VALS: ===== {target_adress_vals}") 215 | # adress_id = odoo8_conn.env['res.partner'].create( 216 | # target_adress_vals 217 | # ) 218 | # _logger.warning(f"EXTERNALLY CREATED ID IS: {f'res_partner_{adress_id}'}") 219 | # model.create( 220 | # { 221 | # "module": PREFIX, 222 | # "name": f"res_partner_{adress_id}", 223 | # "model": "res.partner", 224 | # "res_id": adress.id, 225 | # } 226 | # ) 227 | else: 228 | # ANONYMOUS CHECKOUT PARTNER CREATION START 229 | target_partner_id = odoo8_conn.env["res.partner"].create( 230 | target_partner_vals 231 | ) 232 | 233 | if target_partner_id: 234 | self.create_external_id( 235 | 'res.partner', partner.id, target_partner_id) 236 | else: 237 | _logger.warning(f'O2O-sync: Target Partner ID IS FALSE, VERY BAD!') 238 | 239 | # sync adresses for the customer 240 | for adress in partner.child_ids.filtered( 241 | lambda r: r.type in ["delivery", "invoice"] 242 | ): 243 | target_adress_vals = { 244 | "parent_id": target_partner_id, 245 | "name": adress.name, 246 | "type": adress.type, 247 | "mobile": adress.phone, 248 | "email": adress.email, 249 | "street": adress.street, 250 | "street2": adress.street2, 251 | "zip": adress.zip, 252 | "city": adress.city, 253 | "country_id": target_country[0] 254 | if target_country 255 | else False, 256 | "category_id": [(4, 233, 0)], # slutkonsument 257 | "lang": adress.lang, 258 | } 259 | odoo8_conn.env["res.partner"].create(target_adress_vals) 260 | # ANONYMOUS CHECKOUT PARTER CREATION END 261 | return (db, login, password) 262 | 263 | 264 | class SaleOrder(models.Model): 265 | _inherit = "sale.order" 266 | 267 | def _connect_to_host(self): 268 | try: 269 | host = self.env["ir.config_parameter"].sudo().get_param("rpc_host") 270 | port = self.env["ir.config_parameter"].sudo().get_param("rpc_port") 271 | db = self.env["ir.config_parameter"].sudo().get_param("rpc_db") 272 | user = self.env["ir.config_parameter"].sudo().get_param("rpc_user") 273 | password = self.env["ir.config_parameter"].sudo( 274 | ).get_param("rpc_password") 275 | conn = odoorpc.ODOO(host=host, port=port) 276 | conn.login(db, login=user, password=password) 277 | return conn 278 | 279 | except Exception as e: 280 | _logger.warning(f"O2O-sync: Could not connect to host. {e}") 281 | 282 | def sync_sale_order(self): 283 | action_id = hex(random.randint(1, 2**32))[2:] # For log easy-of-use 284 | _logger.info("O2O-sync: Order sync ID: {} starting - Sale Order(s): {}".format( 285 | action_id, 286 | self.mapped("name") 287 | )) 288 | self._sync_sale_order() 289 | _logger.info("O2O-sync: Order sync ID: {} processed".format(action_id)) 290 | 291 | def sale_order_on_remote(self, conn): 292 | ''' 293 | Return RecordSet of sale orders in self that are on remote. 294 | 295 | Parameters 296 | ========== 297 | conn : OdooRPC-connection 298 | Open RPC connection to remote. 299 | 300 | Returns 301 | ======= 302 | RecordSet[sale.order] : 303 | Subset of self who is found on remote 304 | ''' 305 | on_remote = self.filtered( 306 | lambda SO: bool(conn.env["sale.order"].search( 307 | [("name",'=',SO.name)]))) 308 | return on_remote 309 | 310 | def sync_sanity_check(self, conn): 311 | ''' 312 | General sanity check on self if the sync should be done with conn. 313 | 314 | Run before any actual sync functionality starts. 315 | ''' 316 | # Don't sync if records are on remote. 317 | # TODO Decide if the rest should be synced but probably not. 318 | on_remote = self.sale_order_on_remote(conn) 319 | if on_remote: 320 | raise ValidationError( 321 | "Sale Order(s) {} already on remote."\ 322 | " Sync not started".format(on_remote.mapped("name"))) 323 | # Dont sync i state is not 'done'. 324 | sync_states = ["sale", "sent", "done"] 325 | not_done = self.filtered( 326 | lambda SO: SO.state not in sync_states) 327 | if not_done: 328 | raise ValidationError( 329 | "Sale Order(s) {} not in {}."\ 330 | " Sync not started".format(not_done.mapped("name"), 331 | sync_states)) 332 | 333 | def _sync_sale_order(self): 334 | """Connects to a remote odoo server and syncronizes the sale order""" 335 | _logger.info("Syncronizing sale.order...") 336 | 337 | odoo8_conn = self._connect_to_host() 338 | 339 | if odoo8_conn : # Dealt with via sanity check: and self.state in ["sale", "sent"] 340 | # Not catching ValidationErrors cause problems if other errors occur 341 | # during shop checkout. 342 | if self.env.context.get("sync_catch_exceptions",False): 343 | _logger.debug(f"O2O-sync: Silent ValidationErrors") 344 | try: 345 | self.sync_sanity_check(odoo8_conn) 346 | except ValidationError as e: 347 | _logger.warning(f"O2O-sync: Sanity check failed: {e}") 348 | else: 349 | for r in self: 350 | r._sync_single_sale_order(odoo8_conn) 351 | else: 352 | self.sync_sanity_check(odoo8_conn) 353 | for r in self: 354 | r._sync_single_sale_order(odoo8_conn) 355 | else: 356 | _logger.info("O2O-sync: No connection or no sale order") 357 | 358 | def _sync_single_sale_order(self,target): 359 | ''' 360 | Sync one order with remote Odoo target. 361 | Sanity checks of SO and connection are expected to have been done. 362 | ''' 363 | self.ensure_one() 364 | _logger.info("O2O-sync: Syncing Sale Order: {}".format( self.name)) 365 | 366 | target_country = target.env["res.country"].search( 367 | [("code", "=", self.partner_id.country_id.code)], limit=1 368 | ) 369 | # ANONYMOUS CHECKOUT PARTER CREATION START 370 | target_partner_vals = { 371 | "name": self.partner_id.name, 372 | "type": self.partner_id.type, 373 | "mobile": self.partner_id.phone, 374 | "email": self.partner_id.email, 375 | "street": self.partner_id.street, 376 | "street2": self.partner_id.street2, 377 | "zip": self.partner_id.zip, 378 | "city": self.partner_id.city, 379 | "country_id": target_country[0] if target_country else False, 380 | "category_id": [(4, 233, 0)], # slutkonsument 381 | "lang": self.partner_id.lang, 382 | } 383 | 384 | model = self.env["ir.model.data"] 385 | pricelist_name = model.search( 386 | [ 387 | ("res_id", "=", self.pricelist_id.id), 388 | ("model", "=", "product.pricelist"), 389 | ] 390 | ).name 391 | partner_name = model.search( 392 | [("res_id", "=", self.partner_id.id), 393 | ("module","=",PREFIX), 394 | ("model", "=", "res.partner")] 395 | ).name 396 | 397 | if partner_name: 398 | partner_name = partner_name.split('_')[-1] 399 | 400 | target_partner = False 401 | 402 | target_partner = target.env['res.partner'].browse( 403 | int(partner_name) 404 | ) 405 | 406 | target_partner.write(target_partner_vals) 407 | 408 | if target_partner: 409 | # sync adresses for the customer 410 | for adress in self.partner_id.child_ids.filtered( 411 | lambda r: r.type in ["delivery", "invoice"] 412 | ): 413 | target_adress_vals = { 414 | "name": adress.name, 415 | "type": adress.type, 416 | "mobile": adress.phone, 417 | "email": adress.email, 418 | "street": adress.street, 419 | "street2": adress.street2, 420 | "zip": adress.zip, 421 | "city": adress.city, 422 | "country_id": target_country[0] 423 | if target_country 424 | else False, 425 | "category_id": [(4, 233, 0)], # slutkonsument 426 | "lang": adress.lang, 427 | } 428 | domain = model.search([ 429 | ('module', '=', PREFIX), 430 | ('res_id', '=', adress.id), 431 | ('model', '=', 'res.partner') 432 | ]) 433 | types = [self.env['res.partner'].browse(item.res_id).type for item in domain] 434 | if adress.type in types: 435 | _logger.info("O2O-sync: Creating partner record on remote.") 436 | for child in target_partner.child_ids: 437 | if child.type == adress.type: 438 | child.write(target_adress_vals) 439 | else: 440 | _logger.info("O2O-sync: Updating partner record on remote.") 441 | target_adress_vals.update({ 442 | "parent_id": target_partner.id, 443 | }) 444 | adress_id = target.env['res.partner'].create( 445 | target_adress_vals 446 | ) 447 | model.create( 448 | { 449 | "module": PREFIX, 450 | "name": f"res_partner_{adress_id}", 451 | "model": "res.partner", 452 | "res_id": adress.id, 453 | } 454 | ) 455 | target_partner = target_partner.id; 456 | 457 | if not partner_name: 458 | # No external id found for res.partner in source Odoo 459 | # -> this res.partner did not come from target Odoo. 460 | # We need to see if it exists in target Odoo and if not, create it. 461 | 462 | # Removed this as it caused problems since odoo creates duplicate 463 | # res.partners when creating orders with the same email in the webshop. 464 | # try to find a matching partner in target. 465 | # target_partner_id = odoo_conn.env["res.partner"].search( 466 | # [("email", "=", self.partner_id.email)], limit=1 467 | # ) 468 | # Added this to force the if to always enter the else 469 | target_partner = False 470 | if target_partner: 471 | target_partner = target_partner[0] 472 | else: 473 | # Create a new partner in target Odoo. 474 | target_country = target.env["res.country"].search( 475 | [("code", "=", self.partner_id.country_id.code)], limit=1 476 | ) 477 | 478 | # ANONYMOUS CHECKOUT PARTER CREATION START 479 | target_partner = target.env["res.partner"].create( 480 | target_partner_vals 481 | ) 482 | 483 | # sync adresses for the customer 484 | for adress in self.partner_id.child_ids.filtered( 485 | lambda r: r.type in ["delivery", "invoice"] 486 | ): 487 | target_adress_vals = { 488 | # "parent_id": target_partner_id, 489 | "name": adress.name, 490 | "type": adress.type, 491 | "mobile": adress.phone, 492 | "email": adress.email, 493 | "street": adress.street, 494 | "street2": adress.street2, 495 | "zip": adress.zip, 496 | "city": adress.city, 497 | "country_id": target_country[0] 498 | if target_country 499 | else False, 500 | "category_id": [(4, 233, 0)], # slutkonsument 501 | "lang": adress.lang, 502 | } 503 | created_adress = target.env["res.partner"].create( 504 | target_adress_vals) 505 | model.create( 506 | { 507 | "module": PREFIX, 508 | "name": f"res_partner_{created_adress}", 509 | "model": "res.partner", 510 | "res_id": adress.id, 511 | } 512 | ) 513 | # ANONYMOUS CHECKOUT PARTER CREATION END 514 | 515 | # Create an external id in source Odoo so that we can find 516 | # it quicker next time. 517 | model.create( 518 | { 519 | "module": PREFIX, 520 | "name": f"res_partner_{target_partner}", 521 | "model": "res.partner", 522 | "res_id": self.partner_id.id, 523 | } 524 | ) 525 | 526 | # Should exist as it either did exist to start with or got created 527 | # above 528 | partner_shipping_name = model.search( 529 | [ 530 | ("res_id", "=", self.partner_shipping_id.id ), 531 | ("module","=",PREFIX), 532 | ("model", "=", "res.partner"), 533 | ] 534 | ).name 535 | partner_invoice_name = model.search( 536 | [ 537 | ("res_id", "=", self.partner_invoice_id.id), 538 | ("module","=",PREFIX), 539 | ("model", "=", "res.partner"), 540 | ] 541 | ).name 542 | 543 | sale_order_invoice_type = target.env.ref('__invoice_type.webshop_invoice_type').id 544 | 545 | if self.partner_id.agent_ids: 546 | agent_name = model.search( 547 | [ 548 | ("res_id", "=", self.partner_id.agent_ids[0].id), 549 | ("model", "=", "res.partner"), 550 | ] 551 | ).name 552 | commission_name = model.search( 553 | [ 554 | ("res_id", "=", 555 | self.partner_id.agent_ids[0].commission_id.id), 556 | ("model", "=", "sale.commission"), 557 | ] 558 | ).name 559 | else: 560 | agent_name = False 561 | 562 | try: 563 | # Removed this code. The default pricelist form odoo 8 564 | # will be used now instead. 565 | # try: 566 | # target_pricelist_id = int(pricelist_name.split("_")[-1]) 567 | # except ValueError: 568 | # target_pricelist_id = 3 569 | if not target_partner: 570 | target_partner = int(partner_name.split("_")[-1]) 571 | target_partner_shipping_id = ( 572 | int(partner_shipping_name.split("_")[-1]) 573 | if partner_shipping_name 574 | else False 575 | ) 576 | target_partner_invoice_id = ( 577 | int(partner_invoice_name.split("_")[-1]) 578 | if partner_invoice_name 579 | else False 580 | ) 581 | if agent_name: 582 | target_agent_id = ( 583 | int(agent_name.split("_")[-1]) if agent_name else False 584 | ) 585 | target_commission_id = ( 586 | int(commission_name.split("_")[-1]) 587 | if commission_name 588 | else False 589 | ) 590 | except Exception as e: 591 | _logger.exception(e) 592 | return False 593 | 594 | # TODO: Shop try into smaller reasonable pieces. 595 | try: 596 | # 597 | sale_order_vals = { 598 | "partner_id": target_partner, 599 | "partner_invoice_id": target_partner_invoice_id, 600 | "partner_shipping_id": target_partner_shipping_id, 601 | "name": self.name, 602 | "amount_untaxed": self.amount_untaxed, 603 | "amount_tax": self.amount_tax, 604 | "amount_total": self.amount_total, 605 | "date_order": str(self.date_order), 606 | # "pricelist_id": target_pricelist_id, 607 | "carrier_id": 32, # Hardcoded for now 608 | "invoice_type_id": sale_order_invoice_type, # Hardcoded for now 609 | "picking_policy": "one", 610 | } 611 | 612 | _logger.warning(f"O2O-sync: Sale Order values: {sale_order_vals}") 613 | sale_order_id = target.env["sale.order"].create( 614 | sale_order_vals) 615 | line_ids = [] 616 | 617 | for line in self.order_line: 618 | product_id = False 619 | if line.product_id.default_code: 620 | product_id = target.env["product.product"].search( 621 | [("default_code", "=", line.product_id.default_code)], 622 | limit=1, 623 | ) 624 | if not product_id: 625 | product_name = model.search( 626 | [ 627 | ("res_id", "=", line.product_id.id), 628 | ("model", "=", "product.product"), 629 | ] 630 | ).name 631 | product_id = target.env["product.product"].search( 632 | [("id", "=", product_name.split("_")[-1])] 633 | ) 634 | elif ( 635 | line.product_id.name[0:12] == "Free Product" 636 | and line.product_id.type == "service" 637 | ): 638 | # This is a free product generated by odoo from a coupon 639 | # we translate this to the "Discount" product in target Odoo 640 | product_id = [ 641 | target.env.ref( 642 | "__export__.product_product_4963").id 643 | ] 644 | 645 | if not product_id: 646 | _logger.error( 647 | f"O2O-sync: Product does not exist in other DB {line.product_id.default_code} {line.product_id.name}" 648 | ) 649 | raise Exception 650 | 651 | line_vals = { 652 | "name": line.name, 653 | "order_id": sale_order_id, 654 | "order_partner_id": target_partner, 655 | "price_unit": line.price_unit, 656 | "price_subtotal_incl": line.price_subtotal, 657 | "product_id": product_id[0], 658 | "product_uom_qty": line.product_uom_qty, 659 | } 660 | 661 | # Sync tax if possible. 662 | # Assume only one tax rate: 663 | remote_tax_id = get_remote_id_from_rs(self.env, line.tax_id) if line.tax_id else None 664 | if remote_tax_id: 665 | _logger.info("O2O-sync: Remote tax id found." 666 | " Local->Remote : " 667 | f"{line.tax_id}->{remote_tax_id}") 668 | line_vals["tax_id"] = [(6, 0, (remote_tax_id,) )] 669 | else: 670 | _logger.warning("O2O-sync: No remote tax id found.") 671 | 672 | sale_order_line_id = target.env["sale.order.line"].create( 673 | line_vals 674 | ) 675 | line_ids.append(sale_order_line_id) 676 | 677 | sale_order = target.env["sale.order"].browse(sale_order_id) 678 | sale_order.write({"order_line": [(6, 0, line_ids)]}) 679 | 680 | # create kickback data. 681 | if agent_name: 682 | for so_line in sale_order.order_line: 683 | agent_line_vals = { 684 | "sale_line": so_line.id, 685 | "agent": target_agent_id, 686 | "commission": target_commission_id, 687 | } 688 | so_line.write({"agents": [(0, 0, agent_line_vals)]}) 689 | # Removed this else since this is the default behaviour now. 690 | # else: 691 | # _logger.warning( 692 | # "Sale order without agents data! %s" % sale_order.name 693 | # ) 694 | # Confirm the sale order in target 695 | sale_order.check_order_stock() 696 | # Use this line if we want to send email. 697 | # Currently we do not want to. 698 | # sale_order.with_context(send_email=True).action_button_confirm() 699 | sale_order.action_button_confirm() 700 | for picking_id in sale_order.picking_ids: 701 | picking_id.action_assign() 702 | picking_id.write({'ready4picking': True}) 703 | # change order to state done to indicate that it has been 704 | # transfered correctly 705 | self.state = "done" 706 | _logger.info("O2O-sync: Order sent.") 707 | 708 | except Exception as e: 709 | # Orders that failed sync are left in state "sale". 710 | sale_order.message_post(body=str(e)) 711 | # TODO add better handling 712 | _logger.error("O2O-sync: Error occurred during sync.") 713 | _logger.exception(e) 714 | --------------------------------------------------------------------------------