def remove(invoice, payment): """ Remove a Payment fom an Invoice :param invoice: account.invoice :param payment: account.payment :rtype: bool """ try: # ==================================================================== # # Unit Tests - Ensure Invoice is Open (Default draft) if Framework.isDebugMode() and invoice.state == 'draft': invoice.action_invoice_open() invoice.refresh() # ====================================================================# # Unit Tests => Force Journal to Allow Update Posted if Framework.isDebugMode(): payment.journal_id.update_posted = True # ====================================================================# # Cancel Payment if payment.state == "posted": payment.cancel() # ====================================================================# # Remove Payment invoice.payment_ids = [(3, payment.id, 0)] return True except Exception as exception: Framework.log().fromException(exception, False) return False
def validate(): """Validate Field Definition""" from splashpy.core.framework import Framework # Verify - Field Type is Not Empty. if not isinstance(FieldFactory.new['type'], str) or FieldFactory.new['type'].__len__() < 3: return Framework.log().error("Field type is not defined") # Verify - Field Id is Not Empty. if not isinstance(FieldFactory.new['id'], str) or FieldFactory.new['id'].__len__() < 2: return Framework.log().error("Field ID is not defined") # # Verify - Field Id No Spacial Chars. # if not isinstance(FieldFactory.new.id, str) or FieldFactory.new.id.__len__() < 2: # Framework.log().error("Field IS is not defined") # return False # Verify - Field Name is Not Empty. if not isinstance(FieldFactory.new['name'], str) or FieldFactory.new['name'].__len__() < 3: return Framework.log().error("Field name is not defined") # Verify - Field Desc is Not Empty. if not isinstance(FieldFactory.new['desc'], str) or FieldFactory.new['desc'].__len__() < 3: return Framework.log().error("Field Description is not defined") return True
def setMultilang(iso_code): """ Configure Current Field with Multilangual Options :param iso_code: str :return: void """ from splashpy.core.framework import Framework # Safety Check ==> Verify Language ISO Code if not isinstance(iso_code, str) or iso_code.__len__() < 2: return Framework.log().error( "Default Language ISO Code is Invalid") # Safety Check ==> Verify Field Type is Allowed if not FieldFactory.new['type'] in FieldFactory.__MULTILANG_TYPES__: return Framework.log().error( "This field type is not Multi-lang: " + FieldFactory.new['type']) # Default Language ==> Only Setup Language Option FieldFactory.addOption("language", iso_code) # Other Language ==> Complete Field Setup if not iso_code == FieldFactory.dfLanguage: FieldFactory.identifier(FieldFactory.new['id'] + "_" + iso_code) if FieldFactory.new['itemtype']: FieldFactory.microData( FieldFactory.new['itemtype'] + "/" + iso_code, FieldFactory.new['itemprop'])
def encodefullname(self): """ Encode First Name, Last Name & Legal Name from Buffer :return: str """ # ==================================================================== # # Get Data To Encode first = self.fullname_buffer["first"] last = self.fullname_buffer["last"] legal = self.fullname_buffer["legal"] # ==================================================================== # # Safety Warn if legal == "": legal = "Legal Name not Defined" Framework.log().warn("Legal Name not defined") # ==================================================================== # # Encode if (isinstance(first, str)) and (len(first) > 0): if (isinstance(last, str)) and (len(last) > 0): result = first + ", " + last + " - " + legal # first, last - legal else: result = first + " - " + legal # first - legal else: result = legal # legal return result
def pack(rawdata, secured=True): """Package Splash Data before Transmission""" # Complete Message with System Information rawdata['debug'] = int(Framework.isDebugMode()) rawdata['verbose'] = int(Framework.isDebugMode()) # Complete Message with Log rawdata['log'] = Framework.log().export() # Encode Message to Xml xmlString = XmlManager.to_xml(rawdata) # Verify message is a string if not isinstance(xmlString, str): logging.error("Unable to convert Data to Xml") return False # Encode using correct method if secured is True: rawMessage = Encryption.encrypt(xmlString) else: rawMessage = str(base64.b64encode(xmlString.encode()), "UTF-8") # Verify message is a string if not isinstance(rawMessage, str): logging.error("Unable to Encrypt Xml String") return False return rawMessage
def commit(splash_object, action, object_ids): """ Execute Splash Commit for this Odoo Object :return: bool """ # ====================================================================# # Try to detect User Name try: from odoo.http import request user_name = request.env.user.name except Exception: user_name = "Unknown User" # ==================================================================== # # Send Commit Notification try: # ==================================================================== # # Check if Commits Are Allowed if SettingsManager.is_no_commits(): return True # ==================================================================== # # Execute Commits with Client return OdooClient.get_client().commit( str(splash_object.name), object_ids, action, user_name, "[" + str(action).capitalize() + "]" + str(splash_object.desc) + " modified on Odoo") except Exception as exception: splashLogger = Framework.log() if splashLogger: Framework.log().fromException(exception, False) Framework.log().to_logging().clear() return False
def verify_name(object_name, index, domain, filters=[]): """ Validate Id :param object_name: int,str Id to Verify :param index: str Property Name :param domain: str Target Objects Domain :param filters: list Additional Search Filters :return: None, int """ # No Domain or Filter => Skip if not isinstance(object_name, str) or not isinstance( index, str) or not isinstance(domain, str): return None # Execute Domain Search with Filter results = http.request.env[domain].search([(index, '=ilike', object_name)] + filters) # Results Found => Ok if len(results) > 0: # More than One Result Found => Ok but Warning if len(results) > 1: war = "More than One result by name search: " war += "'" + object_name + "' Name was found " + str( len(results)) + " times" war += " on table '" + domain + "'. First value was used." Framework.log().warn(war) # Return first result return results[0].id else: return None
def touch(attribute, attr_value, is_wnva): """ Find or Create a Product Attributes Value by Code & Value :param attribute: str, product.attribute Attribute Code Name or Product Attribute :param attr_value: None, str Attribute Value :param is_wnva: bool No Variant Attribute? :return: None, product.attribute.value """ # ====================================================================# # Detect Empty Attribute Values if attr_value is None or len(str(attr_value)) < 1: return None # ====================================================================# # STR? Search or Create Attribute by Code if isinstance(attribute, str): attribute = AttributesHelper.touch(attribute, is_wnva) if attribute is None: Framework.log().error("An Error Occurred while Loading Attribute") return None # ====================================================================# # Search for Value in Attribute values = attribute.value_ids.filtered( lambda r: r.name.lower() == attr_value.lower()) if len(values) > 0: return values[0] # ====================================================================# # Crate New Value for Attribute return ValuesHelper.create(attribute, attr_value)
def load(self, object_id): """Load Odoo Object by Id""" try: # ====================================================================# # Order Fields Inputs self.order_inputs() # ====================================================================# # Load Product Variant model = self.getModel().browse([int(object_id)]) if len(model) != 1: return False # ====================================================================# # Load Product Template for template in model.product_tmpl_id: self.template = template break except Exception: from splashpy import Framework Framework.log().warn("Unable to Load Odoo Product " + str(object_id)) return False # self.debug(model, template) return model
def create(self): """ Create a New Order :return: Order Object """ # ====================================================================# # Order Fields Inputs self.order_inputs() # ====================================================================# # Init List of required Fields req_fields = self.collectRequiredFields() if req_fields is False: return False # ==================================================================== # # Pre-Setup Default Team Id req_fields = self.setup_default_team(req_fields) # ====================================================================# # Create a New Simple Order new_order = self.getModel().create(req_fields) # ====================================================================# # Safety Check - Error if new_order is None: Framework.log().error("Order creation failed") return False return new_order
def connect(self): """ Connect Splash Server, With Encryption, Just Say Hello!! :rtype: bool """ # Create Soap Client soap_client = self.__get_client() wsId, wsKey, wsHost = self.config().identifiers() # Execute Connect Request try: soap_response = soap_client.Connect(id=wsId, data=pack({"connect": True})) # Catch Potential Errors except SoapFault as fault: Framework.log().on_fault(fault) return False except Exception as exception: Framework.log().fromException(exception) return False # Decode Response connect_response = unpack(soap_response.children().children().children().__str__()) # Verify Response if connect_response is False: return False return connect_response["result"] == "1"
def ping(self): """ Ping Splash Server, No Encryption, Just Say Hello!! :rtype: bool """ # Create Soap Client soap_client = self.__get_client() wsId, wsKey, wsHost = self.config().identifiers() # Execute Ping Request try: soap_response = soap_client.Ping(id=wsId, data="test") # Catch Potential Errors except SoapFault as fault: Framework.log().on_fault(fault) return False except Exception as exception: Framework.log().fromException(exception) return False # Decode Response ping_response = unpack(soap_response.children().children().children().__str__(), False) # Verify Response if ping_response is False: return False return ping_response["result"] == "1"
def is_commit_allowed(object_type, object_ids=None, action=None): """ Check if Commit is Allowed Local Object :param object_type: str Object Type Name :param object_ids: str|int|list Object Local Id or Array of Local Id :param action: str Action Type (SPL_A_UPDATE, or SPL_A_CREATE, or SPL_A_DELETE) :rtype: bool """ # ==================================================================== # Verify if Server Mode (Soap Request) ==> No Commit Allowed if Framework.isServerMode(): return False # ==================================================================== # Verify this Object is Locked ==> No Action on this Node if isinstance(object_ids, list): for object_id in object_ids: if Framework.getObject(object_type).islocked(object_id): return False elif Framework.getObject(object_type).islocked(object_ids): return False # ==================================================================== # Verify Create Object is Locked ==> No Action on this Node if (const.__SPL_A_CREATE__ is action) and Framework.getObject(object_type).islocked(): return False # ====================================================================// # Verify if Travis Mode (PhpUnit) ==> No Commit Allowed return not SplashClient.is_travis_mode(object_type, action)
def get_main_currency_id(): try: company = http.request.env['res.company']._get_main_company().read( []) return company[0]["currency_id"][0] except Exception as exception: Framework.log().fromException(exception) return None
def add_invoice_line(invoice, line_data): """ Add a New Line to an Invoice :param invoice: account.invoice :param line_data: dict :return: account.invoice.line """ # ====================================================================# # Load Account Id from Configuration account_id = OrderLinesHelper.detect_sales_account_id(invoice) # ====================================================================# # Safety Check if account_id is None or int(account_id) <= 0: Framework.log().error( "Unable to detect Account Id, Add Invoice Line skipped.") Framework.log().error("Please check your configuration.") return None # ====================================================================# # Prepare Minimal Order Line Data req_fields = { "invoice_id": invoice.id, "account_id": account_id, "sequence": 10 + len(invoice.invoice_line_ids), } # ====================================================================# # Link to Product try: req_fields["product_id"] = int( ObjectsHelper.id(line_data["product_id"])) except: pass # ==================================================================== # # Description # Qty Invoiced for field_id in OrderLinesHelper.__generic_fields: try: req_fields[field_id] = line_data[field_id] except: pass # ====================================================================# # Unit Price try: req_fields["price_unit"] = PricesHelper.extract( line_data["price_unit"], "ht") except: pass # ====================================================================# # Create Order Line try: return http.request.env["account.invoice.line"].create(req_fields) except Exception as exception: Framework.log().error( "Unable to create Invoice Line, please check inputs.") Framework.log().fromException(exception, False) Framework.log().dump(req_fields, "New Invoice Line") return None
def getInstance(): """ Safe Access to Splash WebService Client :rtype: SplashClient """ wsId, wsKey, wsHost = Framework.config().identifiers() return SplashClient(wsId, wsKey, None, None, Framework.getClientInfo(), Framework.getServerDetails(), Framework.config())
def setPricesFields(self, field_id, field_data): # Check if Price Field... if not self.isPricesFields(field_id): return # ==================================================================== # # Extract Price try: tax_excl = float(PricesHelper.taxExcluded(field_data)) except TypeError: tax_excl = 0 # ==================================================================== # # Directs Updates of Buy Price # Directs Updates of Base Price if field_id in ["standard_price", "list_price"]: self.setSimple(field_id, tax_excl) # ==================================================================== # # Updates of Final Sell Prices elif field_id == "lst_price": self._set_final_price(tax_excl) # ==================================================================== # # Updates of Variant Final Sell Prices elif field_id == "variant_price": self._set_variant_price(tax_excl) # ==================================================================== # # Load Product Configuration if SettingsManager.is_prd_adv_taxes(): return # ==================================================================== # # Update Product Sell Taxes if field_id in ["lst_price", "list_price"]: tax_rate = PricesHelper.taxPercent(field_data) if tax_rate is not None and tax_rate > 0: tax = TaxHelper.find_by_rate(tax_rate, 'sale') if tax is None: return Framework.log().error( "Unable to Identify Tax ID for Rate " + str(tax_rate)) else: self.object.taxes_id = [(6, 0, [tax.id])] else: self.object.taxes_id = [(6, 0, [])] # ==================================================================== # # Update Product Buy Taxes if field_id == "standard_price": tax_rate = PricesHelper.taxPercent(field_data) if tax_rate is not None and tax_rate > 0: tax = TaxHelper.find_by_rate(tax_rate, 'purchase') if tax is None: return Framework.log().error( "Unable to Identify Tax ID for Rate " + str(tax_rate)) else: self.object.supplier_taxes_id = [(6, 0, [tax.id])] else: self.object.supplier_taxes_id = [(6, 0, [])]
def setContactFields(self, field_id, field_data): # ==================================================================== # # Filter on Field Id if field_id not in ["street", "zip", "city"]: return # ==================================================================== # # Safety Check - Detect Contact Type & Not Parse Fields Street, Zip & City if PartnersHelper.is_contact(self.object): Framework.log().warn("This Address is a Contact Type, Writing " + field_id + " skipped.") self._in.__delitem__(field_id) return self.setSimple(field_id, field_data)
def decrypt(data): """Decrypt Splash Messages""" # ====================================================================# # Safety Checks if not Encryption.verify(data): return False # ====================================================================# # Encrypt Data if Framework.config().method() == "AES-256-CBC": wsId, wsKey, wsHost = Framework.config().identifiers() return AESCipher(wsKey, wsId).decrypt(data) return False
def compare(price1, price2): """ Compare Two Price Array :param price1: dict :param price2: dict :return: bool """ # ==================================================================== # # Check Both Prices are valid if not PricesHelper.isValid(price1) or not PricesHelper.isValid( price2): Framework.log().error("Price Compare: Given Prices are invalid") if Framework.isDebugMode() and not PricesHelper.isValid(price1): Framework.log().dump(price1, " Price 1") if Framework.isDebugMode() and not PricesHelper.isValid(price1): Framework.log().dump(price2, " Price 2") return False # ==================================================================== # # Compare Base Price if bool(price1["base"]) != bool(price2["base"]): return False # ==================================================================== # # Compare Price Amounts return PricesHelper.compareAmounts(price1, price2)
def set_values(invoice, payment, payment_data): """ Set values of Payments Line :param invoice: account.invoice :param payment: None|account.payment :param payment_data: dict :rtype: None|int """ # ====================================================================# # Check if Payment Data are Valid if not InvoicePaymentsHelper.validate(payment_data): Framework.log().warn("Payment Data are incomplete or invalid") return None # ====================================================================# # Check if Payment Data are Modified if payment is not None and InvoicePaymentsHelper.compare( invoice, payment, payment_data): Framework.log().warn("Payments are Similar >> Update Skipped") return payment.id # ====================================================================# # Check if Invoice is Open if invoice.state != 'open' and not Framework.isDebugMode(): Framework.log().error( "Payments cannot be processed because the invoice is not open!" ) return None # ====================================================================# # Recreate Payment # ====================================================================# try: # ====================================================================# # Remove Payment Item if payment is not None: if not InvoicePaymentsHelper.remove(invoice, payment): return None # DEBUG # else: # Framework.log().warn("Payments Deleted >> "+payment.name) # ====================================================================# # Add Payment Item payment = InvoicePaymentsHelper.add(invoice, payment_data) # DEBUG # if payment is not None: # Framework.log().warn("Payments Created >> "+payment_data["name"]) except Exception as ex: # ====================================================================# # Update Failed => Line may be protected Framework.log().error(ex) return None return payment.id if payment is not None else None
def create(self): """ Create a New Address :return: Address Object """ # ====================================================================# # Order Fields Inputs self.order_inputs() # ====================================================================# # Safety Check - First Name is Required if "first" not in self._in: Framework.log().error( "No Legal Name provided, Unable to create Address") return False # ====================================================================# # Load First Name Field in Name Field self._in["name"] = self._in["first"] # ====================================================================# # Safety Check - Address Type is Required (Auto-provide if needed) if "type" not in self._in: self._in["type"] = "other" # ====================================================================# # Init List of required Fields req_fields = self.collectRequiredCoreFields() # ====================================================================# # Delete Name Field self._in.__delitem__("name") # ====================================================================# # Safety Check if req_fields.__len__() < 1: return False # ==================================================================== # # Pre-Setup Default Team Id req_fields = self.setup_default_team(req_fields) # ====================================================================# # Create a New Simple Address new_address = self.getModel().create(req_fields) # ====================================================================# # Safety Check - Error if new_address is None: Framework.log().error("Address is None") return False # ====================================================================# # Initialize Address Fullname buffer self.object = new_address self.initfullname() return new_address
def getVariantsFields(self, index, field_id): # ==================================================================== # # Check if this Variant Field... base_field_id = ListHelper.initOutput(self._out, "variants", field_id) if base_field_id is None: return # ==================================================================== # # Check if Product has Variants if not AttributesHelper.has_attr(self.object): self._in.__delitem__(index) return # ==================================================================== # # List Product Variants Ids for variant in self.object.with_context( active_test=False).product_variant_ids: # ==================================================================== # # Debug Mode => Filter Current Product From List if Framework.isDebugMode() and variant.id == self.object.id: continue # ==================================================================== # # Read Variant Data if base_field_id == "id": value = ObjectsHelper.encode("Product", str(variant.id)) elif base_field_id == "sku": value = str(variant.code) ListHelper.insert(self._out, "variants", field_id, "var-" + str(variant.id), value) self._in.__delitem__(index)
def encrypt(data): """Encrypt Splash Messages""" # ====================================================================# # Safety Checks if not Encryption.verify(data): return False # ====================================================================# # Encrypt Data if Framework.config().method() == "AES-256-CBC": logging.debug("Encrypt using AES-256-CBC Method") wsId, wsKey, wsHost = Framework.config().identifiers() return AESCipher(wsKey, wsId).encrypt(data) return False
def get_payment_code_names(): """ Get List of Available Payment Methods :return: List of Available Payment Methods :rtype: dict """ # ====================================================================# # Execute Domain Search with Filter results = [] methods = http.request.env["account.journal"].search( InvoicePaymentsHelper.__sales_types_filter, limit=50) # ====================================================================# # Parse results for method in methods: results += [ (method.name, "[%s] %s (%s)" % (method.code, method.name, method.type)) ] # ====================================================================# # Add Default Value if not Framework.isDebugMode(): results += [("Unknown", "[Unknown] Use default payment method")] return results
def validate_payments_amounts(invoice, payments): """ Check Payment Amounts ensure Invoice Can Close Strategy: Allow 0.01 error per invoice line. :param invoice: account.invoice :param payments: dict :return: bool """ # ==================================================================== # # Check if Feature is Enabled from odoo.addons.splashsync.helpers import SettingsManager if not SettingsManager.is_sales_check_payments(): return True # ==================================================================== # # Sum Received Payments... payments_total = 0 for payment_data in payments: payments_total += float( payment_data["amount"]) if InvoicePaymentsHelper.validate( payment_data) else 0 # ====================================================================# # Compute Allowed Margin margin = InvoicePaymentsHelper.__get_payment_margin(invoice) # ====================================================================# # Compare Payment Amount vs Invoice Residual if abs(float(invoice.amount_total) - float(payments_total)) < margin: return True return Framework.log().error("Payments Validation fail: " + str(payments_total) + ", expected " + str(invoice.amount_total))
def set(model, field_name, iso_lang, value): """ Set Translation for a Model Field :param model: model :param field_name: str :param iso_lang: str :param value: str :return: void """ try: TransHelper.getModel()._set_ids( model.__class__.__name__ + "," + field_name, "model", iso_lang, [model.id], value) except Exception as exception: from splashpy import Framework Framework.log().fromException(exception)
def __detect_company_id(): """ Get Requested Company Id """ # ====================================================================# # Detect Company Id from Request Query try: if "cid" in http.request.params.keys() and int( http.request.params['cid']) > 0: return int(http.request.params['cid']) if "cname" in http.request.params.keys() and len( str(http.request.params['cname'])) > 0: company = http.request.env['res.company'].sudo().name_search( str(http.request.params['cname']), limit=1) return int(company[0][0]) except Exception: pass # ====================================================================# # Detect Company Id from Logged User try: if not Framework.isServerMode( ) and http.request.env.user.company_id.id: return http.request.env.user.company_id.id except Exception: pass # ====================================================================# # Use Default Company Id return http.request.env['res.company']._get_main_company().id
def unlink_all_inventory_adjustment(product_id): """ Delete All Product Inventory Adjustment so that it could be Deleted ONLY IN DEBUG MODE :param product_id: str|int :return: void """ # ====================================================================# # Safety Check - ONLY In Debug Mode if not Framework.isDebugMode(): return # ====================================================================# # Search for All Product Inventory Adjustments results = InventoryHelper.__get_inventory().search([("product_id", "=", int(product_id))]) # ====================================================================# # FORCED DELETE of All Product Inventory Adjustments for inventory in results: for inventory_move in inventory.move_ids: inventory_move.state = 'assigned' inventory_move._action_cancel() inventory_move.unlink() inventory.state = 'draft' inventory.action_cancel_draft() inventory.unlink() # ====================================================================# # Search for All Product Quantities results = InventoryHelper.__get_quants().sudo().search([ ("product_id", "=", int(product_id)) ]) # ====================================================================# # FORCED DELETE of All Product Inventory Adjustments for quant in results: quant.unlink()
def buildFeaturesFields(self): from odoo.addons.splashsync.helpers import TransHelper # ====================================================================# # Set default System Language FieldFactory.setDefaultLanguage(TransHelper.get_default_iso()) # ====================================================================# # Walk on Available Attributes for attribute in ProductsFeatures.find_all(): # ====================================================================# # Walk on Available Languages for iso_code, lang_name in TransHelper.get_all().items(): # ==================================================================== # # Product Feature Field FieldFactory.create(const.__SPL_T_VARCHAR__, self.encode(attribute), attribute.display_name) FieldFactory.group("Features") FieldFactory.microData("http://schema.org/Product", attribute.name) # ==================================================================== # # Add Language Params FieldFactory.description("["+lang_name+"] "+attribute.display_name) FieldFactory.setMultilang(iso_code) # ==================================================================== # # Filter Variants Attributes During Tests if Framework.isDebugMode() and attribute.name in AttributesHelper.attr_test: FieldFactory.isNotTested() if iso_code != TransHelper.get_default_iso(): FieldFactory.association(self.encode(attribute))