class ProductBrand(Model): _name = "product.brand" _string = "Brand" _fields = { "name": fields.Char("Name", required=True, search=True), "description": fields.Text("Description", search=True), "image": fields.File("Image"), "code": fields.Char("Code"), "parent_id": fields.Many2One("product.brand","Parent Brand"), "sub_brands": fields.One2Many("product.brand","parent_id","Sub Brands"), "products": fields.One2Many("product","brand_id","Products", operator="child_of"), "num_products": fields.Integer("Number of products", function="get_num_products"), "groups": fields.Many2Many("product.brand.group","Group"), } _order = "name" def get_num_products(self, ids, context={}): vals = {} for obj in self.browse(ids): nums = 0 for product in obj.products: if not product.parent_id: nums += 1 vals[obj.id] = nums return vals
class ProductGroup(Model): _name = "product.group" _string = "Product Group" _key = ["code"] _fields = { "name": fields.Char("Group Name", required=True, search=True), "code": fields.Char("Group Code", search=True), "parent_id": fields.Many2One("product.group", "Parent"), "products": fields.Many2Many("product", "Products"), "filter_products": fields.Many2Many("product", "Products", function="get_filter_products"), "image": fields.File("Image"), "company_id": fields.Many2One("company", "Company"), } _order = "name" def get_filter_products(self, ids, context={}): group_id = ids[0] cond = [["groups.id", "=", group_id], ["is_published", "=", True]] if context.get("product_filter"): cond.append(context["product_filter"]) prod_ids = get_model("product").search(cond) vals = { group_id: prod_ids, } return vals
class EmailAttach(Model): _name = "email.attach" _string = "Email Attachment" _fields = { "email_id": fields.Many2One("email.message", "Email", required=True, on_delete="cascade"), "file": fields.File("File", required=True), }
class CartLineImage(Model): _name = "ecom.cart.line.image" _fields = { "line_id": fields.Many2One("ecom.cart.line", "Line", required=True, on_delete="cascade"), "image": fields.File("Image"), "name": fields.Char("Name"), }
class ProductBrandGroup(Model): _name = "product.brand.group" _string = "Product Brand Group" _key = ["code"] _fields = { "name": fields.Char("Group Name", required=True, search=True), "code": fields.Char("Group Code", search=True), "parent_id": fields.Many2One("product.brand.group", "Parent"), "brands": fields.Many2Many("product.brand", "Product Brands"), "image": fields.File("Image"), "company_id": fields.Many2One("company","Company"), } _order = "name"
class DocumentTmpl(Model): _name = "document.tmpl" _string = "Document Template" _fields = { "file": fields.File("File"), "categ_id": fields.Many2One("document.categ", "Category", required=True, search=True), "description": fields.Text("Description", search=True), "date": fields.Date("Date", required=True, search=True), "attachments": fields.One2Many("attach", "related_id", "Attachments"), "comments": fields.One2Many("message", "related_id", "Comments"), } _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d"), }
class Attach(Model): _name = "attach" _string = "Attachment" _order = "date desc" _fields = { "date": fields.DateTime("Date", required=True, search=True), "user_id": fields.Many2One("base.user", "User", search=True), "file": fields.File("File", required=True), "related_id": fields.Reference([["document", "Document"]], "Related To"), "description": fields.Text("Description", search=True), "comments": fields.One2Many("message", "related_id", "Comments"), } _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), "user_id": lambda self, context: int(context.get("user_id")), }
class OptionValue(Model): _name = "product.custom.option.value" _fields = { "cust_opt_id": fields.Many2One("product.custom.option", "Custom Option", required=True), "name": fields.Char("Name", required=True, translate=True), "code": fields.Char("Code"), "description": fields.Text("Description"), "image": fields.File("Image"), "price": fields.Decimal("Price"), }
class ReportTemplate(Model): _name = "report.template" _string = "Report Template" _multi_company = True _fields = { "name": fields.Char("Template Name", required=True, search=True), "type": fields.Selection( [["cust_invoice", "Customer Invoice"], ["cust_credit_note", "Customer Credit Note"], ["supp_invoice", "Supplier Invoice"], ["payment", "Payment"], ["account_move", "Journal Entry"], ["sale_quot", "Quotation"], ["sale_order", "Sales Order"], ["purch_order", "Purchase Order"], ["purchase_request", "Purchase Request"], ["prod_order", "Production Order"], ["goods_receipt", "Goods Receipt"], ["goods_transfer", "Goods Transfer"], ["goods_issue", "Goods Issue"], ["pay_slip", "Pay Slip"], ["tax_detail", "Tax Detail"], ["hr_expense", "HR Expense"], ["landed_cost", "Landed Cost"], ["other", "Other"]], "Template Type", required=True, search=True), "format": fields.Selection( [["odt", "ODT (old)"], ["odt2", "ODT"], ["ods", "ODS"], ["docx", "DOCX (old)"], ["xlsx", "XLSX"], ["jrxml", "JRXML (old)"], ["jrxml2", "JRXML"], ["jsx", "JSX"]], "Template Format", required=True, search=True), "file": fields.File("Template File"), "company_id": fields.Many2One("company", "Company"), "model_id": fields.Many2One("model", "Model"), "method": fields.Char("Method"), } _defaults = { "file_type": "odt", }
class AttributeOption(Model): _name = "product.attribute.option" _fields = { "attribute_id": fields.Many2One("product.attribute", "Attribute", required=True), "name": fields.Char("Name", required=True, translate=True, size=256), "code": fields.Char("Code", required=True, size=256), "sequence": fields.Integer("Sequence", required=True), "description": fields.Text("Description"), "image": fields.File("Image"), "price": fields.Float("Price"), } _order = "sequence" _defaults = { "sequence": 0, }
class Import(Model): _name = "import.data" _transient = True _fields = { "model": fields.Char("Model", required=True), "next": fields.Char("Next"), "title": fields.Char("Title"), "file": fields.File("File to import", required=True), } def get_data(self, context={}): model = context["import_model"] m = get_model(model) title = "Import" if m._string: title += " " + m._string return { "model": model, "title": title, "next": context.get("next"), } def do_import(self, ids, context={}): obj = self.browse(ids[0]) #dbname = get_active_db() #data = open(os.path.join("static", "db", dbname, "files", obj.file), "rU", errors="replace").read() m = get_model(obj.model) #m.import_data(data) m.import_csv(obj.file) if obj.next: return { "next": { "name": obj.next, }, "flash": "Data imported successfully", }
class Product(Model): _name = "product" _key = ["name"] _order = "name" _name_field = "name" _fields = { "name": fields.Char("Name", required=True), "code": fields.Char("Code"), "description": fields.Text("Description"), "purchase_price": fields.Decimal("Purchase Price"), "sale_price": fields.Decimal("Sale Price"), "tags": fields.Many2Many("tag", "Tags"), "image": fields.File("Image"), "cost_method": fields.Selection( [["standard", "Standard Cost"], ["average", "Weighted Average"], ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"), "cost_price": fields.Decimal("Cost Price"), "stock_in_account_id": fields.Many2One("account.account", "Stock Input Account"), "stock_out_account_id": fields.Many2One("account.account", "Stock Output Account"), "uuid": fields.Char("UUID"), } _defaults = { "uuid": lambda *a: str(uuid.uuid4()), }
class CartLine(Model): _name = "ecom.cart.line" _fields = { "cart_id": fields.Many2One("ecom.cart", "Cart", required=True, on_delete="cascade"), "sequence": fields.Integer("Item No.", required=True), "product_id": fields.Many2One("product", "Product", required=True, on_delete="cascade"), "description": fields.Text("Description"), "qty": fields.Integer("Qty", required=True), "unit_price": fields.Decimal("Unit Price", required=True), "discount_percent": fields.Decimal("Discount Percent"), "discount_amount": fields.Decimal("Discount Amount"), "promotion_amount": fields.Decimal("Promotion Amount", function="_get_amount"), "amount_before_discount": fields.Decimal("Amount Before Discount", function="_get_amount", function_multi=True), "amount": fields.Decimal("Amount", function="_get_amount", function_multi=True), "special_price": fields.Decimal("Special Price", function="_get_amount", function_multi=True), "has_discount": fields.Boolean("Has Discount", function="_get_amount", function_multi=True), "image": fields.File("Image"), "images": fields.One2Many("ecom.cart.line.image", "line_id", "Images"), "ship_method_id": fields.Many2One("ship.method", "Shipping Method"), } _order = "sequence" _defaults = { "is_discounted": False, } def _get_amount(self, ids, context={}): vals = {} cart_ids = [] for obj in self.browse(ids): cart_ids.append(obj.cart_id.id) cart_ids = list(set(cart_ids)) for cart in get_model("ecom.cart").browse(cart_ids): prod_qtys = {} prom_amts = {} prom_pcts = {} for line in cart.lines: prod_qtys.setdefault(line.product_id.id, 0) prod_qtys[line.product_id.id] += line.qty for prom in cart.used_promotions: if prom.amount and prom.product_id: prom_amts.setdefault(prom.product_id.id, 0) prom_amts[prom.product_id.id] += prom.amount elif prom.percent: prom_pcts.setdefault(prom.product_id.id, 0) prom_pcts[prom.product_id.id] += prom.percent for line in cart.lines: amt_before_disc = line.qty * line.unit_price amt = amt_before_disc has_disc = False if line.discount_percent: amt -= amt_before_disc * line.discount_percent / 100 has_disc = True if line.discount_amount: amt -= line.discount_amount has_disc = True amt_before_prom = amt prom_amt = prom_amts.get( line.product_id.id, Decimal(0)) / prod_qtys[line.product_id.id] * line.qty prom_pct = prom_pcts.get(line.product_id.id, Decimal(0)) + prom_pcts.get(None, 0) if prom_pct: prom_amt += math.ceil( amt_before_prom / line.qty * prom_pct / 100) * line.qty if prom_amt: amt -= prom_amt has_disc = True special_price = amt / line.qty if line.qty else None if line.id in ids: vals[line.id] = { "promotion_amount": prom_amt, "amount_before_discount": amt_before_disc, "amount": amt, "special_price": special_price, "has_discount": has_disc, } return vals def change_qty(self, ids, qty): print("CartLine.change_qty", ids, qty) obj = self.browse(ids)[0] if qty == obj.qty: return if qty > 0: disc_amt = obj.discount_amount * qty / obj.qty obj.write({"qty": qty, "discount_amount": disc_amt}) else: obj.delete() cart = obj.cart_id cart.update_promotions()
class ProductCateg(Model): _name = "product.categ" _string = "Product Category" _export_name_field = "code" _fields = { "name": fields.Char("Name", required=True, search=True), "code": fields.Char("Short Code", search=True), "parent_id": fields.Many2One("product.categ", "Parent Category"), "description": fields.Text("Description"), "comments": fields.One2Many("message", "related_id", "Comments"), "products": fields.One2Many("product", "categ_id", "Products", operator="child_of", condition=[["is_published", "=", True]]), "image": fields.File("Image"), "sub_categories": fields.One2Many("product.categ", "parent_id", "Sub Categories"), "num_products": fields.Integer("Number of products", function="get_num_products"), "gross_profit": fields.Decimal("Gross Profit (%)"), "sale_account_id": fields.Many2One("account.account", "Sales Account", multi_company=True), "sale_tax_id": fields.Many2One("account.tax.rate", "Sales Tax"), "purchase_account_id": fields.Many2One("account.account", "Purchase Account", multi_company=True), "purchase_tax_id": fields.Many2One("account.tax.rate", "Purchase Tax"), "cost_method": fields.Selection( [["standard", "Standard Cost"], ["average", "Weighted Average"], ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"), "cogs_account_id": fields.Many2One("account.account", "Cost Of Goods Sold Account", multi_company=True), "stock_account_id": fields.Many2One("account.account", "Inventory Account", multi_company=True), } _order = "name" _constraints = ["_check_cycle"] def get_full_parent_name(self, obj_id): obj = self.browse(obj_id) full = [obj.name] while obj.parent_id: obj = obj.parent_id full.append(obj.name) name = "/".join(full[::-1]) return name def get_num_products(self, ids, context={}): vals = {} for obj in self.browse(ids): nums = 0 for product in obj.products: if not product.parent_id: nums += 1 vals[obj.id] = nums return vals def name_get(self, ids, context={}): if not access.check_permission(self._name, "read", ids): return [(id, "Permission denied") for id in ids] f_name = self._name_field or "name" f_image = self._image_field or "image" if f_image in self._fields: show_image = True fields = [f_name, f_image] else: show_image = False fields = [f_name] res = self.read(ids, fields) for r in res: r[f_name] = self.get_full_parent_name(r["id"]) if show_image: return [(r["id"], r[f_name], r[f_image]) for r in res] else: return [(r["id"], r[f_name]) for r in res] def update_sale_prices(self, ids, context={}): obj = self.browse(ids[0]) if not obj.gross_profit: raise Exception("Missing gross profit") n = 0 for prod in get_model("product").search_browse( [["categ_id", "=", obj.id]]): sale_price = round(prod.landed_cost / (1 - obj.gross_profit / 100), 2) prod.write({ "gross_profit": obj.gross_profit, "sale_price": sale_price }) n += 1 return { "flash": "%d products updated" % n, }
class Expense(Model): _name = "account.expense" _name_field = "ref" _fields = { "contact_id": fields.Many2One("contact", "Contact", required=True), "date": fields.Date("Date", required=True), "ref": fields.Char("Reference", required=True), "attach": fields.File("Attachment"), "tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]], "Tax Type", required=True), "lines": fields.One2Many("account.expense.line", "expense_id", "Lines"), "amount_subtotal": fields.Decimal("Subtotal", function="get_amount", function_multi=True), "amount_tax": fields.Decimal("Tax Amount", function="get_amount", function_multi=True), "amount_total": fields.Decimal("Total", function="get_amount", function_multi=True), "claim_id": fields.Many2One("account.claim", "Claim", on_delete="cascade"), "user_id": fields.Many2One("base.user", "Receipt Owner", required=True), "state": fields.Selection( [["draft", "Draft"], ["waiting_approval", "Waiting Approval"], ["approved", "Approved"], ["declined", "Declined"]], "Status", required=True), "uuid": fields.Char("UUID"), } _order = "date desc,id desc" _defaults = { "tax_type": "tax_in", "uuid": lambda *a: str(uuid.uuid4()), "user_id": lambda self, context: int(context["user_id"]), "state": "draft", } def write(self, ids, vals, **kw): claim_ids = [] for obj in self.browse(ids): if obj.claim_id: claim_ids.append(obj.claim_id.id) super().write(ids, vals, **kw) claim_id = vals.get("claim_id") if claim_id: claim_ids.append(claim_id) self.function_store(ids) if claim_ids: get_model("account.claim").function_store(claim_ids) def delete(self, ids, **kw): claim_ids = [] for obj in self.browse(ids): if obj.claim_id: claim_ids.append(obj.claim_id.id) super().delete(ids, **kw) if claim_ids: get_model("account.claim").function_store(claim_ids) def get_amount(self, ids, context={}): # XXX: taxes res = {} for obj in self.browse(ids): vals = {} subtotal = 0 for line in obj.lines: subtotal += line.amount vals["amount_subtotal"] = subtotal vals["amount_tax"] = 0 vals["amount_total"] = subtotal res[obj.id] = vals return res def update_amounts(self, context): data = context["data"] data["amount_subtotal"] = 0 data["amount_tax"] = 0 tax_type = data["tax_type"] for line in data["lines"]: if not line: continue amt = line.get("qty", 0) * line.get("unit_price", 0) line["amount"] = amt tax_id = line.get("tax_id") if tax_id: tax = get_model("account.tax.rate").compute_tax( tax_id, amt, tax_type=tax_type) data["amount_tax"] += tax else: tax = 0 if tax_type == "tax_in": data["amount_subtotal"] += amt - tax else: data["amount_subtotal"] += amt data["amount_total"] = data["amount_subtotal"] + data["amount_tax"] return data def do_submit(self, ids, context={}): user_id = None for obj in self.browse(ids): if user_id is None: user_id = obj.user_id.id else: assert user_id == obj.user_id.id, "Expenses belong to different users" vals = { "user_id": user_id, } claim_id = get_model("account.claim").create(vals) self.write(ids, {"claim_id": claim_id, "state": "waiting_approval"}) return {"next": {"name": "claim_waiting_approval"}} def do_approve(self, ids, context={}): claim_id = None for obj in self.browse(ids): obj.write({"state": "approved"}) claim_id = obj.claim_id.id return { "next": { "name": "claim_edit", "active_id": claim_id, } } def do_decline(self, ids, context={}): claim_id = None for obj in self.browse(ids): obj.write({"state": "declined"}) claim_id = obj.claim_id.id return { "next": { "name": "claim_edit", "active_id": claim_id, } } def onchange_account(self, context): data = context["data"] path = context["path"] line = get_data_path(data, path, parent=True) acc_id = line.get("account_id") if not acc_id: return {} acc = get_model("account.account").browse(acc_id) line["tax_id"] = acc.tax_id.id data = self.update_amounts(context) return data
class ImportAttendance(Model): _name = "hr.import.attendance" _string = "Import Attendance" _fields = { "import_type": fields.Selection([["manual", "Manual"], ["auto", "Auto"]], "Import From", required=True), "file": fields.File("CSV File", required=False), "machine_id": fields.Many2One("hr.attendance.config", "Machine Config"), "encoding": fields.Selection([["utf-8", "UTF-8"], ["tis-620", "TIS-620"]], "Encoding", required=True), "date": fields.Date("Date From"), "date_fmt": fields.Char("Date Format"), "comments": fields.One2Many("message", "related_id", "Comments"), } _order = "time desc" _defaults = { "import_type": "manual", "encoding": "utf-8", 'date_fmt': '%Y-%m-%d %H:%M:%S' } def get_data(self, ids, context={}): machine = 1 if context: if "machine_id" in context: machine = context["machine_id"] obj = get_model("hr.attendance.config").browse(machine) auth_handler = urllib.request.HTTPBasicAuthHandler() auth_handler.add_password(realm='Basic Authentication Application', uri="http://" + obj.ip_address, user=obj.user, passwd=obj.password) opener = urllib.request.build_opener(auth_handler) urllib.request.install_opener(opener) urllib.request.urlopen('http://' + obj.ip_address) post_data = { 'uid': 'extlog.dat' } # POST data for download attendance log conn = http.client.HTTPConnection(obj.ip_address) params = urllib.parse.urlencode(post_data) headers = { 'Content-type': "application/x-www-form-urlencoded", 'Accept': 'text/plain' } conn.request('POST', obj.url_download, params, headers) response = conn.getresponse() log = response.read() res = log.decode('tis-620') lines = str(res).split('\r\n') if conn: conn.close() return lines def import_auto(self, ids, context={}): obj = self.browse(ids)[0] date = context.get('date', datetime.today()) lines = obj.get_data(context=context) current_date = datetime.today() date_in = date.strftime("%Y-%m-%d 00:00:00") date_out = current_date.strftime("%Y-%m-%d 23:59:59") count = 0 detail = [] st = "" for line in lines: if line: st += line.replace("\t", ",") st += '\n' line = line.split('\t') if len(line) > 1: if line[1] >= date_in and line[1] <= date_out: attendance_id = get_model("hr.employee").search( [["attendance_id", "=", line[0]]]) dt_time = datetime.strptime(str(line[1]), "%Y-%m-%d %H:%M:%S") dt_in = dt_time.strftime("%Y-%m-%d 00:00:00") dt_out = dt_time.strftime("%Y-%m-%d 23:59:59") if attendance_id: employee = get_model("hr.employee").browse( attendance_id)[0] have = [] have = get_model("hr.attendance").search( [["employee_id", "=", employee.id], ["time", "=", line[1]]]) check = False if not have: bf_id = [] bf_id = get_model("hr.attendance").search( [["employee_id", "=", employee.id], ["time", ">=", dt_in], ["time", "<=", dt_out], ["time", "<", line[1]]]) if not bf_id: action = "sign_in" elif bf_id: attend = get_model("hr.attendance").browse( bf_id)[0] date_get = datetime.strptime( attend.time, "%Y-%m-%d %H:%M:%S") dt = date_get + timedelta(minutes=1) date_check = datetime.strptime( str(dt), "%Y-%m-%d %H:%M:%S").strftime( "%Y-%m-%d %H:%M:%S") if line[1] <= date_check: check = True if attend.action == "sign_out": action = "sign_in" elif attend.action == "sign_in": action = "sign_out" if check is False: count += 1 detail.append({ "name": employee.first_name + " " + employee.last_name, "date": line[1], "action": action }) vals = { "employee_id": employee.id, "time": line[1], "action": action, } get_model("hr.attendance").create(vals) audit_log("Add attendance %s record at %s employee: %s " % (count, date, str(detail))) open("/tmp/res.csv", "w").write(st) # XXX return count def _import_data(self, ids, context={}): obj = self.browse(ids[0]) count = 0 if obj.import_type == 'auto': if not obj.machine_id: raise Exception("Select device to import from") context = ({"machine_id": obj.machine_id.id, "date": obj.date}) count = self.import_auto(ids, context=context) else: if not obj.file: raise Exception("Please give csv file for import data") dbname = get_active_db() data = open( os.path.join("static", "db", dbname, "files", obj.file), "rb").read().decode(obj.encoding) found_delim = False for delim in (",", ";", "\t"): try: try: rd = csv.reader(StringIO(data), delimiter=delim) except: raise Exception("Invalid CSV file") headers = next(rd) headers = [h.strip() for h in headers] for h in ["id", "date"]: if not h in headers: raise Exception("Missing header: '%s'" % h) found_delim = True break except: pass if not found_delim: raise Exception("Failed to open CSV file") rows = [r for r in rd] if not rows: raise Exception("Statement is empty") formats = [ "%Y-%m-%d %H:%M:%S", "%d/%m/%Y %H:%M:%S", "%m/%d/%Y %H:%M:%S", "%d/%m/%y %H:%M:%S", "%m/%d/%y %H:%M:%S" ] date_fmt = None for fmt in formats: fmt_ok = True for row in rows: vals = dict(zip(headers, row)) date = vals["date"].strip() if not date: continue try: datetime.strptime(date, fmt) except: fmt_ok = False break if fmt_ok: date_fmt = fmt break if not date_fmt: raise Exception("Could not detect date format") for i, row in enumerate(rows): vals = dict(zip(headers, row)) try: date = vals["date"].strip() if not date: raise Exception("Missing date") date = datetime.strptime( date, date_fmt).strftime("%Y-%m-%d %H:%M:%S") date_in = datetime.strptime( date, date_fmt).strftime("%Y-%m-%d 00:00:00") date_out = datetime.strptime( date, date_fmt).strftime("%Y-%m-%d 23:59:59") id_employee = vals["id"].strip().replace(",", "") if not id_employee: raise Exception("missing employeeid") attendance_id = get_model("hr.employee").search( [["attendance_id", "=", id_employee]]) if attendance_id: employee = get_model("hr.employee").browse( attendance_id)[0] have_id = [] have_id = get_model("hr.attendance").search( [["employee_id", "=", employee.id], ["time", "=", date]]) check = False if not have_id: bf_id = [] bf_id = get_model("hr.attendance").search( [["employee_id", "=", employee.id], ["time", ">=", date_in], ["time", "<=", date_out], ["time", "<", date]]) if not bf_id: action = "sign_in" elif bf_id: attend = get_model("hr.attendance").browse( bf_id)[0] date_get = datetime.strptime( attend.time, date_fmt) dt = date_get + timedelta(minutes=1) date_check = datetime.strptime( str(dt), date_fmt).strftime("%Y-%m-%d %H:%M:%S") if date <= date_check: check = True if attend.action == "sign_out": action = "sign_in" elif attend.action == "sign_in": action = "sign_out" if check is False: count += 1 vals = { "time": date, "employee_id": employee.id, "action": action, } get_model("hr.attendance").create(vals) except Exception as e: audit_log("Failed to get attendance orders", details=e) raise Exception("Error on line %d (%s)" % (i + 2, e)) return { "next": { "name": "attend", "mode": "list", }, "flash": "Import : %s records" % (count) } def set_att(self, ids, att_ids, context={}): index = 0 for emp in get_model("hr.employee").search_browse( [['work_status', '=', 'working']]): if index > len(att_ids) - 1: att_ids.append(index) # XXX generate our-self emp.write({ 'attendance_id': att_ids[index], }) print("update %s -> %s" % (emp.code, att_ids[index])) index += 1 print("Done!") def import_data(self, ids, context={}): obj = self.browse(ids)[0] if obj.import_type == 'auto': obj.import_auto() else: if not obj.file: raise Exception("File not found") if obj.file.split(".")[-1] != 'csv': raise Exception("Wrong File") fpath = get_file_path(obj.file) data = open(fpath, "r").read().split("\n") att_ids = [] records = {} for row in data: lines = row.split(",") if not lines: continue size = len(lines) if size < 2: continue if size > 2: raise Exception("Wrong File") att_id = lines[0] att_date = lines[1] if not records.get(att_id): records[att_id] = [] records[att_id].append(att_date) continue # TODO Check format date if att_id not in att_ids: att_ids.append(att_id) # self.set_att(ids,att_ids,context=context) emps = { emp['attendance_id']: emp['id'] for emp in get_model("hr.employee").search_read( [], ['attendance_id']) } att = get_model("hr.attendance") at_ids = att.search([]) # XXX testing att.delete(at_ids) for att_id, lines in records.items(): att_id = int(att_id) date_list = [] for line in lines: datetime = line date = datetime.split(" ")[0] action = 'sign_in' if date in date_list: action = 'sign_out' # FIXME find the last record and overwrite time date_list.append(date) att.create({ 'employee_id': emps[att_id], 'action': action, 'time': datetime, }) print("Done!")
class Invoice(Model): _name = "account.invoice" _string = "Invoice" _audit_log = True _key = ["company_id", "number"] _name_field = "number" _multi_company = True _fields = { "type": fields.Selection([["out", "Receivable"], ["in", "Payable"]], "Type", required=True), "inv_type": fields.Selection([["invoice", "Invoice"], ["credit", "Credit Note"], ["debit", "Debit Note"]], "Subtype", required=True, search=True), "number": fields.Char("Number", search=True), "ref": fields.Char("Ref", size=256, search=True), "memo": fields.Char("Memo", size=1024, search=True), "contact_id": fields.Many2One("contact", "Contact", required=True, search=True), "contact_credit": fields.Decimal("Outstanding Credit", function="get_contact_credit"), "account_id": fields.Many2One("account.account", "Account"), "date": fields.Date("Date", required=True, search=True), "due_date": fields.Date("Due Date", search=True), "currency_id": fields.Many2One("currency", "Currency", required=True, search=True), "tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]], "Tax Type", required=True), "state": fields.Selection([("draft", "Draft"), ("waiting_approval", "Waiting Approval"), ("waiting_payment", "Waiting Payment"), ("paid", "Paid"), ("voided", "Voided")], "Status", function="get_state", store=True, function_order=20, search=True), "lines": fields.One2Many("account.invoice.line", "invoice_id", "Lines"), "amount_subtotal": fields.Decimal("Subtotal", function="get_amount", function_multi=True, store=True), "amount_tax": fields.Decimal("Tax Amount", function="get_amount", function_multi=True, store=True), "amount_total": fields.Decimal("Total", function="get_amount", function_multi=True, store=True), "amount_paid": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True), "amount_due": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True), "amount_credit_total": fields.Decimal("Total Credit", function="get_amount", function_multi=True, store=True), "amount_credit_remain": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True), "amount_total_cur": fields.Decimal("Total Amount", function="get_amount", function_multi=True, store=True), "amount_due_cur": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True), "amount_paid_cur": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True), "amount_credit_remain_cur": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True), "amount_rounding": fields.Decimal("Rounding", function="get_amount", function_multi=True, store=True), "qty_total": fields.Decimal("Total Quantity", function="get_qty_total"), "attachment": fields.File("Attachment"), "payments": fields.One2Many("account.payment.line", "invoice_id", "Payments", condition=[["payment_id.state", "=", "posted"]]), # XXX: deprecated "move_id": fields.Many2One("account.move", "Journal Entry"), "reconcile_move_line_id": fields.Many2One("account.move.line", "Reconcile Item"), # XXX: deprecated "credit_alloc": fields.One2Many("account.credit.alloc", "credit_id", "Credit Allocation"), # XXX: deprecated "credit_notes": fields.One2Many("account.credit.alloc", "invoice_id", "Credit Notes"), # XXX: deprecated "currency_rate": fields.Decimal("Currency Rate", scale=6), "payment_id": fields.Many2One("account.payment", "Payment"), "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"], ["production.order","Production Order"], ["project", "Project"], ["job", "Service Order"], ["service.contract", "Service Contract"]], "Related To"), "company_id": fields.Many2One("company", "Company"), "amount_discount": fields.Decimal("Discount", function="get_discount"), "bill_address_id": fields.Many2One("address", "Billing Address"), "comments": fields.One2Many("message", "related_id", "Comments"), "documents": fields.One2Many("document", "related_id", "Documents"), "fixed_assets": fields.One2Many("account.fixed.asset", "invoice_id", "Fixed Assets"), "tax_no": fields.Char("Tax No."), "tax_branch_no": fields.Char("Tax Branch No."), "pay_method_id": fields.Many2One("payment.method", "Payment Method"), "journal_id": fields.Many2One("account.journal", "Journal"), "sequence_id": fields.Many2One("sequence", "Sequence"), "original_invoice_id": fields.Many2One("account.invoice", "Original Invoice"), "product_id": fields.Many2One("product","Product",store=False,function_search="search_product",search=True), "taxes": fields.One2Many("account.invoice.tax","invoice_id","Taxes"), "agg_amount_total": fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]), "agg_amount_subtotal": fields.Decimal("Total Amount w/o Tax", agg_function=["sum", "amount_subtotal"]), "year": fields.Char("Year", sql_function=["year", "date"]), "quarter": fields.Char("Quarter", sql_function=["quarter", "date"]), "month": fields.Char("Month", sql_function=["month", "date"]), "week": fields.Char("Week", sql_function=["week", "date"]), "transaction_no": fields.Char("Transaction ID",search=True), "payment_entries": fields.One2Many("account.move.line",None,"Payment Entries",function="get_payment_entries"), "journal_date": fields.Date("Journal Date"), } _order = "date desc,number desc" def _get_currency(self, context={}): settings = get_model("settings").browse(1) return settings.currency_id.id def _get_number(self, context={}): defaults = context.get("defaults") if defaults: # XXX type = defaults.get("type") inv_type = defaults.get("inv_type") else: type = context.get("type") inv_type = context.get("inv_type") seq_id = context.get("sequence_id") if not seq_id: seq_type = None if type == "out": if inv_type in ("invoice", "prepay"): seq_type = "cust_invoice" elif inv_type == "credit": seq_type = "cust_credit" elif inv_type == "debit": seq_type = "cust_debit" elif type == "in": if inv_type in ("invoice", "prepay"): seq_type = "supp_invoice" elif inv_type == "credit": seq_type = "supp_credit" elif inv_type == "debit": seq_type = "supp_debit" if not seq_type: return seq_id = get_model("sequence").find_sequence(type=seq_type) if not seq_id: return None while 1: num = get_model("sequence").get_next_number(seq_id, context=context) res = self.search([["number", "=", num]]) if not res: return num get_model("sequence").increment_number(seq_id, context=context) _defaults = { "state": "draft", "currency_id": _get_currency, "tax_type": "tax_ex", "number": _get_number, "date": lambda *a: time.strftime("%Y-%m-%d"), "company_id": lambda *a: get_active_company(), } _constraints = ["check_fields"] def search_product(self, clause, context={}): product_id = clause[2] product = get_model("product").browse(product_id) product_ids = [product_id] for var in product.variants: product_ids.append(var.id) for comp in product.components: product_ids.append(comp.component_id.id) invoice_ids = [] for line in get_model("account.invoice.line").search_browse([["product_id","in",product_ids]]): invoice_ids.append(line.invoice_id.id) cond = [["id","in",invoice_ids]] return cond def check_fields(self, ids, context={}): for obj in self.browse(ids): if obj.state in ("waiting_approval", "waiting_payment"): if obj.inv_type == "invoice": if not obj.due_date: raise Exception("Missing due date") # if not obj.lines: # XXX: in myob, lines can be empty... # raise Exception("Lines are empty") def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): name = obj.number if not name: if obj.inv_type == "invoice": name = "Invoice" elif obj.inv_type == "credit": name = "Credit Note" elif obj.inv_type == "prepay": name = "Prepayment" elif obj.inv_type == "overpay": name = "Overpayment" if obj.ref: name += " [%s]" % obj.ref vals.append((obj.id, name)) return vals def create(self, vals, context={}): id = super(Invoice, self).create(vals, context=context) self.function_store([id]) return id def write(self, ids, vals, **kw): super(Invoice, self).write(ids, vals, **kw) self.function_store(ids) sale_ids = [] purch_ids = [] for inv in self.browse(ids): for line in inv.lines: if line.sale_id: sale_ids.append(line.sale_id.id) if line.purch_id: purch_ids.append(line.purch_id.id) if sale_ids: get_model("sale.order").function_store(sale_ids) if purch_ids: get_model("purchase.order").function_store(purch_ids) def delete(self, ids, context={}): sale_ids = [] purch_ids = [] for inv in self.browse(ids): if inv.inv_type not in ("prepay", "overpay"): if inv.state not in ("draft", "waiting_approval", "voided"): raise Exception("Can't delete invoice with this status") for line in inv.lines: if line.sale_id: sale_ids.append(line.sale_id.id) if line.purch_id: purch_ids.append(line.purch_id.id) super(Invoice, self).delete(ids, context=context) if sale_ids: get_model("sale.order").function_store(sale_ids) if purch_ids: get_model("purchase.order").function_store(purch_ids) def function_store(self, ids, field_names=None, context={}): super().function_store(ids, field_names, context) sale_ids = [] purch_ids = [] for obj in self.browse(ids): for line in obj.lines: if line.sale_id: sale_ids.append(line.sale_id.id) if line.purch_id: purch_ids.append(line.purch_id.id) if sale_ids: get_model("sale.order").function_store(sale_ids) if purch_ids: get_model("purchase.order").function_store(purch_ids) def submit_for_approval(self, ids, context={}): for obj in self.browse(ids): if obj.state != "draft": raise Exception("Invalid state") obj.write({"state": "waiting_approval"}) self.trigger(ids, "submit_for_approval") return { "flash": "Invoice submitted for approval." } def approve(self, ids, context={}): obj = self.browse(ids)[0] if obj.state not in ("draft", "waiting_approval"): raise Exception("Invalid state") obj.post() if obj.inv_type == "invoice": msg = "Invoice approved." if obj.type == "in": obj.create_fixed_assets() elif obj.inv_type == "credit": msg = "Credit note approved." elif obj.inv_type == "debit": msg = "Debit note approved." return { "flash": msg, } def calc_taxes(self,ids,context={}): obj=self.browse(ids[0]) obj.taxes.delete() settings = get_model("settings").browse(1) if obj.currency_rate: currency_rate = obj.currency_rate else: if obj.currency_id.id == settings.currency_id.id: currency_rate = 1 else: rate_from = obj.currency_id.get_rate(date=obj.date) if not rate_from: raise Exception("Missing currency rate for %s" % obj.currency_id.code) rate_to = settings.currency_id.get_rate(date=obj.date) if not rate_to: raise Exception("Missing currency rate for %s" % settings.currency_id.code) currency_rate = rate_from / rate_to obj.write({"currency_rate": currency_rate}) taxes = {} tax_nos = [] total_amt = 0 total_base = 0 total_tax = 0 for line in obj.lines: cur_amt = get_model("currency").convert( line.amount, obj.currency_id.id, settings.currency_id.id, rate=currency_rate) tax_id = line.tax_id if tax_id and obj.tax_type != "no_tax": base_amt = get_model("account.tax.rate").compute_base(tax_id, cur_amt, tax_type=obj.tax_type) if settings.rounding_account_id: base_amt=get_model("currency").round(obj.currency_id.id,base_amt) tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice") for comp_id, tax_amt in tax_comps.items(): tax_vals = taxes.setdefault(comp_id, {"tax_amt": 0, "base_amt": 0}) tax_vals["tax_amt"] += tax_amt tax_vals["base_amt"] += base_amt else: base_amt = cur_amt for comp_id, tax_vals in taxes.items(): comp = get_model("account.tax.component").browse(comp_id) acc_id = comp.account_id.id if not acc_id: raise Exception("Missing account for tax component %s" % comp.name) vals = { "invoice_id": obj.id, "tax_comp_id": comp_id, "base_amount": get_model("currency").round(obj.currency_id.id,tax_vals["base_amt"]), "tax_amount": get_model("currency").round(obj.currency_id.id,tax_vals["tax_amt"]), } if comp.type in ("vat", "vat_exempt"): if obj.type == "out": if obj.tax_no: tax_no = obj.tax_no else: tax_no = self.gen_tax_no(exclude=tax_nos, context={"date": obj.date}) tax_nos.append(tax_no) obj.write({"tax_no": tax_no}) vals["tax_no"] = tax_no elif obj.type == "in": vals["tax_no"] = obj.tax_no get_model("account.invoice.tax").create(vals) def post(self, ids, context={}): t0 = time.time() settings = get_model("settings").browse(1) for obj in self.browse(ids): if obj.related_id: for line in obj.lines: if not line.related_id: line.write({"related_id":"%s,%d"%(obj.related_id._model,obj.related_id.id)}) obj.check_related() #if obj.amount_total == 0: # raise Exception("Invoice total is zero") # need in some cases if obj.amount_total < 0: raise Exception("Invoice total is negative") if not obj.taxes: obj.calc_taxes() obj=obj.browse()[0] contact = obj.contact_id if obj.type == "out": account_id = contact.account_receivable_id.id or settings.account_receivable_id.id if not account_id: raise Exception("Account receivable not found") elif obj.type == "in": account_id = contact.account_payable_id.id or settings.account_payable_id.id if not account_id: raise Exception("Account payable not found") account=get_model("account.account").browse(account_id) if account.currency_id.id!=obj.currency_id.id: raise Exception("Currency of accounts %s is different than invoice (%s / %s)"%(account.code,account.currency_id.code,obj.currency_id.code)) sign = obj.type == "out" and 1 or -1 if obj.inv_type == "credit": sign *= -1 obj.write({"account_id": account_id}) if obj.type == "out": desc = "Sale; " + contact.name elif obj.type == "in": desc = "Purchase; " + contact.name if obj.type == "out": journal_id = obj.journal_id.id or settings.sale_journal_id.id if not journal_id: raise Exception("Sales journal not found") elif obj.type == "in": journal_id = obj.journal_id.id or settings.purchase_journal_id.id if not journal_id: raise Exception("Purchases journal not found") if obj.currency_rate: currency_rate = obj.currency_rate else: if obj.currency_id.id == settings.currency_id.id: currency_rate = 1 else: rate_from = obj.currency_id.get_rate(date=obj.date) if not rate_from: raise Exception("Missing currency rate for %s" % obj.currency_id.code) rate_to = settings.currency_id.get_rate(date=obj.date) if not rate_to: raise Exception("Missing currency rate for %s" % settings.currency_id.code) currency_rate = rate_from / rate_to obj.write({"currency_rate": currency_rate}) move_vals = { "journal_id": journal_id, "number": obj.number, "date": obj.journal_date or obj.date, "ref": obj.ref, "narration": desc, "related_id": "account.invoice,%s" % obj.id, "company_id": obj.company_id.id, } lines = [] t01 = time.time() for line in obj.lines: cur_amt = get_model("currency").convert( line.amount, obj.currency_id.id, settings.currency_id.id, rate=currency_rate) tax_id = line.tax_id if tax_id and obj.tax_type != "no_tax": base_amt = get_model("account.tax.rate").compute_base(tax_id, cur_amt, tax_type=obj.tax_type) else: base_amt = cur_amt acc_id = line.account_id.id if not acc_id: raise Exception("Missing line account for invoice line '%s'" % line.description) amt = base_amt * sign line_vals = { "description": line.description, "account_id": acc_id, "credit": amt > 0 and amt or 0, "debit": amt < 0 and -amt or 0, "track_id": line.track_id.id, "track2_id": line.track2_id.id, "contact_id": contact.id, } lines.append(line_vals) for tax in obj.taxes: comp = tax.tax_comp_id acc_id = comp.account_id.id if not acc_id: raise Exception("Missing account for tax component %s" % comp.name) amt = sign * tax.tax_amount line_vals = { "description": desc, "account_id": acc_id, "credit": amt > 0 and amt or 0, "debit": amt < 0 and -amt or 0, "tax_comp_id": comp.id, "tax_base": tax.base_amount, "contact_id": contact.id, "invoice_id": obj.id, "tax_no": tax.tax_no, "tax_date": obj.date, } lines.append(line_vals) t02 = time.time() dt01 = (t02 - t01) * 1000 print("post dt01", dt01) groups = {} keys = ["description", "account_id", "track_id", "tax_comp_id", "contact_id", "invoice_id", "reconcile_id"] for line in lines: key_val = tuple(line.get(k) for k in keys) if key_val in groups: group = groups[key_val] group["debit"] += line["debit"] group["credit"] += line["credit"] if line.get("tax_base"): if "tax_base" not in group: group["tax_base"] = 0 group["tax_base"] += line["tax_base"] else: groups[key_val] = line.copy() group_lines = sorted(groups.values(), key=lambda l: (l["debit"], l["credit"])) for line in group_lines: amt = line["debit"] - line["credit"] amt = get_model("currency").round(obj.currency_id.id,amt) if amt >= 0: line["debit"] = amt line["credit"] = 0 else: line["debit"] = 0 line["credit"] = -amt amt = 0 for line in group_lines: amt -= line["debit"] - line["credit"] line_vals = { "description": desc, "account_id": account_id, "debit": amt > 0 and amt or 0, "credit": amt < 0 and -amt or 0, "due_date": obj.due_date, "contact_id": contact.id, } acc = get_model("account.account").browse(account_id) if acc.currency_id.id != settings.currency_id.id: if acc.currency_id.id != obj.currency_id.id: raise Exception("Invalid account currency for this invoice: %s" % acc.code) line_vals["amount_cur"] = obj.amount_total move_vals["lines"] = [("create", line_vals)] move_vals["lines"] += [("create", vals) for vals in group_lines] t03 = time.time() dt02 = (t03 - t02) * 1000 print("post dt02", dt02) move_id = get_model("account.move").create(move_vals) t04 = time.time() dt03 = (t04 - t03) * 1000 print("post dt03", dt03) get_model("account.move").post([move_id]) t05 = time.time() dt04 = (t05 - t04) * 1000 print("post dt04", dt04) obj.write({"move_id": move_id, "state": "waiting_payment"}) t06 = time.time() dt05 = (t06 - t05) * 1000 print("post dt05", dt05) t1 = time.time() dt = (t1 - t0) * 1000 print("invoice.post <<< %d ms" % dt) def repost_invoices(self, context={}): # XXX ids = self.search([["state", "in", ("waiting_payment", "paid")]], order="date") for obj in self.browse(ids): print("invoice %d..." % obj.id) if not obj.move_id: raise Exception("No journal entry for invoice #%d" % obj.id) obj.move_id.delete() obj.post() def void(self, ids, context={}): print("invoice.void", ids) obj = self.browse(ids)[0] if obj.state not in ("draft", "waiting_payment"): raise Exception("Invalid invoice state") if obj.payment_entries: raise Exception("Can't void invoice because there are still related payment entries") if obj.move_id: obj.move_id.void() obj.move_id.delete() obj.write({"state": "voided"}) def to_draft(self, ids, context={}): obj = self.browse(ids)[0] if obj.state not in ("waiting_payment","voided"): raise Exception("Invalid status") if obj.payment_entries: raise Exception("There are still payment entries for this invoice") if obj.move_id: obj.move_id.void() obj.move_id.delete() if obj.reconcile_move_line_id: # XXX: deprecated obj.write({"reconcile_move_line_id":None}) obj.taxes.delete() obj.write({"state": "draft"}) def get_amount(self, ids, context={}): t0 = time.time() settings = get_model("settings").browse(1) res = {} for inv in self.browse(ids): vals = {} subtotal = 0 tax = 0 for line in inv.lines: tax_id = line.tax_id if tax_id and inv.tax_type != "no_tax": base_amt = get_model("account.tax.rate").compute_base(tax_id, line.amount, tax_type=inv.tax_type) tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice") for comp_id, tax_amt in tax_comps.items(): tax += tax_amt else: base_amt = line.amount subtotal += base_amt subtotal=get_model("currency").round(inv.currency_id.id,subtotal) tax=get_model("currency").round(inv.currency_id.id,tax) vals["amount_subtotal"] = subtotal if inv.taxes: tax=sum(t.tax_amount for t in inv.taxes) vals["amount_tax"] = tax if inv.tax_type == "tax_in": vals["amount_rounding"] = sum(l.amount for l in inv.lines) - (subtotal + tax) else: vals["amount_rounding"] = 0 vals["amount_total"] = subtotal + tax + vals["amount_rounding"] vals["amount_total_cur"] = get_model("currency").convert( vals["amount_total"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate) vals["amount_credit_total"] = vals["amount_total"] paid_amt = 0 for pmt in inv.payment_entries: if pmt.amount_cur is not None: pmt_amt=abs(pmt.amount_cur) # XXX: no need for abs, amount_cur always>=0 else: if inv.type == "in": pmt_amt=pmt.debit else: pmt_amt=pmt.credit paid_amt+=pmt_amt vals["amount_paid"] = paid_amt if inv.inv_type in ("invoice", "debit"): vals["amount_due"] = vals["amount_total"] - paid_amt elif inv.inv_type in ("credit", "prepay", "overpay"): cred_amt = 0 for pmt in inv.payment_entries: if pmt.amount_cur is not None: pmt_amt=abs(pmt.amount_cur) else: if inv.type == "in": pmt_amt=pmt.credit else: pmt_amt=pmt.debit cred_amt += pmt_amt vals["amount_credit_remain"] = vals["amount_total"] - cred_amt vals["amount_due"] = -vals["amount_credit_remain"] vals["amount_due_cur"] = get_model("currency").convert( vals["amount_due"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate) vals["amount_paid_cur"] = get_model("currency").convert( vals["amount_paid"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate) vals["amount_credit_remain_cur"] = get_model("currency").convert( vals.get("amount_credit_remain", 0), inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate) res[inv.id] = vals t1 = time.time() dt = (t1 - t0) * 1000 print("invoice.get_amount <<< %d ms" % dt) return res def get_qty_total(self, ids, context={}): res = {} for obj in self.browse(ids): qty = sum([line.qty or 0 for line in obj.lines]) res[obj.id] = qty return res def update_amounts(self, context): data = context["data"] settings=get_model("settings").browse(1) currency_id = data["currency_id"] data["amount_subtotal"] = 0 data["amount_tax"] = 0 tax_type = data["tax_type"] tax_in_total = 0 for line in data["lines"]: if not line: continue if line.get("unit_price") is not None: amt = (line.get("qty") or 0) * (line.get("unit_price") or 0) if line.get("discount"): disc = amt * line["discount"] / 100 amt -= disc if line.get("discount_amount"): amt -= line["discount_amount"] line["amount"] = amt else: amt = line.get("amount") or 0 tax_id = line.get("tax_id") if tax_id and tax_type != "no_tax": base_amt = get_model("account.tax.rate").compute_base(tax_id, amt, tax_type=tax_type) tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice") for comp_id, tax_amt in tax_comps.items(): data["amount_tax"] += tax_amt else: base_amt = amt data["amount_subtotal"] += base_amt if tax_type == "tax_in": data["amount_rounding"] = sum( l.get("amount") or 0 for l in data["lines"] if l) - (data["amount_subtotal"] + data["amount_tax"]) else: data["amount_rounding"] = 0 data["amount_total"] = data["amount_subtotal"] + data["amount_tax"] + data["amount_rounding"] return data def onchange_product(self, context): data = context["data"] type = data["type"] path = context["path"] contact_id = data["contact_id"] contact = get_model("contact").browse(contact_id) line = get_data_path(data, path, parent=True) prod_id = line.get("product_id") if not prod_id: return {} prod = get_model("product").browse(prod_id) line["description"] = prod.description line["qty"] = 1 if prod.uom_id is not None: line["uom_id"] = prod.uom_id.id if type == "out": if prod.sale_price is not None: line["unit_price"] = prod.sale_price if prod.sale_account_id is not None: line["account_id"] = prod.sale_account_id.id or prod.categ_id.sale_account_id.id if prod.sale_tax_id is not None: line["tax_id"] = contact.tax_receivable_id.id or prod.sale_tax_id.id or prod.categ_id.sale_tax_id.id elif type == "in": if prod.purchase_price is not None: line["unit_price"] = prod.purchase_price if prod.purchase_account_id is not None: line["account_id"] = prod.purchase_account_id.id or prod.categ_id.purchase_account_id.id if prod.purchase_tax_id is not None: line["tax_id"] = contact.tax_payable_id.id or prod.purchase_tax_id.id or prod.categ_id.purchase_tax_id.id data = self.update_amounts(context) return data def onchange_account(self, context): data = context["data"] path = context["path"] line = get_data_path(data, path, parent=True) acc_id = line.get("account_id") if not acc_id: return {} acc = get_model("account.account").browse(acc_id) line["tax_id"] = acc.tax_id.id data = self.update_amounts(context) return data def onchange_contact(self, context): data = context["data"] contact_id = data.get("contact_id") if not contact_id: return {} contact = get_model("contact").browse(contact_id) data["bill_address_id"] = contact.get_address(pref_type="billing") if data["type"] == "out": data["journal_id"] = contact.sale_journal_id.id elif data["type"] == "in": data["journal_id"] = contact.purchase_journal_id.id self.onchange_journal(context=context) if contact.currency_id: data["currency_id"] = contact.currency_id.id else: settings = get_model("settings").browse(1) data["currency_id"] = settings.currency_id.id return data def view_invoice(self, ids, context={}): obj = self.browse(ids[0]) if obj.type == "out": action = "cust_invoice" if obj.inv_type == "invoice": layout = "cust_invoice_form" elif obj.inv_type == "credit": layout = "cust_credit_form" elif obj.inv_type == "debit": layout = "cust_debit_form" elif obj.inv_type == "prepay": layout = "cust_prepay_form" elif obj.inv_type == "overpay": layout = "cust_overpay_form" elif obj.type == "in": action = "supp_invoice" if obj.inv_type == "invoice": layout = "supp_invoice_form" elif obj.inv_type == "credit": layout = "supp_credit_form" elif obj.inv_type == "debit": layout = "supp_debit_form" elif obj.inv_type == "prepay": layout = "supp_prepay_form" elif obj.inv_type == "overpay": layout = "supp_overpay_form" next_action={ "name": action, "mode": "form", "form_view_xml": layout, "active_id": obj.id, } call_action=context.get("action",{}) if call_action.get("tab_no"): next_action["tab_no"]=call_action["tab_no"] if call_action.get("offset"): next_action["offset"]=call_action["offset"] if call_action.get("search_condition"): next_action["search_condition"]=call_action["search_condition"] return { "next": next_action, } def get_contact_credit(self, ids, context={}): obj = self.browse(ids[0]) contact = get_model("contact").browse(obj.contact_id.id, context={"currency_id": obj.currency_id.id}) vals = {} if obj.type == "out": amt = contact.receivable_credit elif obj.type == "in": amt = contact.payable_credit vals[obj.id] = amt return vals def get_state(self, ids, context={}): vals = {} for obj in self.browse(ids): state = obj.state if state == "waiting_payment": if obj.inv_type in ("invoice", "debit"): if obj.amount_due == 0: state = "paid" elif obj.inv_type in ("credit", "prepay", "overpay"): if obj.amount_credit_remain == 0: state = "paid" elif state == "paid": if obj.inv_type in ("invoice", "debit"): if obj.amount_due > 0: state = "waiting_payment" elif obj.inv_type in ("credit", "prepay", "overpay"): if obj.amount_credit_remain > 0: state = "waiting_payment" vals[obj.id] = state return vals def copy(self, ids, context): obj = self.browse(ids)[0] vals = { "type": obj.type, "inv_type": obj.inv_type, "ref": obj.ref, "contact_id": obj.contact_id.id, "currency_id": obj.currency_id.id, "tax_type": obj.tax_type, "memo": obj.memo, "lines": [], } if obj.related_id: vals["related_id"] = "%s,%s" % (obj.related_id._model, obj.related_id.id) for line in obj.lines: line_vals = { "product_id": line.product_id.id, "description": line.description, "qty": line.qty, "uom_id": line.uom_id.id, "unit_price": line.unit_price, "tax_id": line.tax_id.id, "account_id": line.account_id.id, "sale_id": line.sale_id.id, "purch_id": line.purch_id.id, "amount": line.amount, } vals["lines"].append(("create", line_vals)) new_id = self.create(vals, context={"type": obj.type, "inv_type": obj.inv_type}) new_obj = self.browse(new_id) if obj.type == "out": msg = "Invoice %s copied to %s" % (obj.number, new_obj.number) else: msg = "Invoice copied" return { "next": { "name": "view_invoice", "active_id": new_id, }, "flash": msg, } def copy_to_credit_note(self, ids, context): obj = self.browse(ids)[0] vals = { "type": obj.type, "inv_type": "credit", "ref": obj.number, "contact_id": obj.contact_id.id, "bill_address_id": obj.bill_address_id.id, "currency_id": obj.currency_id.id, "currency_rate": obj.currency_rate, "tax_type": obj.tax_type, "memo": obj.memo, "tax_no": obj.tax_no, "pay_method_id": obj.pay_method_id.id, "original_invoice_id": obj.id, "lines": [], } if obj.related_id: vals["related_id"] = "%s,%s" % (obj.related_id._model, obj.related_id.id) for line in obj.lines: line_vals = { "product_id": line.product_id.id, "description": line.description, "qty": line.qty, "uom_id": line.uom_id.id, "unit_price": line.unit_price, "tax_id": line.tax_id.id, "account_id": line.account_id.id, "sale_id": line.sale_id.id, "purch_id": line.purch_id.id, "amount": line.amount, } vals["lines"].append(("create", line_vals)) new_id = self.create(vals, context={"type": vals["type"], "inv_type": vals["inv_type"]}) new_obj = self.browse(new_id) msg = "Credit note %s created from invoice %s" % (new_obj.number, obj.number) return { "next": { "name": "view_invoice", "active_id": new_id, }, "flash": msg, "invoice_id": new_id, } def view_journal_entry(self, ids, context={}): obj = self.browse(ids)[0] return { "next": { "name": "journal_entry", "mode": "form", "active_id": obj.move_id.id, } } def gen_tax_no(self, exclude=None, context={}): company_id = get_active_company() # XXX: improve this? seq_id = get_model("sequence").find_sequence(type="tax_no") if not seq_id: return None while 1: num = get_model("sequence").get_next_number(seq_id, context=context) if exclude and num in exclude: get_model("sequence").increment_number(seq_id, context=context) continue res = get_model("account.move.line").search([["tax_no", "=", num], ["move_id.company_id", "=", company_id]]) if not res: return num get_model("sequence").increment_number(seq_id, context=context) def get_discount(self, ids, context={}): vals = {} for obj in self.browse(ids): amt = 0 for line in obj.lines: amt += line.amount_discount vals[obj.id] = amt return vals def create_fixed_assets(self, ids, context={}): for obj in self.browse(ids): if obj.fixed_assets: raise Exception("Fixed assets already created for invoice %s" % obj.number) for line in obj.lines: acc = line.account_id if acc.type != "fixed_asset": continue ass_type = acc.fixed_asset_type_id if not ass_type: continue vals = { "name": line.description, "type_id": ass_type.id, "date_purchase": obj.date, "price_purchase": line.amount, # XXX: should be tax-ex "fixed_asset_account_id": acc.id, "dep_rate": ass_type.dep_rate, "dep_method": ass_type.dep_method, "accum_dep_account_id": ass_type.accum_dep_account_id.id, "dep_exp_account_id": ass_type.dep_exp_account_id.id, "invoice_id": obj.id, } get_model("account.fixed.asset").create(vals) def delete_alloc(self, context={}): alloc_id = context["alloc_id"] get_model("account.credit.alloc").delete([alloc_id]) def onchange_date(self, context={}): data = context["data"] ctx = { "type": data["type"], "inv_type": data["inv_type"], "date": data["date"], } number = self._get_number(context=ctx) data["number"] = number return data def check_related(self, ids, context={}): obj = self.browse(ids)[0] rel = obj.related_id if not rel: return # if rel._model=="job": # XXX: doesn't work for bkkbase modules # if not rel.done_approved_by_id: # raise Exception("Service order has to be approved before it is invoiced") def get_template_invoice_form(self, ids=None, context={}): if ids is None: # XXX: for backward compat with old templates ids = context["ids"] obj = get_model("account.invoice").browse(ids)[0] if obj.type == "out": if obj.amount_discount: return "cust_invoice_form_disc" else: return "cust_invoice_form" elif obj.type == "in": return "supp_invoice_form" def onchange_journal(self, context={}): data = context["data"] journal_id = data["journal_id"] if journal_id: journal = get_model("account.journal").browse(journal_id) data["sequence_id"] = journal.sequence_id.id else: data["sequence_id"] = None self.onchange_sequence(context=context) return data def onchange_sequence(self, context={}): data = context["data"] seq_id = data["sequence_id"] num = self._get_number(context={"type": data["type"], "inv_type": data["inv_type"], "date": data["date"], "sequence_id": seq_id}) data["number"] = num return data def pay_online(self,ids,context={}): obj=self.browse(ids[0]) method=obj.pay_method_id if not method: raise Exception("Missing payment method for invoice %s"%obj.number) ctx={ "amount": obj.amount_total, "currency_id": obj.currency_id.id, "details": "Invoice %s"%obj.number, } res=method.start_payment(context=ctx) if not res: raise Exception("Failed to start online payment for payment method %s"%method.name) transaction_no=res["transaction_no"] obj.write({"transaction_no":transaction_no}) return { "next": res["payment_action"], } def get_payment_entries(self,ids,context={}): vals={} for obj in self.browse(ids): line_ids=[] if obj.move_id: inv_line=obj.move_id.lines[0] rec=inv_line.reconcile_id if rec: for line in rec.lines: if line.id!=inv_line.id: line_ids.append(line.id) vals[obj.id]=line_ids return vals
class BatchImport(Model): _name = "batch.import" _string = "Batch Import" _fields = { "from_date": fields.Date("From Date", required=True, search=True), "to_date": fields.Date("To Date", required=True), "cash_account_id": fields.Many2One("account.account", "Cash Account"), "bank_account_id": fields.Many2One("account.account", "Bank Account"), "cash_payments": fields.One2Many("batch.import.payment", "import_id", "Cash Payments", condition=[["type", "=", "cash"]]), "bank_payments": fields.One2Many("batch.import.payment", "import_id", "Bank Payments", condition=[["type", "=", "bank"]]), "sale_invoices": fields.One2Many("batch.import.sale.invoice", "import_id", "Sales Invoices"), "purchase_invoices": fields.One2Many("batch.import.purchase.invoice", "import_id", "Purchase Invoices"), "cash_file": fields.File("Cash File"), "bank_file": fields.File("Bank File"), "sale_file": fields.File("Sales File"), "purchase_file": fields.File("Purchases File"), "date_fmt": fields.Char("Date Format"), "state": fields.Selection([["draft", "Draft"], ["posted", "Posted"]], "Status", required=True), } _order = "from_date,id" def _get_cash_account(self, context={}): res = get_model("account.account").search([["type", "=", "cash"]], order="code") if not res: return None return res[0] def _get_bank_account(self, context={}): res = get_model("account.account").search([["type", "=", "bank"]], order="code") if not res: return None return res[0] _defaults = { "state": "draft", "date_fmt": "%m/%d/%Y", "from_date": lambda *a: time.strftime("%Y-%m-01"), "to_date": lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"), "cash_account_id": _get_cash_account, "bank_account_id": _get_bank_account, } def clear_data(self, ids, context={}): obj = self.browse(ids)[0] obj.cash_payments.delete() obj.bank_payments.delete() obj.sale_invoices.delete() obj.purchase_invoices.delete() def import_files(self, ids, context={}): obj = self.browse(ids)[0] obj.clear_data() if obj.cash_file: obj.import_cash_file() if obj.bank_file: obj.import_bank_file() if obj.sale_file: obj.import_sale_file() if obj.purchase_file: obj.import_purchase_file() def import_cash_file(self, ids, context={}): obj = self.browse(ids)[0] path = get_file_path(obj.cash_file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] for row in rd: print("row", row) line = dict(zip(headers, row)) print("line", line) vals = { "import_id": obj.id, "type": "cash", "date": parse_date(line["Date"], obj.date_fmt), "description": line["Description"], "received": parse_float(line["Received"]), "spent": parse_float(line["Spent"]), "invoice_no": line["Invoice No."], "other_account_id": get_account(line["Other Account"]), } get_model("batch.import.payment").create(vals) def import_bank_file(self, ids, context={}): obj = self.browse(ids)[0] path = get_file_path(obj.bank_file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] for row in rd: print("row", row) line = dict(zip(headers, row)) print("line", line) vals = { "import_id": obj.id, "type": "bank", "date": parse_date(line["Date"], obj.date_fmt), "description": line["Description"], "received": parse_float(line["Received"]), "spent": parse_float(line["Spent"]), "invoice_no": line["Invoice No."], "other_account_id": get_account(line["Other Account"]), } get_model("batch.import.payment").create(vals) def import_sale_file(self, ids, context={}): obj = self.browse(ids)[0] path = get_file_path(obj.sale_file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] for row in rd: print("row", row) line = dict(zip(headers, row)) print("line", line) vals = { "import_id": obj.id, "date": parse_date(line["Date"], obj.date_fmt), "number": line["Invoice No."], "contact": line["Customer Name"], "description": line["Description"], "amount": parse_float(line["Amount"]), "account_id": get_account(line["Income Account"]), "tax_id": get_tax_rate(line["Tax Rate"]), } get_model("batch.import.sale.invoice").create(vals) def import_purchase_file(self, ids, context={}): obj = self.browse(ids)[0] path = get_file_path(obj.purchase_file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] for row in rd: print("row", row) line = dict(zip(headers, row)) print("line", line) vals = { "import_id": obj.id, "date": parse_date(line["Date"], obj.date_fmt), "number": line["Invoice No."], "contact": line["Supplier Name"], "description": line["Description"], "amount": parse_float(line["Amount"]), "account_id": get_account(line["Expense Account"]), "tax_id": get_tax_rate(line["Tax Rate"]), } get_model("batch.import.purchase.invoice").create(vals) def post(self, ids, context={}): obj = self.browse(ids)[0] for line in obj.sale_invoices: if line.invoice_id: continue vals = { "date": line.date, "due_date": line.date, # XXX "number": line.number, "contact_id": merge_contact(line.contact), "type": "out", "inv_type": "invoice", "lines": [], } line_vals = { "description": line.description, "amount": line.amount, "account_id": line.account_id.id, "tax_id": line.tax_id.id, } vals["lines"].append(("create", line_vals)) inv_id = get_model("account.invoice").create(vals, context={ "type": vals["type"], "inv_type": vals["inv_type"] }) get_model("account.invoice").post([inv_id]) line.write({"invoice_id": inv_id}) for line in obj.purchase_invoices: if line.invoice_id: continue vals = { "date": line.date, "due_date": line.date, # XXX "number": line.number, "contact_id": merge_contact(line.contact), "type": "in", "inv_type": "invoice", "lines": [], } line_vals = { "description": line.description, "amount": line.amount, "account_id": line.account_id.id, "tax_id": line.tax_id.id, } vals["lines"].append(("create", line_vals)) inv_id = get_model("account.invoice").create(vals, context={ "type": vals["type"], "inv_type": vals["inv_type"] }) get_model("account.invoice").post([inv_id]) line.write({"invoice_id": inv_id}) for line in obj.cash_payments: if line.payment_id: continue vals = { "date": line.date, "account_id": obj.cash_account_id.id, "lines": [], } if line.received: vals["type"] = "in" elif line.spent: vals["type"] = "out" else: raise Exception("Missing amount in cash payment") amt = line.received or line.spent if line.invoice_no: vals["pay_type"] = "invoice" line_vals = { "type": "invoice", "description": line.description, "invoice_id": get_invoice(line.invoice_no), "amount": amt, } else: vals["pay_type"] = "direct" line_vals = { "type": "direct", "description": line.description, "amount": amt, "account_id": line.other_account_id.id, } vals["lines"].append(("create", line_vals)) pmt_id = get_model("account.payment").create( vals, context={"type": vals["type"]}) get_model("account.payment").post([pmt_id]) line.write({"payment_id": pmt_id}) for line in obj.bank_payments: if line.payment_id: continue vals = { "date": line.date, "account_id": obj.bank_account_id.id, "lines": [], } if line.received: vals["type"] = "in" elif line.spent: vals["type"] = "out" else: raise Exception("Missing amount in bank payment") amt = line.received or line.spent if line.invoice_no: vals["pay_type"] = "invoice" line_vals = { "type": "invoice", "description": line.description, "invoice_id": get_invoice(line.invoice_no), "amount": amt, } else: vals["pay_type"] = "direct" line_vals = { "type": "direct", "description": line.description, "amount": amt, "account_id": line.other_account_id.id, } vals["lines"].append(("create", line_vals)) pmt_id = get_model("account.payment").create( vals, context={"type": vals["type"]}) get_model("account.payment").post([pmt_id]) line.write({"payment_id": pmt_id}) obj.write({"state": "posted"})
class Contact(Model): _name = "contact" _string = "Contact" _audit_log = True _export_field = "name" _key = ["code"] _fields = { "user_id": fields.Many2One("base.user", "User"), "type": fields.Selection([["person", "Individual"], ["org", "Organization"]], "Contact Type", required=True, search=True), "customer": fields.Boolean("Customer", search=True), "supplier": fields.Boolean("Supplier", search=True), "name": fields.Char("Name", required=True, search=True, translate=True, size=256), "code": fields.Char("Code", search=True, required=True), "phone": fields.Char("Phone", search=True), "fax": fields.Char("Fax"), "website": fields.Char("Website"), "industry": fields.Char("Industry"), # XXX: deprecated "employees": fields.Char("Employees"), "revenue": fields.Char("Annual Revenue"), "description": fields.Text("Description"), "tax_no": fields.Char("Tax ID Number"), "tax_branch_no": fields.Char("Tax Branch Id"), "bank_account_no": fields.Char("Bank Account Number"), "bank_account_name": fields.Char("Bank Account Name"), "bank_account_details": fields.Char("Bank Account Details"), "active": fields.Boolean("Active"), "account_receivable_id": fields.Many2One("account.account", "Account Receivable", multi_company=True), "tax_receivable_id": fields.Many2One("account.tax.rate", "Account Receivable Tax"), "account_payable_id": fields.Many2One("account.account", "Account Payable", multi_company=True), "tax_payable_id": fields.Many2One("account.tax.rate", "Account Payable Tax"), "currency_id": fields.Many2One("currency", "Default Currency"), "payables_due": fields.Decimal("Payables Due"), "payables_overdue": fields.Decimal("Payables Overdue"), "receivables_due": fields.Decimal("Receivables Due"), "receivables_overdue": fields.Decimal("Receivables Overdue"), "payable_credit": fields.Decimal("Payable Credit", function="get_credit", function_multi=True), "receivable_credit": fields.Decimal("Receivable Credit", function="get_credit", function_multi=True), "invoices": fields.One2Many("account.invoice", "contact_id", "Invoices"), "sale_price_list_id": fields.Many2One("price.list", "Sales Price List", condition=[["type", "=", "sale"]]), "purchase_price_list_id": fields.Many2One("price.list", "Purchasing Price List", condition=[["type", "=", "purchase"]]), "categ_id": fields.Many2One("contact.categ", "Contact Category"), "payment_terms": fields.Char("Payment Terms"), "opports": fields.One2Many("sale.opportunity", "contact_id", "Open Opportunities", condition=[["state", "=", "open"]]), "addresses": fields.One2Many("address", "contact_id", "Addresses"), "comments": fields.One2Many("message", "related_id", "Comments"), "bank_accounts": fields.One2Many("bank.account", "contact_id", "Bank Accounts"), "last_name": fields.Char("Last Name"), "first_name": fields.Char("First Name"), "first_name2": fields.Char("First Name (2)"), "first_name3": fields.Char("First Name (3)"), "title": fields.Char("Title"), "position": fields.Char("Position"), "report_to_id": fields.Many2One("contact", "Reports To"), "mobile": fields.Char("Mobile"), "email": fields.Char("Email", search=True), "home_phone": fields.Char("Home Phone"), "other_phone": fields.Char("Other Phone"), "assistant": fields.Char("Assistant"), "assistant_phone": fields.Char("Assistant Phone"), "birth_date": fields.Date("Birth Date"), "department": fields.Char("Department"), "job_templates": fields.Many2Many("job.template", "Job Template"), "projects": fields.One2Many("project", "contact_id", "Projects"), "documents": fields.One2Many("document", "contact_id", "Documents"), "assigned_to_id": fields.Many2One("base.user", "Assigned To"), "lead_source": fields.Char("Lead source"), "inquiry_type": fields.Char("Type of inquiry"), "relations": fields.One2Many("contact.relation", "from_contact_id", "Relations", function="_get_relations"), "contact_id": fields.Many2One( "contact", "Parent"), # XXX: not used any more, just there for migration "emails": fields.One2Many("email.message", "name_id", "Emails"), "default_address_id": fields.Many2One("address", "Default Address", function="get_default_address"), "sale_orders": fields.One2Many("sale.order", "contact_id", "Sales Orders"), "country_id": fields.Many2One("country", "Country", search=True), "region": fields.Char("Region"), # XXX: deprecated "service_items": fields.One2Many("service.item", "contact_id", "Service Items", condition=[["parent_id", "=", None]]), "contracts": fields.One2Many("service.contract", "contact_id", "Contracts"), "branch": fields.Char("Branch"), # XXX: add by Cash "industry_id": fields.Many2One("industry", "Industry", search=True), "region_id": fields.Many2One("region", "Region", search=True), "commission_po_percent": fields.Decimal("Commission Purchase Percentage"), "business_area_id": fields.Many2One("business.area", "Business Area", search=True), "fleet_size_id": fields.Many2One("fleet.size", "Fleet Size", search=True), "groups": fields.Many2Many("contact.group", "Groups", search=True), "sale_journal_id": fields.Many2One("account.journal", "Sales Journal"), "purchase_journal_id": fields.Many2One("account.journal", "Purchase Journal"), "pay_in_journal_id": fields.Many2One("account.journal", "Receipts Journal"), "pay_out_journal_id": fields.Many2One("account.journal", "Disbursements Journal"), "pick_in_journal_id": fields.Many2One("stock.journal", "Goods Receipt Journal"), "pick_out_journal_id": fields.Many2One("stock.journal", "Goods Issue Journal"), "coupons": fields.One2Many("sale.coupon", "contact_id", "Coupons"), "companies": fields.Many2Many("company", "Companies"), "request_product_groups": fields.Many2Many("product.group", "Request Product Groups", reltable="m2m_contact_request_product_groups", relfield="contact_id", relfield_other="group_id"), "exclude_product_groups": fields.Many2Many("product.group", "Exclude Product Groups", reltable="m2m_contact_exclude_product_groups", relfield="contact_id", relfield_other="group_id"), "picture": fields.File("Picture"), "users": fields.One2Many("base.user", "contact_id", "Users"), "ship_free": fields.Boolean("Free Shipping"), } def _get_number(self, context={}): seq_id = get_model("sequence").find_sequence(type="contact") if not seq_id: return None while 1: num = get_model("sequence").get_next_number(seq_id, context=context) res = self.search([["code", "=", num]]) if not res: return num get_model("sequence").increment_number(seq_id, context=context) _defaults = { "active": True, "type": "person", "code": _get_number, } _order = "name" _constraints = ["check_email"] def create(self, vals, **kw): if not vals.get("type"): if vals.get("name"): vals["type"] = "org" elif vals.get("last_name"): vals["type"] = "person" if vals.get("type") == "person": if vals.get("first_name"): vals["name"] = vals["first_name"] + " " + vals["last_name"] else: vals["name"] = vals["last_name"] new_id = super().create(vals, **kw) return new_id def write(self, ids, vals, set_name=True, **kw): super().write(ids, vals, **kw) if set_name: for obj in self.browse(ids): if obj.type == "person": if obj.first_name: name = obj.first_name + " " + obj.last_name else: name = obj.last_name obj.write({"name": name}, set_name=False) def get_credit(self, ids, context={}): print("contact.get_credit", ids) currency_id = context.get("currency_id") print("currency_id", currency_id) vals = {} for obj in self.browse(ids): ctx = { "contact_id": obj.id, } r_credit = 0 p_credit = 0 for acc in get_model("account.account").search_browse( [["type", "=", "cust_deposit"]], context=ctx): r_credit -= acc.balance for acc in get_model("account.account").search_browse( [["type", "=", "sup_deposit"]], context=ctx): p_credit += acc.balance vals[obj.id] = { "receivable_credit": r_credit, "payable_credit": p_credit, # TODO } return vals def get_address_str(self, ids, context={}): obj = self.browse(ids[0]) if not obj.addresses: return "" addr = obj.addresses[0] return addr.name_get()[0][1] def _get_relations(self, ids, context={}): cond = [ "or", ["from_contact_id", "in", ids], ["to_contact_id", "in", ids] ] rels = get_model("contact.relation").search_read( cond, ["from_contact_id", "to_contact_id"]) vals = {} for rel in rels: from_id = rel["from_contact_id"][0] to_id = rel["to_contact_id"][0] vals.setdefault(from_id, []).append(rel["id"]) vals.setdefault(to_id, []).append(rel["id"]) return vals def get_address(self, ids, pref_type=None, context={}): obj = self.browse(ids)[0] for addr in obj.addresses: if pref_type and addr.type != pref_type: continue return addr.id if obj.addresses: return obj.addresses[0].id return None def get_default_address(self, ids, context={}): vals = {} for obj in self.browse(ids): addr_id = None for addr in obj.addresses: if addr.type == "billing": addr_id = addr.id break if not addr_id and obj.addresses: addr_id = obj.addresses[0].id vals[obj.id] = addr_id print("XXX", vals) return vals def check_email(self, ids, context={}): for obj in self.browse(ids): if not obj.email: continue if not utils.check_email_syntax(obj.email): raise Exception("Invalid email for contact '%s'" % obj.name) def find_address(self, ids, addr_vals, context={}): obj = self.browse(ids[0]) addr_id = None for addr in obj.addresses: if "address" in addr_vals and addr_vals["address"] != addr.address: continue if "address2" in addr_vals and addr_vals[ "address2"] != addr.address2: continue if "city" in addr_vals and addr_vals["city"] != addr.city: continue if "postal_code" in addr_vals and addr_vals[ "postal_code"] != addr.postal_code: continue if "country_id" in addr_vals and addr_vals[ "country_id"] != addr.country_id.id: continue if "province_id" in addr_vals and addr_vals[ "province_id"] != addr.province_id.id: continue if "district_id" in addr_vals and addr_vals[ "district_id"] != addr.district_id.id: continue if "subdistrict_id" in addr_vals and addr_vals[ "subdistrict_id"] != addr.subdistrict_id.id: continue if "phone" in addr_vals and addr_vals["phone"] != addr.phone: continue if "first_name" in addr_vals and addr_vals[ "phone"] != addr.first_name: continue if "last_name" in addr_vals and addr_vals[ "last_name"] != addr.last_name: continue addr_id = addr.id break return addr_id def add_address(self, ids, addr_vals, context={}): addr_id = self.find_address(ids) if not addr_id: vals = addr_vals.copy() vals["contact_id"] = ids[0] addr_id = get_model("address").create(vals) return addr_id
class ProductImage(Model): _name = "product.image" _fields = { "product_id": fields.Many2One("product", "Product", required=True, on_delete="cascade"), "image": fields.File("Image", required=True), "title": fields.Char("Title"), "description": fields.Text("Description"), "rotate_cw": fields.Boolean("Rotate Clockwise", function="_get_related", function_context={"path": "product_id.rotate_cw"}), "rotate_footage": fields.Char("Define Column", function="_get_related", function_context={"path": "product_id.rotate_footage"}), "rotate_frame": fields.Char("Define Amount of image", function="_get_related", function_context={"path": "product_id.rotate_frame"}), "rotate_speed": fields.Char("Define Rotating Speed", function="_get_related", function_context={"path": "product_id.rotate_speed"}), "rotate_height": fields.Char("Image Height", function="cal_dimension", function_multi=True), "rotate_width": fields.Char("Image Width", function="cal_dimension", function_multi=True), "master_image": fields.Char("Master image", function="cal_dimension", function_multi=True), } def cal_dimension(self, ids, context={}): all_vals = {} dbname = database.get_active_db() for obj in self.browse(ids): master_img = obj.product_id.image master_path = os.path.join("static/db/", dbname, "files", master_img) frame = int(obj.get("rotate_frame")) column = int(obj.get("rotate_footage")) row = 1 if frame and column: row = frame / column vals = {} im_path = obj.image if im_path and frame and column: filename = os.path.join("static/db/", dbname, "files", im_path) img = Image.open(filename) (width, height) = img.size swidth = math.floor(width / column) sheight = math.floor(height / row) vals["rotate_width"] = swidth vals["rotate_height"] = sheight vals["master_image"] = master_path all_vals[obj.id] = vals else: print("Not enough arguments given") return all_vals
class Product(Model): _name = "product" _string = "Product" _audit_log = True _key = ["code", "state", "company_id"] _order = "code,name" _export_name_field = "code" _history = True _fields = { "name": fields.Char("Name", required=True, search=True, translate=True, size=256), "code": fields.Char("Code", required=True, search=True), "type": fields.Selection([["stock", "Stockable"], ["consumable", "Consumable"], ["service", "Service"], ["master", "Master"], ["bundle", "Bundle"]], "Product Type", required=True, search=True), "uom_id": fields.Many2One("uom", "Default UoM", required=True, search=True), "parent_id": fields.Many2One("product", "Master Product"), "categ_id": fields.Many2One("product.categ", "Product Category", search=True), "description": fields.Text("Description", translate=True), "purchase_price": fields.Decimal("Purchase Price", scale=6), "sale_price": fields.Decimal("List Price (Sales Invoice UoM)", scale=6), "sale_price_order_uom": fields.Decimal("List Price (Sales Order UoM)", scale=6, function="get_sale_price_order_uom"), "tags": fields.Many2Many("tag", "Tags"), "image": fields.File("Image"), "cost_method": fields.Selection( [["standard", "Standard Cost"], ["average", "Weighted Average"], ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"), "cost_price": fields.Decimal("Cost Price", scale=6), "stock_in_account_id": fields.Many2One("account.account", "Stock Input Account", multi_company=True), # XXX: deprecated "stock_out_account_id": fields.Many2One("account.account", "Stock Output Account", multi_company=True), # XXX: deprecated "cogs_account_id": fields.Many2One("account.account", "Cost Of Goods Sold Account", multi_company=True), "stock_account_id": fields.Many2One("account.account", "Inventory Account", multi_company=True), "purchase_account_id": fields.Many2One("account.account", "Purchase Account", multi_company=True), "purchase_tax_id": fields.Many2One("account.tax.rate", "Purchase Tax"), "supplier_id": fields.Many2One("contact", "Default Supplier"), # XXX: deprecated "sale_account_id": fields.Many2One("account.account", "Sales Account", multi_company=True), "sale_return_account_id": fields.Many2One("account.account", "Sales Returns Account", multi_company=True), "sale_promotion_account_id": fields.Many2One("account.account", "Sales Promotion Account", multi_company=True), "sale_tax_id": fields.Many2One("account.tax.rate", "Sales Tax"), "sale_promotion_tax_id": fields.Many2One("account.tax.rate", "Promotion Tax"), "location_id": fields.Many2One("stock.location", "Warehouse"), # XXX: deprecated "bin_location": fields.Char("Bin Location"), "update_balance": fields.Boolean("Update Balance"), "active": fields.Boolean("Active"), "comments": fields.One2Many("message", "related_id", "Comments"), "categs": fields.Many2Many("product.categ", "Other Categories"), # XXX: deprecated "attributes": fields.One2Many("product.attribute.value", "product_id", "Attributes"), "variants": fields.One2Many("product", "parent_id", "Variants"), #"variant_values": fields.One2Many("product.custom.option.variant.value","product_id","Variant Values"), "custom_options": fields.Many2Many("product.custom.option", "Custom Options"), "images": fields.One2Many("product.image", "product_id", "Images"), "store_type_id": fields.Many2One("store.type", "Storage Type"), "brand_id": fields.Many2One("product.brand", "Brand", search=True), "related_products": fields.Many2Many("product", "Related Products", relfield="product1_id", relfield_other="product2_id"), "min_sale_qty": fields.Decimal("Min Sale Qty"), "sale_unit_qty": fields.Decimal("Sale Unit Qty"), "shelf_life": fields.Decimal("Shelf Life (Days)"), "weight": fields.Decimal("Weight (Kg)"), "volume": fields.Decimal("Volume (M^3)"), "width": fields.Decimal("Width"), "height": fields.Decimal("Height"), "length": fields.Decimal("Length"), "packing_size": fields.Char("Packing Size"), "details": fields.Text("Product Details", translate=True), "details2": fields.Text("Product Details (2)", translate=True), "details2_label": fields.Char("Product Details Label (2)"), "details3": fields.Text("Product Details (3)", translate=True), "details3_label": fields.Char("Product Details Label (3)"), "other_url": fields.Char("Product URL", size=256), "purchase_currency_id": fields.Many2One("currency", "Purchase Currency"), "purchase_currency_rate": fields.Decimal("Purchase Currency Rate", scale=6), "purchase_duty_percent": fields.Decimal("Import Duty (%)"), "purchase_ship_percent": fields.Decimal("Shipping Charge (%)"), "landed_cost": fields.Decimal("Landed Cost", function="get_landed_cost", function_multi=True), "landed_cost_conv": fields.Decimal("Landed Cost (Conv)", function="get_landed_cost", function_multi=True), "gross_profit": fields.Decimal("Gross Profit (%)"), "auto_list_price": fields.Decimal("Auto List Price", function="get_auto_list_price"), "max_discount": fields.Decimal("Max Discount (%)", function="get_max_discount"), "price_index": fields.Decimal("Price Index", function="get_price_index"), "price_notes": fields.Text("Notes"), "price_date": fields.Date("Price Date"), "pricelist_items": fields.One2Many("price.list.item", "product_id", "Pricelist Items"), "procure_method": fields.Selection([["mto", "Make To Order"], ["mts", "Make To Stock"]], "Procurement Method"), "supply_method": fields.Selection( [["purchase", "Purchase"], ["production", "Production"]], "Supply Method"), "can_sell": fields.Boolean("Can Sell"), "can_purchase": fields.Boolean("Can Purchase"), "id": fields.Integer("Database ID", readonly=True), # MTS "create_time": fields.DateTime("Create Time", readonly=True), # MTS "supplier_product_code": fields.Char("Supplier Product Code"), # XXX: deprecated "require_qty2": fields.Boolean("Require Secondary Qty"), "qty2_factor": fields.Decimal("UoM -> Secondary Qty Factor", scale=6), "replacements": fields.Many2Many("product", "Replacement Products", reltable="m2m_product_replacement", relfield="product1_id", relfield_other="product2_id"), "suppliers": fields.One2Many("product.supplier", "product_id", "Suppliers"), "max_qty_loss": fields.Decimal("Max Qty Loss"), "documents": fields.One2Many("document", "related_id", "Documents"), "ship_methods": fields.Many2Many("ship.method", "Shipping Methods"), "ecom_discount_percent": fields.Decimal("Ecom. Discount Percent"), # XXX: deprecated "ecom_special_price": fields.Decimal("Ecom. Special Price"), # XXX: deprecated #"ecom_sale_price": fields.Decimal("Ecom. Sale Price", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated #"ecom_has_discount": fields.Decimal("Ecom. Has Discount", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated "variant_attributes": fields.Json("Variant Attributes", function="get_variant_attributes"), "company_id": fields.Many2One("company", "Company"), "sale_channels": fields.Many2Many("sale.channel", "Sales Channels"), "customer_price": fields.Decimal("Customer Price", function="get_customer_price", function_multi=True), "customer_has_discount": fields.Decimal("Customer Has Discount", function="get_customer_price", function_multi=True), "customer_discount_text": fields.Decimal("Customer Discount Text", function="get_customer_price", function_multi=True), "customer_discount_percent": fields.Decimal("Customer Discount Percent", function="get_customer_price", function_multi=True), "sale_company_id": fields.Many2One("company", "Sold By"), "groups": fields.Many2Many("product.group", "Groups"), "payment_methods": fields.Many2Many("payment.method", "Payment Methods"), "locations": fields.One2Many("product.location", "product_id", "Warehouses"), "stock_qty": fields.Decimal("Total Stock Qty", function="get_stock_qty"), "stock_lots": fields.Many2Many("stock.lot", "Lots In Stock", function="get_stock_lots"), "state": fields.Selection([["draft", "Draft"], ["approved", "Approved"]], "Status"), "sale_uom_id": fields.Many2One("uom", "Sales Order UoM"), "sale_invoice_uom_id": fields.Many2One("uom", "Sales Invoice UoM"), "sale_to_stock_uom_factor": fields.Decimal("Sales Order -> Stock Uom Conversion Factor", scale=6), "sale_to_invoice_uom_factor": fields.Decimal("Sales Order -> Sales Invoice Uom Conversion Factor", scale=6), "purchase_uom_id": fields.Many2One("uom", "Purchase Order UoM"), "purchase_invoice_uom_id": fields.Many2One("uom", "Purchase Invoice UoM"), "purchase_to_stock_uom_factor": fields.Decimal("Purchase Order -> Stock Uom Conversion Factor", scale=6), "purchase_to_invoice_uom_factor": fields.Decimal( "Purchase Order -> Purchase Invoice Uom Conversion Factor", scale=6), "purchase_lead_time": fields.Integer("Purchasing Lead Time (Days)"), "purchase_min_qty": fields.Decimal("Purchase Minimum Qty"), "purchase_qty_multiple": fields.Decimal("Purchase Qty Multiple"), "mfg_lead_time": fields.Integer("Manufacturing Lead Time (Days)"), "mfg_min_qty": fields.Decimal("Manufacturing Minimum Qty"), "mfg_qty_multiple": fields.Decimal("Manufacturing Qty Multiple"), #"purchase_price_uom_id": fields.Many2One("uom", "Purchase Price UoM"), # not needed? #"sale_price_uom_id": fields.Many2One("uom", "List Price UoM"), # not needed? "events": fields.Many2Many("sale.event", "Events"), "is_published": fields.Boolean("Publish Product"), "require_lot": fields.Boolean("Require Lot"), "lot_select": fields.Selection([["fifo", "FIFO"], ["fefo", "FEFO"]], "Lot Selection"), "components": fields.One2Many("product.component", "product_id", "Bundle Components"), "approve_date": fields.DateTime("Approve Date"), "service_items": fields.One2Many("service.item", "product_id", "Service Items"), "lots": fields.One2Many("stock.lot", "product_id", "Lots"), "stock_plan_horizon": fields.Integer("Inventory Planning Horizon (days)"), # XXX: deprecated "ecom_hide_qty": fields.Boolean("Hide Stock Qty From Website"), "ecom_hide_unavail": fields.Boolean("Hide From Website When Out Of Stock"), "ecom_no_order_unavail": fields.Boolean("Prevent Orders When Out Of Stock"), "ecom_select_lot": fields.Boolean("Customers Can Select Lot When Ordering"), "product_origin": fields.Char("Product Origin"), "stock_balances": fields.One2Many("stock.balance", "product_id", "Stock Balances"), "check_lot_neg_stock": fields.Boolean("Check Lot Negative Stock"), "sale_lead_time_nostock": fields.Integer("Sale Lead Time When Out Of Stock (Days)"), "ship_methods": fields.Many2Many("ship.method", "Shipping Methods"), "delivery_weekdays": fields.Char("Delivery Weekday Constraints"), } _defaults = { "update_balance": True, "active": True, "can_sell": False, "can_purchase": False, "company_id": lambda *a: access.get_active_company(), "state": "draft", } def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): if obj.code: name = "[%s] %s" % (obj.code, obj.name) else: name = obj.name vals.append((obj.id, name, obj.image)) return vals def name_search(self, name, condition=None, context={}, limit=None, **kw): print("condition", condition) search_mode = context.get("search_mode") print("##############################") print("search_mode", search_mode) if search_mode == "suffix": cond = [["code", "=ilike", "%" + name]] elif search_mode == "prefix": cond = [["code", "=ilike", name + "%"]] else: cond = [["code", "ilike", name]] if condition: cond = [cond, condition] ids1 = self.search(cond, limit=limit) if search_mode == "suffix": cond = [["name", "=ilike", "%" + name]] elif search_mode == "prefix": cond = [["name", "=ilike", name + "%"]] else: cond = [["name", "ilike", name]] if condition: cond = [cond, condition] ids2 = self.search(cond, limit=limit) ids = list(set(ids1 + ids2)) return self.name_get(ids, context=context) def copy(self, ids, context={}): replace_id = context.get("replace_id") obj = self.browse(ids)[0] code = obj.code if not replace_id: for i in range(1, 10): code = obj.code + " (%s)" % i res = self.search([["code", "=", code]]) if not res: break vals = { "name": obj.name, "code": code, "type": obj.type, "uom_id": obj.uom_id.id, #"parent_id": obj.parent_id.id, XXX "description": obj.description, "image": obj.image, "categ_id": obj.categ_id.id, "categs": [("set", [c.id for c in obj.categs])], "supply_method": obj.supply_method, "procure_method": obj.procure_method, "can_sell": obj.can_sell, "can_purchase": obj.can_purchase, "sale_uom_id": obj.sale_uom_id.id, "sale_invoice_uom_id": obj.sale_invoice_uom_id.id, "sale_to_stock_uom_factor": obj.sale_to_stock_uom_factor, "sale_to_invoice_uom_factor": obj.sale_to_invoice_uom_factor, "purchase_uom_id": obj.purchase_uom_id.id, "purchase_invoice_uom_id": obj.purchase_invoice_uom_id.id, "purchase_to_stock_uom_factor": obj.purchase_to_stock_uom_factor, "purchase_to_invoice_uom_factor": obj.purchase_to_invoice_uom_factor, "purchase_price": obj.purchase_price, "purchase_account_id": obj.purchase_account_id.id, "purchase_tax_id": obj.purchase_tax_id.id, "supplier_id": obj.supplier_id.id, "sale_price": obj.sale_price, "sale_account_id": obj.sale_account_id.id, "sale_tax_id": obj.sale_tax_id.id, "sale_return_account_id": obj.sale_return_account_id.id, "sale_promotion_account_id": obj.sale_promotion_account_id.id, "sale_promotion_tax_id": obj.sale_promotion_tax_id.id, "cost_method": obj.cost_method, "stock_in_account_id": obj.stock_in_account_id.id, "stock_out_account_id": obj.stock_out_account_id.id, "bin_location": obj.bin_location, "sale_company_id": obj.sale_company_id.id, "attributes": [], } vals["attributes"] = [("delete_all", )] for attr in obj.attributes: vals["attributes"].append(("create", { "attribute_id": attr.attribute_id.id, "option_id": attr.option_id.id })) vals["pricelist_items"] = [("delete_all", )] for item in obj.pricelist_items: vals["pricelist_items"].append(("create", { "list_id": item.list_id.id, "price": item.price })) vals["images"] = [("delete_all", )] for image in obj.images: vals["images"].append(("create", { "image": image.image, "title": image.title })) print("vals", vals) if replace_id: self.write([replace_id], vals) new_id = replace_id else: new_id = self.create(vals) return { "next": { "name": "product", "mode": "form", "active_id": new_id, }, "flash": "New product copied from %s" % obj.name, } def get_landed_cost(self, ids, context={}): vals = {} for obj in self.browse(ids): amt = Decimal(obj.purchase_price or 0) * Decimal( 1 + (obj.purchase_duty_percent or 0) / 100) * Decimal(1 + (obj.purchase_ship_percent or 0) / 100) amt_cur = amt * Decimal(obj.purchase_currency_rate or 1) vals[obj.id] = { "landed_cost": amt, "landed_cost_conv": amt_cur, } return vals def get_auto_list_price(self, ids, context={}): vals = {} for obj in self.browse(ids): if obj.landed_cost_conv and obj.gross_profit and obj.gross_profit != 100: vals[obj.id] = obj.landed_cost_conv / (1 - obj.gross_profit / 100) else: vals[obj.id] = None return vals def get_max_discount(self, ids, context={}): vals = {} for obj in self.browse(ids): if obj.sale_price and obj.landed_cost_conv: vals[obj.id] = max(0, (obj.sale_price - obj.landed_cost_conv) / obj.sale_price * 100) else: vals[obj.id] = None return vals def get_price_index(self, ids, context={}): vals = {} for obj in self.browse(ids): if obj.sale_price and obj.landed_cost_conv: vals[obj.id] = obj.sale_price / obj.landed_cost_conv else: vals[obj.id] = None return vals def update_prices(self, context={}): print("update_prices") data = context["data"] purchase_price = data.get("purchase_price") purchase_currency_rate = data.get("purchase_currency_rate") purchase_duty_percent = data.get("purchase_duty_percent") purchase_ship_percent = data.get("purchase_ship_percent") if purchase_price: landed_cost = purchase_price * \ (1 + (purchase_duty_percent or 0) / 100) * (1 + (purchase_ship_percent or 0) / 100) landed_cost_conv = landed_cost * (purchase_currency_rate or 1) else: landed_cost = None landed_cost_conv = None gross_profit = data.get("gross_profit") if landed_cost_conv and gross_profit and gross_profit != 100: auto_list_price = landed_cost_conv / (1 - gross_profit / 100) else: auto_list_price = None sale_price = data.get("sale_price") if sale_price and landed_cost_conv: max_discount = max(0, (sale_price - landed_cost_conv) / sale_price * 100) price_index = sale_price / landed_cost else: max_discount = None price_index = None data.update({ "landed_cost": landed_cost, "landed_cost_conv": landed_cost_conv, "auto_list_price": auto_list_price, "max_discount": max_discount, "price_index": price_index, }) return data def get_ecom_sale_price(self, ids, context={}): raise Exception("Deprecated!") vals = {} for obj in self.browse(ids): if obj.ecom_discount_percent: p = (obj.sale_price or 0) * (1 - obj.ecom_discount_percent / 100) has_disc = True elif obj.ecom_special_price: p = obj.ecom_special_price has_disc = True else: p = obj.sale_price or 0 has_disc = False vals[obj.id] = { "ecom_sale_price": p, "ecom_has_discount": has_disc, } return vals def create_variants(self, ids, context={}): # deprecated print("##################################") print("product.create_variants", ids) obj = self.browse(ids[0]) if obj.type != "master": raise Exception("Not a master product") if not obj.custom_options: raise Exception("No custom options for this product") variants = [{}] for opt in obj.custom_options: new_variants = [] for variant in variants: for opt_val in opt.values: new_variant = variant.copy() new_variant[opt.code] = opt_val.code new_variants.append(new_variant) variants = new_variants print("variants", len(variants), variants) count = 1 for variant in variants: vals = { "code": "%s_VARIANT_%.2d" % (obj.code, count), "name": obj.name, "type": "stock", "uom_id": obj.uom_id.id, "parent_id": obj.id, "location_id": obj.location_id.id, "attributes": [], } for k, v in variant.items(): name = "_CUST_OPT_" + k res = get_model("product.attribute").search( [["name", "=", name]]) if res: attr_id = res[0] else: attr_id = get_model("product.attribute").create( {"name": name}) vals["attributes"].append(("create", { "attribute_id": attr_id, "value": v, })) get_model("product").create(vals) count += 1 return { "flash": "%d variants created" % len(variants), } def get_variant_attributes(self, ids, context={}): print("get_variant_attributes", ids) vals = {} for obj in self.browse(ids): attrs = [] attr_options = {} for variant in obj.variants: for attr in variant.attributes: if not attr.attribute_id or not attr.option_id: continue attr_code = attr.attribute_id.code attr_name = attr.attribute_id.name attrs.append((attr_name, attr_code)) opt_code = attr.option_id.code opt_name = attr.option_id.name opt_sequence = attr.option_id.sequence if attr_code == "color": opt_image = variant.image or attr.option_id.image else: opt_image = attr.option_id.image #attr_options.setdefault(attr_code,[]).append((opt_sequence,opt_name,opt_code,opt_image)) attr_options.setdefault(attr_code, []).append( (opt_sequence, opt_name, opt_code)) attrs = list(set(attrs)) res = [] for attr_name, attr_code in sorted(attrs): attr_vals = { "code": attr_code, "name": attr_name, "values": [], } attr_options[attr_code] = list(set(attr_options[attr_code])) #for opt_sequence,opt_name,opt_code,opt_image in sorted(attr_options[attr_code]): for opt_sequence, opt_name, opt_code in sorted( attr_options[attr_code]): attr_vals["values"].append({ "sequence": opt_sequence, "code": opt_code, "name": opt_name, #"image": opt_image, }) res.append(attr_vals) vals[obj.id] = res print("vals", vals) return vals def get_customer_price(self, ids, context={}): # XXX: make it faster pricelist_id = context.get("pricelist_id") pricelist_ids = context.get("pricelist_ids") if pricelist_ids is None and pricelist_id: pricelist_ids = [pricelist_id] vals = {} for obj in self.browse(ids): sale_price = None discount_text = None discount_percent = None if pricelist_ids: min_sale_price = None for item in obj.pricelist_items: if item.list_id.id in pricelist_ids: sale_price = (item.price or 0) if min_sale_price is None or sale_price < min_sale_price: min_sale_price = sale_price discount_text = item.discount_text discount_percent = item.discount_percent sale_price = min_sale_price if sale_price is None: sale_price = (obj.sale_price or 0) has_discount = sale_price < (obj.sale_price or 0) vals[obj.id] = { "customer_price": sale_price, "customer_has_discount": has_discount, "customer_discount_text": discount_text, "customer_discount_percent": discount_percent, } return vals def get_stock_qty(self, ids, context={}): vals = {} for obj in self.browse(ids): qty = 0 for loc in obj.locations: qty += loc.stock_qty vals[obj.id] = qty return vals def get_stock_lots(self, ids, context={}): db = database.get_connection() res = db.query( "SELECT b.product_id,b.location_id,p.sale_price,l.id AS lot_id,l.number,l.weight,SUM(b.qty_virt) AS qty FROM stock_balance b JOIN stock_lot l ON l.id=b.lot_id JOIN product p ON p.id=b.product_id WHERE b.product_id IN %s GROUP BY b.product_id,b.location_id,p.sale_price,l.id,l.number,l.weight ORDER BY l.expiry_date,l.received_date", tuple(ids)) lots = {} prod_lots = {} for r in res: k = (r.product_id, r.lot_id) if k not in lots: lot_vals = { "id": r.lot_id, "qty": 0, } lots[k] = lot_vals prod_lots.setdefault(r.product_id, []).append(lot_vals) lots[k]["qty"] += r.qty vals = {} for obj in self.browse(ids): vals[obj.id] = [ l["id"] for l in prod_lots.get(obj.id, []) if l["qty"] > 0 ] return vals def to_draft(self, ids, context={}): for obj in self.browse(ids): obj.write({"state": "draft", "is_published": False}) #XXX def approve(self, ids, context={}): for obj in self.browse(ids): res = self.search([["code", "=", obj.code], ["state", "=", "approved"], ["company_id", "=", obj.company_id.id]]) if res: repl_id = res[0] obj.copy(context={"replace_id": repl_id}) obj.write({"active": False}) else: vals = { "state": "approved", } if not obj.parent_id: group_ids = [] #XXX for group in obj.groups: group_ids.append(group.id) res = get_model("product.group").search( [["code", "=", "new"]]) if res and res[0] not in group_ids: group_ids.append(res[0]) vals.update({ "is_published": True, "groups": [("set", group_ids)] }) if not obj.approve_date: t = time.strftime("%Y-%m-%d %H:%M:%S") vals.update({"approve_date": t}) obj.write(vals) return { "flash": "Products approved", } def ecom_preview(self, ids, context={}): prod_id = ids[0] return { "next": { "type": "url", "url": "/ecom_product?product_id=%s" % prod_id, } } def get_sale_price_order_uom(self, ids, context={}): vals = {} for obj in self.browse(ids): factor = obj.sale_to_invoice_uom_factor or 1 vals[obj.id] = math.ceil((obj.sale_price or 0) * factor) return vals def create_thumbnails(self, ids, context={}): print("Product.create_thumbnails", ids) for obj in self.browse(ids): if not obj.image: continue dbname = database.get_active_db() if not dbname: return None fdir = os.path.join(os.getcwd(), "static", "db", dbname, "files") path = os.path.join(fdir, obj.image) basename, ext = os.path.splitext(obj.image) res = "," in basename if not res: rand = base64.urlsafe_b64encode(os.urandom(8)).decode() res = os.path.splitext(obj.image) basename, ext = res fname2 = basename + "," + rand + ext #rename image dest_path = fdir + "/" + fname2 print("destination path and file name ", dest_path) cmd = "cp %s %s" % (path, dest_path) os.system(cmd) obj.write({ 'image': fname2, }) utils.create_thumbnails(fname2) else: print("called", obj.image) utils.create_thumbnails(obj.image)
class ImportStatement(Model): _name = "import.statement" _transient = True _fields = { "account_id": fields.Many2One("account.account", "Account", required=True, on_delete="cascade"), "date_start": fields.Date("From Date", required=True), "date_end": fields.Date("To Date", required=True), "file": fields.File("CSV File", required=True), "encoding": fields.Selection([["utf-8", "UTF-8"], ["tis-620", "TIS-620"]], "Encoding", required=True), } _defaults = { "encoding": "utf-8", } def import_data(self, ids, context={}): obj = self.browse(ids[0]) acc_id = obj.account_id.id dbname = get_active_db() data = open(os.path.join("static", "db", dbname, "files", obj.file), "rb").read().decode(obj.encoding) found_delim = False for delim in (",", ";", "\t"): try: try: rd = csv.reader(StringIO(data), delimiter=delim) except: raise Exception("Invalid CSV file") headers = next(rd) headers = [h.strip() for h in headers] for h in [ "Date", "Description", "Spent", "Received", "Balance" ]: if not h in headers: raise Exception("Missing header: '%s'" % h) found_delim = True break except: pass if not found_delim: raise Exception("Failed to open CSV file") rows = [r for r in rd] if not rows: raise Exception("Statement is empty") formats = [ "%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d/%m/%y", "%m/%d/%y", "%d-%m-%y", "%m-%d-%y" ] date_fmt = None for fmt in formats: fmt_ok = True for row in rows: vals = dict(zip(headers, row)) date = vals["Date"].strip() if not date: continue try: datetime.strptime(date, fmt) except: fmt_ok = False break if fmt_ok: date_fmt = fmt break if not date_fmt: raise Exception("Could not detect date format") lines = [] for i, row in enumerate(rows): vals = dict(zip(headers, row)) try: date = vals["Date"].strip() if not date: raise Exception("Missing date") date = datetime.strptime(date, date_fmt).strftime("%Y-%m-%d") if date < obj.date_start: raise Exception( "Transaction date is before start date: %s" % date) if date > obj.date_end: raise Exception("Transaction date is after end date: %s" % date) balance = vals["Balance"].strip().replace(",", "") if not balance: raise Exception("missing balance") try: balance = float(balance) except: raise Exception("Failed to read Balance amount") description = vals.get("Description").strip() try: spent = vals["Spent"].strip().replace(",", "") spent = float(spent) if spent else 0 except: raise Exception("Failed to read Spent amount") try: received = vals["Received"].strip().replace(",", "") received = float(received) if received else 0 except: raise Exception("Failed to read Received amount") if not spent and not received: raise Exception("No spent or received amount") if spent and received: raise Exception( "Can not have both Spent and Received amounts on the same line" ) line_vals = { "date": date, "balance": balance, "description": description, "spent": spent, "received": received, } lines.append(line_vals) except Exception as e: raise Exception("Error on line %d (%s)" % (i + 2, e)) if not lines: raise Exception("Empty statement") first_bal = lines[0]["balance"] + lines[0]["spent"] - lines[0][ "received"] first_date = lines[0]["date"] res = get_model("account.statement.line").search( [["statement_id.account_id", "=", acc_id], ["date", "<", first_date]], order="date desc,id desc", limit=1) if res: prev_line = get_model("account.statement.line").browse(res[0]) prev_bal = prev_line.balance if abs(first_bal - prev_bal) > 0.001: raise Exception("Invalid balance: previous balance is %.2f" % prev_bal) st_vals = { "account_id": acc_id, "date_start": obj.date_start, "date_end": obj.date_end, "balance_start": first_bal, "lines": [("create", v) for v in lines], } stmt_id = get_model("account.statement").create(st_vals) return { "next": { "name": "statement", "mode": "page", "active_id": stmt_id, } } def onchange_account(self, context={}): data = context["data"] account_id = data["account_id"] acc = get_model("account.account").browse(account_id) if acc.statements: st = acc.statements[0] d = datetime.strptime(st.date_end, "%Y-%m-%d") + timedelta(days=1) data["date_start"] = d.strftime("%Y-%m-%d") data["date_end"] = (d + relativedelta(day=31)).strftime("%Y-%m-%d") else: data["date_start"] = (datetime.today() - relativedelta(day=1)).strftime("%Y-%m-%d") data["date_end"] = (datetime.today() + relativedelta(day=31)).strftime("%Y-%m-%d") return data
class Employee(Model): _name = "hr.employee" _string = "Employee" _name_field = "first_name" # XXX _multi_company = True _key = ["code", "company_id"] _export_field = "code" _fields = { "code": fields.Char("Employee Code", search=True), "department_id": fields.Many2One("hr.department", "Department", search=True), "title": fields.Selection( [["mr", "Mr."], ["mrs", "Mrs."], ["miss", "Miss"], ["ms", "Ms."]], "Title"), "first_name": fields.Char("First Name", search=True, translate=True), "last_name": fields.Char("Last Name", required=True, search=True, translate=True), "hire_date": fields.Date("Hire Date"), "work_status": fields.Selection([["working", "Working"], ["dismissed", "Dismissed"], ["resigned", "Resigned"], ["died", "Died"]], "Work Status"), "work_type": fields.Selection( [["monthly", "Monthly"], ["daily", "Daily"], ["hourly", "Job"]], "Work Type"), "resign_date": fields.Date("Resign Date"), "position": fields.Char("Position", search=True), "birth_date": fields.Date("Birth Date"), "age": fields.Integer("Age", function="get_age"), "gender": fields.Selection([["male", "Male"], ["female", "Female"]], "Gender"), "marital_status": fields.Selection([["single", "Single"], ["married", "Married"], ["divorced", "Divorced"], ["widowed", "Widowed"]], "Marital Status"), "addresses": fields.One2Many("address", "employee_id", "Address"), "id_no": fields.Char("ID No."), "drive_license_type": fields.Selection([["car", "Car"], ['motorcycle', 'Motorcycle']], "Driving License"), "drive_license_no": fields.Char("Driving License No."), "country_id": fields.Many2One("country", "Country"), "bank_account": fields.Char("Bank Account"), "salary": fields.Decimal("Salary"), "picture": fields.File("Picture"), "tax_no": fields.Char("Taxpayer ID No."), "spouse_first_name": fields.Char("Spouse First Name"), "spouse_last_name": fields.Char("Spouse Last Name"), "spouse_title": fields.Selection([["mr", "Mr."], ["ms", "Ms."]], "Spouse Title"), "spouse_birth_date": fields.Date("Spouse Birth Date"), "spouse_tax_no": fields.Char("Spouse Tax ID No"), "spouse_status": fields.Selection( [["married", "Married existed throughout this tax year"], ["married_new", "Married during this tax year"], ["divorced", "Divorced during tax year"], ["deceased", "Deceased during tax year"]], "Spouse Status"), "spouse_filing_status": fields.Selection( [["joint", "Has income and file joint return"], ["separate", "Has income and file separate tax return"], ["no_income", "Has no income"]], "Spouse Filing Status"), "num_child1": fields.Integer("No. of Children #1 (C3)"), "num_child2": fields.Integer("No. of Children #2 (C3)"), "social_no": fields.Char("Social No."), "social_register": fields.Boolean("Register Soc. Secur."), "social_calc_method": fields.Selection( [["regular", "Regular Rate"], ["none", "Not Participate"], ["special", "Special Rate"]], "Calc. Method"), "prov_fund_no": fields.Char("Prov. Fund No."), "prov_open_date": fields.Char("Opened Prov. Fund A/C Date"), "prov_rate_employer": fields.Decimal("Employer Contribution (%)"), "prov_rate_employee": fields.Decimal("Employee Contribution (%)"), "gov_pension_fund": fields.Decimal("Gov. Pension Fund Amount (B2)"), "teacher_fund": fields.Decimal("Teacher Aid Fund Amount (B3)"), "old_disabled": fields.Decimal("Older than 65 or disabled (personal, B4)"), "old_disabled_spouse": fields.Decimal("Older than 65 or disabled (spouse, B5)"), "severance_pay": fields.Decimal("Severance Pay (B6)"), "education_donation": fields.Decimal("Education Donations (A8)"), "other_donation": fields.Decimal("Other Donations (A10)"), "house_deduct": fields.Decimal("Exemption for home buyer (A13)"), "wht_amount": fields.Decimal("Withholding Tax Amount (A15)"), "father_id_no": fields.Char("Father ID No. (C4)"), "mother_id_no": fields.Char("Mother ID No. (C4)"), "spouse_father_id_no": fields.Char("Father of spouse ID No. (C4)"), "spouse_mother_id_no": fields.Char("Mother of spouse ID No. (C4)"), "disabled_support": fields.Decimal("Disabled person support (C5)"), "parent_health_insurance": fields.Decimal("Parent Health Insurance (C6)"), "life_insurance": fields.Decimal("Life Insurance (C7)"), "retirement_mutual_fund": fields.Decimal("Retirement Mutual Fund (C9)"), "long_term_equity_fund": fields.Decimal("Long Term Equity Fund (C10)"), "interest_residence": fields.Decimal("Interest paid for residence (C11)"), "other_deduct": fields.Decimal("Other Deductions (C12)"), "comments": fields.One2Many("message", "related_id", "Comments"), "active": fields.Boolean("Active"), "time_in": fields.DateTime("Last Sign In", function="get_attend", function_multi=True), "time_out": fields.DateTime("Last Sign Out", function="get_attend", function_multi=True), "attend_state": fields.Selection([["absent", "Absent"], ["present", "Present"]], "Status", function="get_attend", function_multi=True), "user_id": fields.Many2One("base.user", "User", search=True), "payslips": fields.One2Many("hr.payslip", "employee_id", "Payslips"), "documents": fields.One2Many("document", "related_id", "Documents"), "phone": fields.Char("Phone", search=True), "approver_id": fields.Many2One("base.user", "Approver"), "company_id": fields.Many2One("company", "Company"), "leave_types": fields.Many2Many("hr.leave.type", "Leave Types"), "attendance_id": fields.Integer("Attendance ID"), "email": fields.Char("Email", search=True), 'profile_id': fields.Many2One("hr.payitem.profile", "Pay Item Profile"), 'schedule_id': fields.Many2One("hr.schedule", "Working Schedule"), 'leaves': fields.One2Many('hr.leave', 'employee_id', 'Leaves'), } def _get_code(self, context={}): while 1: code = get_model("sequence").get_number("employee") if not code: return None res = self.search([["code", "=", code]]) if not res: return code get_model("sequence").increment("employee") _defaults = { "active": True, "work_status": "working", "code": _get_code, "company_id": lambda *a: get_active_company(), } _order = "code,first_name,last_name" def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): if obj.first_name: name = obj.first_name + " " + obj.last_name else: name = obj.last_name if obj.code: name += " [%s]" % obj.code vals.append((obj.id, name)) return vals def name_search(self, name, condition=[], limit=None, context={}): cond = [[ "or", ["first_name", "ilike", "%" + name + "%"], ["last_name", "ilike", "%" + name + "%"], ["code", "ilike", "%" + name + "%"] ], condition] ids = self.search(cond, limit=limit) return self.name_get(ids, context) def get_age(self, ids, context={}): vals = {} cr_year = int(time.strftime('%Y')) for obj in self.browse(ids): if obj.birth_date: age = cr_year - int(obj.birth_date[0:4]) else: age = 0 vals[obj.id] = age return vals def get_attend(self, ids, context={}): vals = {} for obj in self.browse(ids): # user_id=obj.user_id.id # if user_id: # db=database.get_connection() #res=db.get("SELECT MAX(time) AS time_in FROM hr_attendance WHERE user_id=%s AND action='sign_in'",user_id) # time_in=res.time_in #res=db.get("SELECT MAX(time) AS time_out FROM hr_attendance WHERE user_id=%s AND action='sign_out'",user_id) # time_out=res.time_out # else: # time_in=None # time_out=None db = database.get_connection() res = db.get( "SELECT MAX(time) AS time_in FROM hr_attendance WHERE employee_id=%s AND action='sign_in'", obj.id) time_in = res.time_in res = db.get( "SELECT MAX(time) AS time_out FROM hr_attendance WHERE employee_id=%s AND action='sign_out'", obj.id) time_out = res.time_out if time_in: if time_out and time_out > time_in: state = "absent" else: today = time.strftime("%Y-%m-%d") if time_in.startswith(today): state = "present" else: state = "absent" # should not show timeout of anotherday # if not time_out.startswith(today): # time_out=None else: state = "absent" vals[obj.id] = { "time_in": time_in, "time_out": time_out, "attend_state": state, } return vals def get_address(self, ids, context={}): obj = self.browse(ids)[0] if not obj.addresses: return "" addr = obj.addresses[0] res = addr.get_address_text() return res[addr.id] def onchange_num_child(self, context={}): data = context["data"] setting = get_model("hr.payroll.settings").browse(1) child_alw_limit = setting.child_alw_limit or 0 child_total = (data['num_child1'] or 0) + (data['num_child2'] or 0) if child_alw_limit and child_total > child_alw_limit: data['num_child1'] = 0 data['num_child2'] = 0 return data
class Document(Model): _name = "document" _string = "Document" _audit_log = True _fields = { "file": fields.File("File"), "categ_id": fields.Many2One("document.categ", "Category", search=True), "description": fields.Text("Description", search=True), "contact_id": fields.Many2One("contact", "Contact", search=True), "related_id": fields.Reference( [["sale.quot", "Quotation"], ["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"], ["job", "Service Order"], ["project", "Project"], ["hr.employee", "Employee"], ["account.invoice", "Invoice"], ["account.payment", "Payment"], ["account.track.categ", "Tracking Category"]], "Related To"), "date": fields.Date("Date Created", required=True, search=True), "attachments": fields.One2Many("attach", "related_id", "Attachments"), "comments": fields.One2Many("message", "related_id", "Comments"), "expiry_date": fields.Date("Expiry Date", search=True), "expiring_soon": fields.Boolean("Expiring Soon", store=False, function_search="search_expiring"), "expired": fields.Boolean("Expired", function="get_expired", function_search="search_expired"), "create_job": fields.Boolean("Automatically Create Job To Renew"), # XXX: deprecated "active": fields.Boolean("Active"), "days_remaining": fields.Integer("Days Remaining", function="get_days_remaining"), "reminders": fields.One2Many("reminder", "doc_id", "Reminders"), "state": fields.Selection([["draft", "Draft"], ["verified", "Verified"]], "Status"), "share": fields.Boolean("Share With Contact"), } _order = "date desc" def _get_contact(self, context={}): defaults = context.get("defaults") if not defaults: return related_id = defaults.get("related_id") if not related_id: return model, model_id = related_id.split(",") model_id = int(model_id) if model == "job": job = get_model("job").browse(model_id) return job.contact_id.id elif model == "sale.quot": quot = get_model("sale.quot").browse(model_id) return quot.contact_id.id _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d"), "contact_id": _get_contact, "active": True, "state": "draft", } _constraints = ["_check_date"] def _check_date(self, ids, context={}): for obj in self.browse(ids): if obj.expiry_date: if obj.expiry_date and obj.expiry_date < obj.date: raise Exception("Expiry date is before creation date") def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): if obj.file: s, ext = os.path.splitext(obj.file) name = s.rsplit(",")[0] + ext else: name = "#%d" % obj.id vals.append((obj.id, name)) return vals def search_expiring(self, clause, context={}): d = datetime.date.today() + datetime.timedelta(days=35) return [["expiry_date", "<=", d.strftime("%Y-%m-%d")]] def onchange_categ(self, context={}): data = context["data"] categ_id = data.get("categ_id") if not categ_id: return categ = get_model("document.categ").browse(categ_id) expire_after = categ.expire_after if expire_after: expire_after = expire_after.strip() t0 = datetime.datetime.strptime(data.get("date"), "%Y-%m-%d") p = expire_after[-1] n = int(expire_after[:-1]) if p == "y": dt = relativedelta(years=n) elif p == "m": dt = relativedelta(months=n) elif p == "w": dt = relativedelta(weeks=n) elif p == "d": dt = relativedelta(days=n) exp_date = (t0 + dt).strftime("%Y-%m-%d") else: exp_date = None return { "expiry_date": exp_date, "create_job": categ.create_job, } def onchange_file(self, context={}): print("onchange_file") data = context["data"] filename = data["file"] if not filename: return categ_id = data["categ_id"] if not categ_id: return categ = get_model("document.categ").browse(categ_id) fmt = categ.file_name if not fmt: return contact_id = data.get("contact_id") if contact_id: contact = get_model("contact").browse(contact_id) else: contact = None date = data["date"] vals = { "contact_code": contact and contact.code or "", "doc_code": categ.code or "", "Y": date[0:4], "y": date[2:4], "m": date[5:7], "d": date[8:10], } filename2 = fmt % vals res = os.path.splitext(filename) rand = base64.urlsafe_b64encode(os.urandom(8)).decode() filename2 += "," + rand + res[1] if filename2 != filename: path = utils.get_file_path(filename) path2 = utils.get_file_path(filename2) os.rename(path, path2) return { "vals": { "file": filename2, } } def get_expired(self, ids, context={}): vals = {} for obj in self.browse(ids): if obj.expiry_date: vals[obj.id] = obj.expiry_date < time.strftime("%Y-%m-%d") else: vals[obj.id] = False return vals def search_expired(self, clause, context={}): return [["expiry_date", "<", time.strftime("%Y-%m-%d")]] def do_create_job(self, ids, context={}): for obj in self.browse(ids): categ = obj.categ_id tmpl = categ.job_template_id if not tmpl: continue job_id = tmpl.create_job(context={"contact_id": obj.contact_id.id}) obj.write({"create_job": False, "related_id": "job,%d" % job_id}) def create_jobs(self, context={}): try: for categ in get_model("document.categ").search_browse( [["create_job", "=", True]]): days = categ.create_days and int(categ.create_days) or 0 d = (datetime.date.today() + datetime.timedelta(days=days)).strftime("%Y-%m-%d") for doc in self.search_browse([["expiry_date", "<=", d], ["create_job", "=", True]]): doc.do_create_job() except Exception as e: print("WARNING: Failed to create jobs") import traceback traceback.print_exc() def check_days_before_expiry(self, ids, days=None, days_from=None, days_to=None, categs=None, context={}): print("Document.check_days_before_expiry", ids, days) cond = [] if days != None: d = (datetime.date.today() + datetime.timedelta(days=days)).strftime("%Y-%m-%d") cond.append(["expiry_date", "=", d]) if days_from != None: d = (datetime.date.today() + datetime.timedelta(days=days_from)).strftime("%Y-%m-%d") cond.append(["expiry_date", "<=", d]) if days_to != None: d = (datetime.date.today() + datetime.timedelta(days=days_to)).strftime("%Y-%m-%d") cond.append(["expiry_date", ">=", d]) if categs: cond.append(["categ_id.code", "in", categs]) if ids: cond.append(["ids", "in", ids]) ids = self.search(cond) return ids def get_days_remaining(self, ids, context={}): vals = {} d = datetime.datetime.now() for obj in self.browse(ids): if obj.expiry_date: vals[obj.id] = ( datetime.datetime.strptime(obj.expiry_date, "%Y-%m-%d") - d).days else: vals[obj.id] = None return vals def create_reminders(self, ids, context={}): for obj in self.browse(ids): categ = obj.categ_id if not categ: continue obj.write({"reminders": [("delete_all", )]}) for tmpl in categ.reminder_templates: s = tmpl.scheduled_date.strip() days = int(s) d = datetime.datetime.strptime( obj.expiry_date, "%Y-%m-%d") + datetime.timedelta(days=days) ctx = {"doc": obj} subject = render_template(tmpl.subject, ctx) body = render_template(tmpl.body or "", ctx) vals = { "scheduled_date": d.strftime("%Y-%m-%d"), "doc_id": obj.id, "user_id": tmpl.user_id.id, "subject": subject, "body": body, } get_model("reminder").create(vals) def delete_pending_reminders(self, ids, context={}): for obj in self.browse(ids): for reminder in obj.reminders: if reminder.state == "pending": reminder.delete() def create(self, vals, **kw): new_id = super().create(vals, **kw) obj = self.browse(new_id) obj.create_reminders() return new_id def write(self, ids, vals, **kw): old_categs = {} old_dates = {} for obj in self.browse(ids): old_categs[obj.id] = obj.categ_id.id old_dates[obj.id] = obj.expiry_date super().write(ids, vals, **kw) for obj in self.browse(ids): if obj.categ_id.id != old_categs[ obj.id] or obj.expiry_date != old_dates[obj.id]: obj.create_reminders() def to_draft(self, ids, context={}): for obj in self.browse(ids): obj.write({"state": "draft"}) def to_verified(self, ids, context={}): for obj in self.browse(ids): obj.write({"state": "verified"}) def delete(self, ids, **kw): files = [] for obj in self.browse(ids): if obj.file: files.append(obj.file) super().delete(ids, **kw) for f in files: path = utils.get_file_path(f) os.remove(path)
class Theme(Model): _name = "theme" _string = "Theme" _fields = { "name": fields.Char("Name", required=True), "description": fields.Text("Description"), "file": fields.File("ZIP File"), "templates": fields.One2Many("template", "theme_id", "Templates"), } _defaults = { "state": "inactive", } def activate(self, ids, context={}): obj = self.browse(ids)[0] all_ids = self.search([]) self.write(all_ids, {"state": "inactive"}) obj.write({"state": "active"}) obj.update() def update(self, ids, context={}): obj = self.browse(ids)[0] obj.export_static_files() obj.load_templates() def export_static_files(self, ids, context={}): obj = self.browse(ids)[0] theme = obj.name dbname = get_active_db() if obj.file: zip_path = utils.get_file_path(obj.file) zf = zipfile.ZipFile(zip_path) for n in zf.namelist(): if not n.startswith("static/"): continue if n[-1] == "/": continue n2 = n[7:] if n2.find("..") != -1: continue data = zf.read(n) f_path = "static/db/" + dbname + "/themes/" + theme + "/" + n2 dir_path = os.path.dirname(f_path) if not os.path.exists(dir_path): os.makedirs(dir_path) print("export file", f_path) open(f_path, "wb").write(data) else: export_module_file_all("themes/" + theme + "/static", "static/db/" + dbname + "/themes/" + theme) def load_templates(self, ids, context={}): obj = self.browse(ids[0]) if obj.file: zip_path = utils.get_file_path(obj.file) zf = zipfile.ZipFile(zip_path) for n in zf.namelist(): if not n.startswith("templates/"): continue if not n.endswith(".hbs"): continue n2 = n[10:-4] if n2.find("..") != -1: continue print("load template", n2) data = zf.read(n) vals = { "name": n2, "template": data.decode(), "theme_id": obj.id, } get_model("template").merge(vals) else: theme = obj.name loaded_modules = module.get_loaded_modules() for m in reversed(loaded_modules): if not pkg_resources.resource_isdir( m, "themes/" + theme + "/templates"): continue for f in pkg_resources.resource_listdir( m, "themes/" + theme + "/templates"): if not f.endswith(".hbs"): continue f2 = f[:-4] print("load template", f2) data = pkg_resources.resource_string( m, "themes/" + theme + "/templates/" + f) vals = { "name": f2, "template": data.decode(), "theme_id": obj.id, } get_model("template").merge(vals)
class Settings(Model): _name = "settings" _key = ["name"] _audit_log = True _fields = { "name": fields.Char("Display Name"), # not used any more... "legal_name": fields.Char("Legal Name"), # not used any more... "company_type_id": fields.Many2One("company.type", "Organization Type"), "currency_id": fields.Many2One("currency", "Default Currency", multi_company=True), "account_receivable_id": fields.Many2One("account.account", "Account Receivable", multi_company=True), "tax_receivable_id": fields.Many2One("account.tax.rate", "Account Receivable Tax"), "account_payable_id": fields.Many2One("account.account", "Account Payable", multi_company=True), "tax_payable_id": fields.Many2One("account.tax.rate", "Account Payable Tax"), "year_end_day": fields.Selection(_days, "Financial Year End (Day)"), "year_end_month": fields.Selection(_months, "Financial Year End (Month)"), "lock_date": fields.Date("Lock Date"), "nf_email": fields.Char("Email to Netforce"), # XXX: deprecated "share_settings": fields.One2Many("share.access", "settings_id", "Sharing Settings"), "currency_gain_id": fields.Many2One("account.account", "Currency Gain Account", multi_company=True), "currency_loss_id": fields.Many2One("account.account", "Currency Loss Account", multi_company=True), "unpaid_claim_id": fields.Many2One("account.account", "Unpaid Expense Claims Account", multi_company=True), "retained_earnings_account_id": fields.Many2One("account.account", "Retained Earnings Account", multi_company=True), "logo": fields.File("Company Logo", multi_company=True), "package": fields.Char("Package", readonly=True), "version": fields.Char("Version"), "tax_no": fields.Char("Tax ID Number", multi_company=True), "branch_no": fields.Char("Branch Number", multi_company=True), "addresses": fields.One2Many("address", "settings_id", "Addresses", function="get_addresses"), "date_format": fields.Char("Date Format"), "use_buddhist_date": fields.Boolean("Use Buddhist Date"), "phone": fields.Char("Phone", multi_company=True), "fax": fields.Char("Fax", multi_company=True), "website": fields.Char("Website", multi_company=True), "root_url": fields.Char("Root URL"), "sale_journal_id": fields.Many2One("account.journal", "Sales Journal"), "purchase_journal_id": fields.Many2One("account.journal", "Purchase Journal"), "pay_in_journal_id": fields.Many2One("account.journal", "Receipts Journal"), "pay_out_journal_id": fields.Many2One("account.journal", "Disbursements Journal"), "general_journal_id": fields.Many2One("account.journal", "General Journal"), "default_address_id": fields.Many2One("address", "Default Address", function="get_default_address"), "ar_revenue_id": fields.Many2One("account.account", "Revenue Account", multi_company=True), # XXX: rename for report "input_report_id": fields.Many2One("account.account", "Input Vat Account", multi_company=True), # XXX: rename for report "output_report_id": fields.Many2One("account.account", "Output Vat Account", multi_company=True), # XXX: rename for report "wht3_report_id": fields.Many2One("account.account", "WHT3 Account", multi_company=True), # XXX: rename for report "wht53_report_id": fields.Many2One("account.account", "WHT53 Account", multi_company=True), "sale_copy_picking": fields.Boolean("Auto-copy sales orders to goods issue"), "sale_copy_invoice": fields.Boolean("Auto-copy sales orders to customer invoice"), "sale_copy_production": fields.Boolean("Auto-copy sales orders to production"), "rounding_account_id": fields.Many2One("account.account", "Rounding Account", multi_company=True), "production_waiting_suborder": fields.Boolean("Wait Sub-Order"), # XXX: check this "anon_profile_id": fields.Many2One("profile", "Anonymous User Profile"), "pick_in_journal_id": fields.Many2One("stock.journal", "Goods Receipt Journal"), "pick_out_journal_id": fields.Many2One("stock.journal", "Goods Issue Journal"), "pick_internal_journal_id": fields.Many2One("stock.journal", "Goods Transfer Journal"), "stock_count_journal_id": fields.Many2One("stock.journal", "Stock Count Journal"), "landed_cost_journal_id": fields.Many2One("stock.journal", "Landed Cost Journal"), "transform_journal_id": fields.Many2One("stock.journal", "Transform Journal"), "production_journal_id": fields.Many2One("stock.journal", "Production Journal"), "product_borrow_journal_id": fields.Many2One("stock.journal", "Borrow Request Journal"), "stock_cost_mode": fields.Selection( [["periodic", "Periodic"], ["perpetual", "Perpetual"]], "Inventory Costing Mode"), "landed_cost_variance_account_id": fields.Many2One("account.account", "Landed Cost Variance Account", multi_company=True), "est_ship_account_id": fields.Many2One("account.account", "Estimate Shipping Account", multi_company=True), "est_duty_account_id": fields.Many2One("account.account", "Estimate Duty Account", multi_company=True), "act_ship_account_id": fields.Many2One("account.account", "Actual Shipping Account", multi_company=True), "act_duty_account_id": fields.Many2One("account.account", "Actual Duty Account", multi_company=True), "menu_icon": fields.File("Menu Icon"), "stock_cost_auto_compute": fields.Boolean("Auto Compute Cost"), "purchase_copy_picking": fields.Boolean("Auto-copy purchase orders to goods receipt"), "purchase_copy_invoice": fields.Boolean("Auto-copy purchase orders to supplier invoice"), "lot_expiry_journal_id": fields.Many2One("stock.journal", "Lot Expiry Journal"), "auto_create_delivery_order": fields.Boolean("Auto-create delivery orders"), } _defaults = { "package": "free", } def get_address_str(self, ids, context={}): obj = self.browse(ids[0]) if not obj.addresses: return "" addr = obj.addresses[0] return addr.name_get()[0][1] def write(self, ids, vals, **kw): res = super().write(ids, vals, **kw) if "date_format" in vals or "use_buddhist_date" in vals: static.clear_translations() # XXX: rename this def get_fiscal_year_end(self, date=None): if date: d0 = datetime.datetime.strptime(date, "%Y-%m-%d").date() else: d0 = datetime.date.today() settings = self.browse(1) if not settings.year_end_month or not settings.year_end_day: raise Exception("Missing fiscal year end") month = int(settings.year_end_month) day = int(settings.year_end_day) d = datetime.date(d0.year, month, day) if d < d0: d += relativedelta(years=1) return d.strftime("%Y-%m-%d") def get_fiscal_year_start(self, date=None): d1 = self.get_fiscal_year_end(date) d = datetime.datetime.strptime(d1, "%Y-%m-%d") - relativedelta( years=1) + datetime.timedelta(days=1) return d.strftime("%Y-%m-%d") def get_default_address(self, ids, context={}): vals = {} for obj in self.browse(ids): vals[obj.id] = obj.addresses and obj.addresses[0].id or None return vals def get_addresses(self, ids, context={}): vals = {} comp_id = access.get_active_company() for obj in self.browse(ids): res = get_model("address").search( [["settings_id", "=", obj.id], [ "or", ["company_id", "=", None], ["company_id", "child_of", comp_id] ]]) vals[obj.id] = res return vals
class ConvBal(Model): _name = "conv.bal" _transient = True _fields = { "date": fields.Date("Conversion Date", required=True), "accounts": fields.One2Many("conv.account", "conv_id", "Account Balances"), "sale_invoices": fields.One2Many("conv.sale.invoice", "conv_id", "Sales Invoices"), "purch_invoices": fields.One2Many("conv.purch.invoice", "conv_id", "Purchase Invoices"), "total_debit": fields.Decimal("Total Debit", function="get_total", function_multi=True), "total_credit": fields.Decimal("Total Credit", function="get_total", function_multi=True), "total_sale": fields.Decimal("Total Amount Due", function="get_total", function_multi=True), "total_purch": fields.Decimal("Total Amount Due", function="get_total", function_multi=True), "total_ar": fields.Decimal("Account Receivable Balance", function="get_total", function_multi=True), "total_ap": fields.Decimal("Account Payable Balance", function="get_total", function_multi=True), "move_id": fields.Many2One("account.move", "Opening Entry"), "file": fields.File("CSV File"), "date_fmt": fields.Char("Date Format"), } _defaults = { "date": lambda *a: time.strftime("%Y-%m-01"), "date_fmt": "%m/%d/%Y", } def get_total(self, ids, context={}): vals = {} for obj in self.browse(ids): total_debit = 0 total_credit = 0 total_sale = 0 total_purch = 0 total_ar = 0 total_ap = 0 for acc in obj.accounts: total_debit += acc.debit or 0 total_credit += acc.credit or 0 if acc.account_id.type == "receivable": total_ar += (acc.debit or 0) - (acc.credit or 0) if acc.account_id.type == "payable": total_ap += (acc.credit or 0) - (acc.debit or 0) for inv in obj.sale_invoices: total_sale += inv.amount_due for inv in obj.purch_invoices: total_purch += inv.amount_due vals[obj.id] = { "total_debit": total_debit, "total_credit": total_credit, "total_sale": total_sale, "total_purch": total_purch, "total_ar": total_ar, "total_ap": total_ap, } return vals def next1(self, ids, context={}): obj = self.browse(ids)[0] if obj.total_debit - obj.total_credit != 0: raise Exception("Conversion balance is not balanced") return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal2", } } def next2(self, ids, context={}): obj = self.browse(ids)[0] check_dupplicate_number(lines=obj.sale_invoices) return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal3", } } def back2(self, ids, context={}): obj = self.browse(ids)[0] return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal1", } } def next3(self, ids, context={}): obj = self.browse(ids)[0] check_dupplicate_number(lines=obj.purch_invoices) obj.create_open_entry() obj.create_sale_invoices() obj.create_purch_invoices() return { "next": { "name": "journal_entry", "mode": "form", "active_id": obj.move_id.id, }, "flash": "Conversion balance created successfully", } def back3(self, ids, context={}): obj = self.browse(ids)[0] return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal2", } } def create_open_entry(self, ids, context={}): obj = self.browse(ids)[0] settings = get_model("settings").browse(1) desc = "Conversion balance %s" % obj.date if not settings.general_journal_id: raise Exception("General journal not found") journal_id = settings.general_journal_id.id vals = { "journal_id": journal_id, "number": "OPENING ENTRY", "date": obj.date, "narration": desc, } move_id = get_model("account.move").create(vals) for acc in obj.accounts: if acc.account_id.type in ("receivable", "payable"): continue line_vals = { "move_id": move_id, "description": desc, "account_id": acc.account_id.id, "debit": acc.debit or 0, "credit": acc.credit or 0, "amount_cur": acc.amount_cur, "track_id": acc.track_id and acc.track_id.id or None, "track2_id": acc.track2_id and acc.track2_id.id or None, } line_id = get_model("account.move.line").create(line_vals) for inv in obj.sale_invoices: amt = inv.amount_due line_vals = { "move_id": move_id, "description": desc + ", %s" % inv.number, "account_id": inv.account_id.id, "debit": amt > 0 and amt or 0, "credit": amt < 0 and -amt or 0, "contact_id": inv.contact_id.id, "amount_cur": inv.amount_cur, "track_id": inv.track_id and inv.track_id.id or None, "track2_id": inv.track2_id and inv.track2_id.id or None, } line_id = get_model("account.move.line").create(line_vals) inv.write({"move_line_id": line_id}) for inv in obj.purch_invoices: amt = -inv.amount_due line_vals = { "move_id": move_id, "description": desc + ", %s" % inv.number, "account_id": inv.account_id.id, "debit": amt > 0 and amt or 0, "credit": amt < 0 and -amt or 0, "contact_id": inv.contact_id.id, "amount_cur": -inv.amount_cur if inv.amount_cur is not None else None, "track_id": inv.track_id and inv.track_id.id or None, "track2_id": inv.track2_id and inv.track2_id.id or None, } line_id = get_model("account.move.line").create(line_vals) inv.write({"move_line_id": line_id}) get_model("account.move").post([move_id]) obj.write({"move_id": move_id}) def create_sale_invoices(self, ids, context={}): obj = self.browse(ids)[0] settings = get_model("settings").browse(1) desc = "Conversion balance %s" % obj.date for inv in obj.sale_invoices: vals = { "type": "out", "inv_type": inv.amount_due >= 0 and "invoice" or "credit", "contact_id": inv.contact_id.id, "date": inv.date, "due_date": inv.due_date, "number": inv.number, "ref": inv.ref, "memo": desc, "lines": [], "state": "waiting_payment", "account_id": inv.account_id.id, "reconcile_move_line_id": inv.move_line_id.id, "currency_id": inv.account_id.currency_id.id, "currency_rate": inv.amount_due / inv.amount_cur if inv.amount_cur else None, } line_vals = { "description": desc, "amount": abs(inv.amount_cur or inv.amount_due), "track_id": inv.track_id and inv.track_id.id or None, "track2_id": inv.track2_id and inv.track2_id.id or None, } vals["lines"].append(("create", line_vals)) res = get_model("account.invoice").search([["number", "=", inv.number], ["type", "=", "out"]]) if res: inv2_id = res[0] inv2 = get_model("account.invoice").browse(inv2_id) if abs(inv2.amount_total) - abs(inv.amount_due) != 0: # XXX raise Exception("Failed to update invoice %s: different amount" % inv.number) if inv2.state == "draft": raise Exception("Failed to update invoice %s: invalid state" % inv.number) inv2.write({ "move_id": obj.move_id.id, "reconcile_move_line_id": inv.move_line_id.id, }) else: get_model("account.invoice").create(vals) def create_purch_invoices(self, ids, context={}): obj = self.browse(ids)[0] settings = get_model("settings").browse(1) desc = "Conversion balance %s" % obj.date for inv in obj.purch_invoices: vals = { "type": "in", "inv_type": inv.amount_due >= 0 and "invoice" or "credit", "contact_id": inv.contact_id.id, "date": inv.date, "due_date": inv.due_date, "number": inv.number, "ref": inv.ref, "memo": desc, "lines": [], "state": "waiting_payment", "account_id": inv.account_id.id, "reconcile_move_line_id": inv.move_line_id.id, "currency_id": inv.account_id.currency_id.id, "currency_rate": inv.amount_due / inv.amount_cur if inv.amount_cur else None, } line_vals = { "description": desc, "amount": abs(inv.amount_cur or inv.amount_due), "track_id": inv.track_id and inv.track_id.id or None, "track2_id": inv.track2_id and inv.track2_id.id or None, } vals["lines"].append(("create", line_vals)) res = get_model("account.invoice").search([["number", "=", inv.number], ["type", "=", "in"]]) if res: inv2_id = res[0] inv2 = get_model("account.invoice").browse(inv2_id) if abs(inv2.amount_total) - abs(inv.amount_due) != 0: # XXX raise Exception("Failed to update invoice %s" % inv.number) if inv2.state == "draft": raise Exception("Failed to update invoice %s: invalid state" % inv.number) inv2.write({ "move_id": obj.move_id.id, "reconcile_move_line_id": inv.move_line_id.id, }) else: get_model("account.invoice").create(vals) def update_total(self, context): data = context["data"] data["total_debit"] = 0 data["total_credit"] = 0 for line in data["accounts"]: if not line: continue debit = line.get("debit") or 0 credit = line.get("credit") or 0 data["total_debit"] += debit data["total_credit"] += credit return data def import_acc(self, ids, context={}): obj = self.browse(ids)[0] obj.write({"file": None}) return { "next": { "view_cls": "form_popup", "model": "conv.bal", "active_id": obj.id, "target": "_popup", "view_xml": "conv_import1", } } def import_sale(self, ids, context={}): obj = self.browse(ids)[0] obj.write({"file": None}) return { "next": { "view_cls": "form_popup", "model": "conv.bal", "active_id": obj.id, "target": "_popup", "view_xml": "conv_import2", } } def import_purch(self, ids, context={}): obj = self.browse(ids)[0] obj.write({"file": None}) return { "next": { "view_cls": "form_popup", "model": "conv.bal", "active_id": obj.id, "target": "_popup", "view_xml": "conv_import3", } } def get_track_categ(self, val): val=val.strip() mr = get_model("account.track.categ") ctx={ 'parent_vals': val, } res = mr.import_get(val, context=ctx) if not res: raise Exception("Invalid value for field %s"%val) val = res return val def import_acc_file(self, ids, context): obj = self.browse(ids)[0] path = get_file_path(obj.file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] del_ids = get_model("conv.account").search([["conv_id", "=", obj.id]]) get_model("conv.account").delete(del_ids) for row in rd: print("row", row) line = dict(zip(headers, row)) print("line", line) if not line.get("Account"): continue acc_code = line["Account"].strip() if not acc_code: continue res = get_model("account.account").search([["code", "=", acc_code]]) if not res: raise Exception("Account code not found: %s" % acc_code) acc_id = res[0] debit = float(line["Debit"].strip().replace(",", "") or 0) credit = float(line["Credit"].strip().replace(",", "") or 0) amount_cur = line["Currency Amt"].strip().replace(",", "") if amount_cur: amount_cur = float(amount_cur) else: amount_cur = None track_id=line.get('Track-1') if track_id: track_id = self.get_track_categ(track_id) track2_id=line.get('Track-2') if track2_id: track2_id = self.get_track_categ(track2_id) vals = { "conv_id": obj.id, "account_id": acc_id, "debit": debit, "credit": credit, "amount_cur": amount_cur, "track_id": track_id, "track2_id": track2_id, } get_model("conv.account").create(vals) return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal1", } } def import_sale_file(self, ids, context): obj = self.browse(ids)[0] path = get_file_path(obj.file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] del_ids = get_model("conv.sale.invoice").search([["conv_id", "=", obj.id]]) get_model("conv.sale.invoice").delete(del_ids) i = 1 for row in rd: i += 1 try: print("row", row) line = dict(zip(headers, row)) print("line", line) if not line.get("Number"): continue number = line["Number"].strip() if not number: continue ref = line["Reference"].strip() contact_name = line["Contact"].strip() res = get_model("contact").search([["name", "=", contact_name]]) if not res: raise Exception("Contact not found: '%s'" % contact_name) contact_id = res[0] date = datetime.datetime.strptime(line["Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d") due_date = datetime.datetime.strptime(line["Due Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d") amount_due = float(line["Amount Due"].strip().replace(",", "") or 0) acc_code = line["Account"].strip() if not acc_code: raise Exception("Account is missing") res = get_model("account.account").search([["code", "=", acc_code]]) if not res: raise Exception("Account code not found: %s" % acc_code) acc_id = res[0] amount_cur = None if line.get("Currency Amt"): amount_cur = line["Currency Amt"].strip().replace(",", "") amount_cur = float(amount_cur) track_id=line.get('Track-1') if track_id: track_id = self.get_track_categ(track_id) track2_id=line.get('Track-2') if track2_id: track2_id = self.get_track_categ(track2_id) vals = { "conv_id": obj.id, "number": number, "ref": ref, "contact_id": contact_id, "date": date, "due_date": due_date, "amount_due": amount_due, "account_id": acc_id, "amount_cur": amount_cur, "track_id": track_id, "track2_id": track2_id, } get_model("conv.sale.invoice").create(vals) except Exception as e: raise Exception("Error line %d: %s" % (i, e)) return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal2", } } def import_purch_file(self, ids, context): obj = self.browse(ids)[0] path = get_file_path(obj.file) data = open(path).read() rd = csv.reader(StringIO(data)) headers = next(rd) headers = [h.strip() for h in headers] del_ids = get_model("conv.purch.invoice").search([["conv_id", "=", obj.id]]) get_model("conv.purch.invoice").delete(del_ids) i = 1 for row in rd: i += 1 try: print("row", row) line = dict(zip(headers, row)) print("line", line) if not line.get("Number"): continue number = line["Number"].strip() if not number: continue ref = line["Reference"].strip() contact_name = line["Contact"].strip() res = get_model("contact").search([["name", "=", contact_name]]) if not res: raise Exception("Contact not found: '%s'" % contact_name) contact_id = res[0] date = datetime.datetime.strptime(line["Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d") due_date = datetime.datetime.strptime(line["Due Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d") amount_due = float(line["Amount Due"].strip().replace(",", "") or 0) acc_code = line["Account"].strip() if not acc_code: raise Exception("Account is missing") res = get_model("account.account").search([["code", "=", acc_code]]) if not res: raise Exception("Account code not found: %s" % acc_code) acc_id = res[0] amount_cur=None if line.get("Currency Amt"): amount_cur = line["Currency Amt"].strip().replace(",", "") amount_cur = float(amount_cur) track_id=line.get('Track-1') if track_id: track_id = self.get_track_categ(track_id) track2_id=line.get('Track-2') if track2_id: track2_id = self.get_track_categ(track2_id) vals = { "conv_id": obj.id, "number": number, "ref": ref, "contact_id": contact_id, "date": date, "due_date": due_date, "amount_due": amount_due, "account_id": acc_id, "amount_cur": amount_cur, "track_id": track_id, "track2_id": track2_id, } get_model("conv.purch.invoice").create(vals) except Exception as e: raise Exception("Error line %d: %s" % (i, e)) return { "next": { "name": "conv_bal", "active_id": obj.id, "view_xml": "conv_bal3", } }
class ImportModule(Model): _name = "import.module" _transient = True _fields = { "file": fields.File("Zip File"), } def do_import(self, ids, context={}): print("import modules") obj = self.browse(ids)[0] path = utils.get_file_path(obj.file) zf = zipfile.ZipFile(path, "r") modules = {} for n in zf.namelist(): if not n.endswith("/module.json"): continue path = n[:-len("/module.json")] data = zf.read(n).decode() vals = json.loads(data) name = vals["name"] module = { "name": name, "path": path, "info": vals, "model_files": [], "layout_files": [], "action_files": [], "template_files": [], "script_files": [], } modules[name] = module if not modules: raise Exception("No modules found") print("modules", modules.keys()) ids = get_model("module").search([["name", "in", modules.keys()]]) get_model("module").delete_modules(ids) for name, module in modules.items(): info = module["info"] vals = { "name": name, "description": info.get("description"), "version": info.get("version"), "author": info.get("author"), } get_model("module").create(vals) for path in zf.namelist(): found = False for name, module in modules.items(): if path.startswith(module["path"] + "/"): path2 = path[len(module["path"]) + 1:] module = modules[name] found = True break if not found: continue if path2.startswith("models/") and path2.endswith(".xml"): module["model_files"].append(path) elif path2.startswith("layouts/") and path2.endswith(".xml"): module["layout_files"].append(path) elif path2.startswith("actions/") and path2.endswith(".json"): module["action_files"].append(path) elif path2.startswith("templates/") and path2.endswith(".hbs"): module["template_files"].append(path) elif path2.startswith("scripts/") and path2.endswith(".js"): module["script_files"].append(path) for mod_name, module in modules.items(): for path in module["model_files"]: print("import model", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() root = etree.fromstring(data) vals = { "name": root.attrib["name"], "module_id": get_model("module").get(mod_name, require=True), } if root.attrib.get("string"): vals["string"] = root.attrib["string"] if root.attrib.get("description"): # XXX vals["description"] = root.attrib["description"] res = get_model("model").search([["name", "=", vals["name"]]]) if res: model_id = res[0] get_model("model").write([model_id], vals) else: model_id = get_model("model").create(vals) def _import_field(el, model, mod_name): vals = { "model_id": get_model("model").get(model, require=True), "module_id": get_model("module").get(mod_name, require=True), "name": el.attrib["name"], "string": el.attrib["string"], "type": el.attrib["type"], } if el.attrib.get("relation"): vals["relation_id"] = get_model("model").get( el.attrib["relation"], require=True) if el.attrib.get("relfield"): vals["relfield_id"] = get_model("field").find_field( el.attrib["relation"], el.attrib["relfield"]) if el.attrib.get("selection"): vals["selection"] = el.attrib["selection"] if el.attrib.get("required"): vals["required"] = True if el.attrib.get("readonly"): vals["readonly"] = True if el.attrib.get("function"): vals["function"] = el.attrib["function"] if el.attrib.get("default"): vals["default"] = el.attrib["default"] if el.attrib.get("search"): vals["search"] = True if el.attrib.get("condition"): vals["condition"] = el.attrib["condition"] if el.attrib.get("description"): vals["description"] = el.attrib["description"] get_model("field").create(vals) for mod_name, module in modules.items(): for path in module["model_files"]: print("import fields #1", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() root = etree.fromstring(data) for el in root: if el.tag != "field": continue if el.attrib.get("relfield"): continue _import_field(el, root.attrib["name"], mod_name) for mod_name, module in modules.items(): for path in module["model_files"]: print("import fields #2", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() root = etree.fromstring(data) for el in root: if el.tag != "field": continue if not el.attrib.get("relfield"): continue _import_field(el, root.attrib["name"], mod_name) for mod_name, module in modules.items(): for path in module["layout_files"]: print("import layout", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() root = etree.fromstring(data) vals = { "name": n, "module_id": get_model("module").get(mod_name, require=True), "type": root.tag.lower(), } if root.attrib.get("model"): vals["model_id"] = get_model("model").get( root.attrib["model"], require=True) del root.attrib["model"] if root.attrib.get("inherit"): vals["inherit"] = root.attrib["inherit"] del root.attrib["inherit"] vals["layout"] = etree.tostring(root, pretty_print=True).decode() get_model("view.layout").create(vals) for mod_name, module in modules.items(): for path in module["action_files"]: print("import action", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() vals = json.loads(data) vals2 = { "name": n, "module_id": get_model("module").get(mod_name, require=True), } if vals.get("string"): vals2["string"] = vals["string"] if vals.get("view"): vals2["view"] = vals["view"] if vals.get("model"): vals2["model_id"] = get_model("model").get(vals["model"], require=True) if vals.get("view_layout"): vals2["view_layout_id"] = get_model("view.layout").get( vals["view_layout"], require=True) if vals.get("menu"): vals2["menu_id"] = get_model("view.layout").get( vals["menu"], require=True) if vals.get("options"): vals2["options"] = json.dumps(vals["options"]) get_model("action").create(vals2) for mod_name, module in modules.items(): for path in module["template_files"]: print("import template", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() vals = { "name": n, "module_id": get_model("module").get(mod_name, require=True), "template": data, } get_model("template").create(vals) for mod_name, module in modules.items(): for path in module["script_files"]: print("import script", path) n = os.path.splitext(os.path.basename(path))[0] data = zf.read(path).decode() vals = { "name": n, "module_id": get_model("module").get(mod_name, require=True), "code": data, } get_model("script").create(vals) return { "next": { "name": "module", }, "flash": "Modules imported successfully", }
class Message(Model): _name = "message" _string = "Message" _fields = { "date": fields.DateTime("Date", required=True, search=True), "from_id": fields.Many2One("base.user", "From User", required=True, search=True), "to_id": fields.Many2One("base.user", "To User", search=True), "subject": fields.Char("Subject", search=True), "body": fields.Text("Message Body", required=True, search=True), "attach": fields.File("Attachment"), "ref_uuid": fields.Char("Reference UUID"), "related_id": fields.Reference([], "Related To"), "state": fields.Selection([["new", "New"], ["opened", "Opened"]], "Status", required=True, search=True), "open_dummy": fields.Boolean("Open Dummy", function="get_open_dummy"), } _order = "date desc" _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), "from_id": lambda *a: get_active_user(), "state": "new", } def create(self, vals, **kw): new_id = super().create(vals, **kw) to_id = vals.get("to_id") if to_id: db = get_connection() get_model("ws.event").new_event("new_message", to_id) self.trigger([new_id], "created") return new_id def get_open_dummy(self, ids, context={}): vals = {} user_id = get_active_user() for obj in self.browse(ids): if user_id == obj.to_id.id: obj.write({"state": "opened"}) vals[obj.id] = True return vals def send_from_template(self, template=None, from_user=None, to_user=None, context={}): print("####################################################") print("Message.send_from_template", template, from_user, to_user) res = get_model("message.tmpl").search([["name", "=", template]]) if not res: raise Exception("Template not found: %s" % template) tmpl_id = res[0] tmpl = get_model("message.tmpl").browse(tmpl_id) try: trigger_model = context.get("trigger_model") if not trigger_model: raise Exception("Missing trigger model") print("trigger_model", trigger_model) tm = get_model(trigger_model) trigger_ids = context.get("trigger_ids") if trigger_ids is None: raise Exception("Missing trigger ids") print("trigger_ids", trigger_ids) user_id = get_active_user() user = get_model("base.user").browse(user_id) for obj in tm.browse(trigger_ids): tmpl_ctx = {"obj": obj, "user": user} from_user_ = from_user if not from_user_: try: from_user_ = render_template(tmpl.from_user or "", tmpl_ctx) except: raise Exception("Error in 'From User': %s" % tmpl.from_user) if not from_user_: raise Exception("Missing 'From User'") res = get_model("base.user").search( [["login", "=", from_user_]]) if not res: raise Exception("'From User' not found: %s" % from_user_) from_id = res[0] to_user_ = to_user if not to_user_: try: to_user_ = render_template(tmpl.to_user or "", tmpl_ctx) except: raise Exception("Error in 'To User': %s" % tmpl.to_user) if not to_user_: raise Exception("Missing 'To User'") to_ids = [] for login in [x.strip() for x in to_user_.split(",")]: res = get_model("base.user").search([["login", "=", login]]) if not res: raise Exception("'To User' not found: %s" % login) to_id = res[0] to_ids.append(to_id) try: subject = render_template(tmpl.subject, tmpl_ctx) except: raise Exception("Error in 'Subject': %s" % tmpl.subject) try: body = render_template(tmpl.body, tmpl_ctx) except: raise Exception("Error in 'Body': %s" % tmpl.body) for to_id in to_ids: vals = { "from_id": from_id, "to_id": to_id, "subject": subject, "body": body, } self.create(vals) except Exception as e: import traceback traceback.print_exc() raise Exception("Error in template %s: %s" % (template, e))