Add entity address/contact tables and CRUD for entity resources

This commit is contained in:
seb
2026-08-06 05:43:50 +02:00
parent 0b7e40ac9c
commit 3b77a1c946
32 changed files with 3078 additions and 1363 deletions

View File

@@ -187,6 +187,78 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
--- ---
## `customergroup`
Company-scoped customer groups. Auth via `resolveCompanyAccess`.
| Method | Signature | Returns |
| --- | --- | --- |
| `list` | `(googleAuthId, companyId)` | `{ id, name }[]` |
| `create` | `(googleAuthId, companyId, { name })` | `{ id, name }` |
| `update` | `(googleAuthId, companyId, id, { name })` | `{ id, name }` |
| `delete` | `(googleAuthId, companyId, id)` | `void` |
Empty name → `Name required`. Deleting the POS group (or any group still referenced with `NO ACTION`) → `Customer group in use`. Cascades entities when the group is deleted. Errors: `Company not found`, `Not authorized`, `Name required`, `Customer group not found`, `Customer group in use`.
---
## `entity`
Entities belong to a `customergroup` of the company. Auth via `resolveCompanyAccess`.
| Method | Signature | Returns |
| --- | --- | --- |
| `list` | `(googleAuthId, companyId, { customergroupId? })` | `Entity[]` |
| `getById` | `(googleAuthId, companyId, id)` | `Entity` |
| `create` | `(googleAuthId, companyId, { customergroupId, name, firstName? })` | `Entity` |
| `update` | `(googleAuthId, companyId, id, { name?, firstName? })` | `Entity` |
| `delete` | `(googleAuthId, companyId, id)` | `void` |
```ts
type Entity = {
id: number,
customergroupId: number,
firstName: string | null,
name: string,
createdAt: Date,
updatedAt: Date,
}
```
`create` / `update` write `entityhistory` (`Erstellt`, or name/firstName change lines). `customergroupId` must belong to the company. Errors: `Company not found`, `Not authorized`, `Name required`, `Customer group not found`, `Entity not found`.
---
## `entityaddress`
Addresses for an entity. Auth via company + entity ownership.
| Method | Signature | Returns |
| --- | --- | --- |
| `list` | `(googleAuthId, companyId, entityId)` | `EntityAddress[]` |
| `create` | `(googleAuthId, companyId, entityId, { addressType, firstName?, name?, street?, streetNumber?, zip?, city?, county? })` | `EntityAddress` |
| `update` | `(googleAuthId, companyId, entityId, id, patch)` | `EntityAddress` |
| `delete` | `(googleAuthId, companyId, entityId, id)` | `void` |
`addressType` required. Errors: `Company not found`, `Not authorized`, `Entity not found`, `Address type required`, `Address not found`.
---
## `entitycontact`
Contacts for an entity (`contactType` e.g. `email`, `phone`). Auth via company + entity ownership.
| Method | Signature | Returns |
| --- | --- | --- |
| `list` | `(googleAuthId, companyId, entityId)` | `{ id, entityId, contactType, contact }[]` |
| `create` | `(googleAuthId, companyId, entityId, { contactType, contact })` | row |
| `update` | `(googleAuthId, companyId, entityId, id, { contactType?, contact? })` | row |
| `delete` | `(googleAuthId, companyId, entityId, id)` | `void` |
Errors: `Company not found`, `Not authorized`, `Entity not found`, `Contact required`, `Contact not found`.
---
## `supplier` ## `supplier`
Suppliers are `entity` rows in the company `customergroup` named `Lieferanten` (created implicitly on first supplier). Auth via `resolveCompanyAccess`. Suppliers are `entity` rows in the company `customergroup` named `Lieferanten` (created implicitly on first supplier). Auth via `resolveCompanyAccess`.
@@ -519,6 +591,23 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
| `GET /api/brand` | `brand.list(googleAuthId, companyId)` | | `GET /api/brand` | `brand.list(googleAuthId, companyId)` |
| `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` | | `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` |
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` | | `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` |
| `GET /api/customergroup` | `customergroup.list(googleAuthId, companyId)` |
| `POST /api/customergroup` | `customergroup.create(googleAuthId, companyId, { name })` |
| `PATCH /api/customergroup/:id` | `customergroup.update(googleAuthId, companyId, id, { name })` |
| `DELETE /api/customergroup/:id` | `customergroup.delete(googleAuthId, companyId, id)` |
| `GET /api/entity` | `entity.list(googleAuthId, companyId, { customergroupId? })` |
| `GET /api/entity/:id` | `entity.getById(googleAuthId, companyId, id)` |
| `POST /api/entity` | `entity.create(googleAuthId, companyId, { customergroupId, name, firstName? })` |
| `PATCH /api/entity/:id` | `entity.update(googleAuthId, companyId, id, { name?, firstName? })` |
| `DELETE /api/entity/:id` | `entity.delete(googleAuthId, companyId, id)` |
| `GET /api/entity/:entityId/address` | `entityaddress.list(googleAuthId, companyId, entityId)` |
| `POST /api/entity/:entityId/address` | `entityaddress.create(googleAuthId, companyId, entityId, { addressType, … })` |
| `PATCH /api/entity/:entityId/address/:id` | `entityaddress.update(googleAuthId, companyId, entityId, id, patch)` |
| `DELETE /api/entity/:entityId/address/:id` | `entityaddress.delete(googleAuthId, companyId, entityId, id)` |
| `GET /api/entity/:entityId/contact` | `entitycontact.list(googleAuthId, companyId, entityId)` |
| `POST /api/entity/:entityId/contact` | `entitycontact.create(googleAuthId, companyId, entityId, { contactType, contact })` |
| `PATCH /api/entity/:entityId/contact/:id` | `entitycontact.update(googleAuthId, companyId, entityId, id, patch)` |
| `DELETE /api/entity/:entityId/contact/:id` | `entitycontact.delete(googleAuthId, companyId, entityId, id)` |
| `GET /api/supplier` | `supplier.list(googleAuthId, companyId)` | | `GET /api/supplier` | `supplier.list(googleAuthId, companyId)` |
| `POST /api/supplier` | `supplier.create(googleAuthId, companyId, { name })` | | `POST /api/supplier` | `supplier.create(googleAuthId, companyId, { name })` |
| `PATCH /api/supplier/:id` | `supplier.update(googleAuthId, companyId, id, { name })` | | `PATCH /api/supplier/:id` | `supplier.update(googleAuthId, companyId, id, { name })` |

View File

@@ -275,6 +275,8 @@ const CLUSTERS = [
'customergroup', 'customergroup',
'entity', 'entity',
'entityhistory', 'entityhistory',
'entityaddress',
'entitycontact',
'numbers', 'numbers',
], ],
}, },

View File

@@ -10,14 +10,16 @@ digraph schema {
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0"; style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14; fontcolor="#1a365d"; margin=14;
customergroup [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="3" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>customergroup</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; customergroup [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="3" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>customergroup</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
entityaddress [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entityaddress</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="first_name" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">first_name?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="street" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">street?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="streetnumber" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">streetnumber?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="zip" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">zip?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="city" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">city?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="county" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">county?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="addresstype" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">addresstype</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
numbers [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>numbers</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="type" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">type</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="prefix" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">prefix</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="padding" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">padding</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="next" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">next</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; numbers [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>numbers</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="type" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">type</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="prefix" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">prefix</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="padding" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">padding</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="next" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">next</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
entity [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in3" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="4" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entity</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="customergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>customergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="first_name" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">first_name?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="created_at" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">created_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="updated_at" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">updated_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
company [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in3" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in4" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in5" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in6" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in7" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in8" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in9" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="10" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>company</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="default_language_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>default_language_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="root_category_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>root_category_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="poscustomergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>poscustomergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="taxrate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>taxrate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="reducedtaxrate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>reducedtaxrate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="taxfreerate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>taxfreerate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; company [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in3" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in4" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in5" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in6" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in7" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in8" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in9" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="10" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>company</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="default_language_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>default_language_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="root_category_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>root_category_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="poscustomergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>poscustomergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="taxrate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>taxrate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="reducedtaxrate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>reducedtaxrate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="taxfreerate" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>taxfreerate</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
entity [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entity</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="customergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>customergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="first_name" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">first_name?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="name" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">name</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="created_at" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">created_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="updated_at" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">updated_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
syncpos [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>syncpos</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="paircode" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">paircode</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="token" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">token?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; syncpos [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>syncpos</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="paircode" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">paircode</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="token" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">token?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
entitycontact [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entitycontact</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="contacttype" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">contacttype</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(50)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="contact" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">contact</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
meta [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>meta</B></FONT></TD></TR><TR><TD PORT="key" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">key</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="value" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">value</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; meta [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>meta</B></FONT></TD></TR><TR><TD PORT="key" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">key</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="value" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">value</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
companysetting [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>companysetting</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="key" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">key</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="value" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">value</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="frontend" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">frontend</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">BIT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
entityhistory [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entityhistory</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="companyuser_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>companyuser_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="date" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">date</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="text" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">text</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; entityhistory [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>entityhistory</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="companyuser_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>companyuser_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="date" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">date</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="text" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">text</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
companyuser [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>companyuser</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="google_auth_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>google_auth_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR></TABLE>>]; companyuser [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="2" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>companyuser</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="google_auth_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>google_auth_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR></TABLE>>];
companysetting [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>companysetting</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="key" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">key</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="value" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">value</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="frontend" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">frontend</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">BIT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
google_auth [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>google_auth</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="selected_company" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>selected_company</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="email" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">email</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(320)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="picture" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">picture?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(2048)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="picturebinary" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">picturebinary?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">VARBINARY(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="sub" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">sub?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="created_at" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">created_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; google_auth [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>google_auth</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="selected_company" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>selected_company</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="email" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">email</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(320)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="picture" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">picture?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(2048)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="picturebinary" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">picturebinary?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">VARBINARY(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="sub" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">sub?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(255)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="created_at" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">created_at</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
} }
subgraph cluster_i18n { subgraph cluster_i18n {
@@ -58,9 +60,9 @@ digraph schema {
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0"; style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14; fontcolor="#1a365d"; margin=14;
tax [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in3" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in4" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="5" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>tax</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="rate" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">rate</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DECIMAL(18,4)</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; tax [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in1" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in2" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in3" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD><TD PORT="in4" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="5" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>tax</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="rate" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">rate</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DECIMAL(18,4)</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
manifest [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>manifest</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="number" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">number</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="type" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">type</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="date" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">date</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
manifestarticle [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>manifestarticle</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="article_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>article_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="tax_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>tax_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="manifest_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>manifest_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="amount" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">amount</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="net" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">net</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="transactedamount" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">transactedamount</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="comment" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">comment?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="rebate" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">rebate</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
price [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>price</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="article_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>article_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="customergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>customergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="tax_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>tax_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="minmultiplier" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">minmultiplier</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DECIMAL(18,4)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="net" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">net</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR></TABLE>>]; price [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>price</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="article_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>article_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="customergroup_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>customergroup_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="tax_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>tax_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="minmultiplier" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">minmultiplier</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DECIMAL(18,4)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="net" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">net</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
manifest [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="1" BORDER="1"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD PORT="in0" BGCOLOR="#bee3f8" BORDER="0" WIDTH="10" HEIGHT="4" CELLPADDING="0"></TD></TR><TR><TD COLSPAN="1" ALIGN="CENTER" CELLPADDING="3"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>manifest</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="entity_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>entity_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="company_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>company_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="number" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">number</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="type" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">type</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(100)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="date" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">date</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">DATETIME2</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
manifestarticle [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4" COLOR="#a0aec0"><TR><TD COLSPAN="2" BGCOLOR="#bee3f8" ALIGN="CENTER" CELLPADDING="4" BORDER="1"><FONT COLOR="#1a365d" POINT-SIZE="12"><B>manifestarticle</B></FONT></TD></TR><TR><TD PORT="id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>PK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="article_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>article_id</B>?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="tax_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>tax_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="manifest_id" COLSPAN="2" BGCOLOR="#ebf8ff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748"><B>manifest_id</B></FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">INT <B>FK</B></FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="amount" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">amount</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="net" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">net</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="transactedamount" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">transactedamount</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="comment" COLSPAN="2" BGCOLOR="#f7fafc" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">comment?</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">NVARCHAR(MAX)</FONT></TD></TR></TABLE></TD></TR><TR><TD PORT="rebate" COLSPAN="2" BGCOLOR="#ffffff" BORDER="1" CELLPADDING="0"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" FIXEDSIZE="FALSE"><TR><TD ALIGN="LEFT" CELLPADDING="4"><FONT COLOR="#2d3748">rebate</FONT></TD><TD ALIGN="RIGHT" CELLPADDING="4"><FONT COLOR="#718096" POINT-SIZE="9">FLOAT</FONT></TD></TR></TABLE></TD></TR></TABLE>>];
} }
subgraph cluster_units { subgraph cluster_units {
label="Units"; labeljust="l"; fontsize=15; label="Units"; labeljust="l"; fontsize=15;
@@ -81,52 +83,54 @@ digraph schema {
company:reducedtaxrate:e -> tax:in0:n [color="#c05621", fontcolor="#c05621", tooltip="FK_company_reducedtaxrate"]; company:reducedtaxrate:e -> tax:in0:n [color="#c05621", fontcolor="#c05621", tooltip="FK_company_reducedtaxrate"];
company:taxfreerate:e -> tax:in1:n [color="#319795", fontcolor="#319795", tooltip="FK_company_taxfreerate"]; company:taxfreerate:e -> tax:in1:n [color="#319795", fontcolor="#319795", tooltip="FK_company_taxfreerate"];
customergroup:company_id:se -> company:in3:n [color="#97266d", fontcolor="#97266d", tooltip="FK_customergroup_company"]; customergroup:company_id:se -> company:in3:n [color="#97266d", fontcolor="#97266d", tooltip="FK_customergroup_company"];
company:poscustomergroup_id:nw -> customergroup:in0:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_company_poscustomergroup"]; company:poscustomergroup_id:nw -> customergroup:in1:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_company_poscustomergroup"];
entity:customergroup_id:sw -> customergroup:in1:n [color="#276749", fontcolor="#276749", tooltip="FK_entity_customergroup"]; entity:customergroup_id:sw -> customergroup:in0:n [color="#276749", fontcolor="#276749", tooltip="FK_entity_customergroup"];
entityhistory:entity_id:sw -> entity:in0:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_entityhistory_entity"]; entityaddress:entity_id:se -> entity:in0:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_entityaddress_entity"];
entityhistory:companyuser_id:se -> companyuser:in1:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_entityhistory_companyuser"]; entitycontact:entity_id:sw -> entity:in1:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_entitycontact_entity"];
unit:company_id:e -> company:in0:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_unit_company"]; entityhistory:entity_id:sw -> entity:in2:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_entityhistory_entity"];
unittext:unit_id:se -> unit:in0:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_unittext_unit"]; entityhistory:companyuser_id:se -> companyuser:in1:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_entityhistory_companyuser"];
unittext:language_id:se -> language:in0:n [color="#22543d", fontcolor="#22543d", tooltip="FK_unittext_language"]; unit:company_id:e -> company:in0:n [color="#22543d", fontcolor="#22543d", tooltip="FK_unit_company"];
brand:company_id:e -> company:in1:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_brand_company"]; unittext:unit_id:se -> unit:in0:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_unittext_unit"];
article:unit_id:sw -> unit:in1:n [color="#702459", fontcolor="#702459", tooltip="FK_article_unit"]; unittext:language_id:se -> language:in0:n [color="#702459", fontcolor="#702459", tooltip="FK_unittext_language"];
article:brand_id:e -> brand:in0:n [color="#744210", fontcolor="#744210", tooltip="FK_article_brand"]; brand:company_id:e -> company:in1:n [color="#744210", fontcolor="#744210", tooltip="FK_brand_company"];
article:articlelist_id:sw -> articlelist:in1:n [color="#285e61", fontcolor="#285e61", tooltip="FK_article_articlelist"]; article:unit_id:sw -> unit:in1:n [color="#285e61", fontcolor="#285e61", tooltip="FK_article_unit"];
article:optionslist_id:se -> optionslist:in0:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_article_optionslist"]; article:brand_id:e -> brand:in0:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_article_brand"];
article:fatherarticle_id:e -> article:id:e [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_article_fatherarticle"]; article:articlelist_id:sw -> articlelist:in1:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_article_articlelist"];
articlehistory:article_id:w -> article:in5:n [color="#38a169", fontcolor="#38a169", tooltip="FK_articlehistory_article"]; article:optionslist_id:se -> optionslist:in0:n [color="#38a169", fontcolor="#38a169", tooltip="FK_article_optionslist"];
articlehistory:companyuser_id:e -> companyuser:in0:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_articlehistory_companyuser"]; article:fatherarticle_id:e -> article:id:e [color="#3182ce", fontcolor="#3182ce", tooltip="FK_article_fatherarticle"];
articleean:article_id:sw -> article:in3:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_articleean_article"]; articlehistory:article_id:w -> article:in5:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_articlehistory_article"];
articlepic:article_id:w -> article:in7:n [color="#c53030", fontcolor="#c53030", tooltip="FK_articlepic_article"]; articlehistory:companyuser_id:e -> companyuser:in0:n [color="#c53030", fontcolor="#c53030", tooltip="FK_articlehistory_companyuser"];
articlepic:pic_id:se -> pic:in1:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_articlepic_pic"]; articleean:article_id:sw -> article:in3:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_articleean_article"];
brandpic:brand_id:w -> brand:in1:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_brandpic_brand"]; articlepic:article_id:w -> article:in7:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_articlepic_article"];
brandpic:pic_id:se -> pic:in0:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_brandpic_pic"]; articlepic:pic_id:se -> pic:in1:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_articlepic_pic"];
articlelistarticle:articlelist_id:se -> articlelist:in0:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_articlelistarticle_articlelist"]; brandpic:brand_id:w -> brand:in1:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_brandpic_brand"];
articlelistarticle:article_id:se -> article:in1:n [color="#c05621", fontcolor="#c05621", tooltip="FK_articlelistarticle_article"]; brandpic:pic_id:se -> pic:in0:n [color="#c05621", fontcolor="#c05621", tooltip="FK_brandpic_pic"];
optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#319795", fontcolor="#319795", tooltip="FK_optionslistarticle_optionslist"]; articlelistarticle:articlelist_id:se -> articlelist:in0:n [color="#319795", fontcolor="#319795", tooltip="FK_articlelistarticle_articlelist"];
optionslistarticle:article_id:sw -> article:in4:n [color="#97266d", fontcolor="#97266d", tooltip="FK_optionslistarticle_article"]; articlelistarticle:article_id:se -> article:in1:n [color="#97266d", fontcolor="#97266d", tooltip="FK_articlelistarticle_article"];
categoryarticle:category_id:sw -> category:in2:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_categoryarticle_category"]; optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_optionslistarticle_optionslist"];
categoryarticle:article_id:w -> article:in6:n [color="#276749", fontcolor="#276749", tooltip="FK_categoryarticle_article"]; optionslistarticle:article_id:sw -> article:in4:n [color="#276749", fontcolor="#276749", tooltip="FK_optionslistarticle_article"];
categorypic:category_id:sw -> category:in4:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_categorypic_category"]; categoryarticle:category_id:sw -> category:in2:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_categoryarticle_category"];
categorypic:pic_id:sw -> pic:in2:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_categorypic_pic"]; categoryarticle:article_id:w -> article:in6:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_categoryarticle_article"];
categorytext:category_id:e -> category:in0:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_categorytext_category"]; categorypic:category_id:sw -> category:in4:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_categorypic_category"];
categorytext:language_id:sw -> language:in1:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_categorytext_language"]; categorypic:pic_id:sw -> pic:in2:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_categorypic_pic"];
articletext:article_id:e -> article:in0:n [color="#22543d", fontcolor="#22543d", tooltip="FK_articletext_article"]; categorytext:category_id:e -> category:in0:n [color="#22543d", fontcolor="#22543d", tooltip="FK_categorytext_category"];
articletext:language_id:sw -> language:in2:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_articletext_language"]; categorytext:language_id:sw -> language:in1:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_categorytext_language"];
price:tax_id:sw -> tax:in4:n [color="#702459", fontcolor="#702459", tooltip="FK_price_tax"]; articletext:article_id:e -> article:in0:n [color="#702459", fontcolor="#702459", tooltip="FK_articletext_article"];
price:article_id:w -> article:in9:n [color="#744210", fontcolor="#744210", tooltip="FK_price_article"]; articletext:language_id:sw -> language:in2:n [color="#744210", fontcolor="#744210", tooltip="FK_articletext_language"];
price:customergroup_id:w -> customergroup:in2:n [color="#285e61", fontcolor="#285e61", tooltip="FK_price_customergroup"]; price:tax_id:sw -> tax:in3:n [color="#285e61", fontcolor="#285e61", tooltip="FK_price_tax"];
manifest:company_id:sw -> company:in9:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_manifest_company"]; price:article_id:w -> article:in8:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_price_article"];
manifest:entity_id:w -> entity:in1:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_manifest_entity"]; price:customergroup_id:w -> customergroup:in2:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_price_customergroup"];
manifestarticle:manifest_id:sw -> manifest:in0:n [color="#38a169", fontcolor="#38a169", tooltip="FK_manifestarticle_manifest"]; manifest:company_id:sw -> company:in9:n [color="#38a169", fontcolor="#38a169", tooltip="FK_manifest_company"];
manifestarticle:article_id:w -> article:in8:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_manifestarticle_article"]; manifest:entity_id:w -> entity:in3:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_manifest_entity"];
manifestarticle:tax_id:sw -> tax:in3:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_manifestarticle_tax"]; manifestarticle:manifest_id:sw -> manifest:in0:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_manifestarticle_manifest"];
numbers:company_id:se -> company:in4:n [color="#c53030", fontcolor="#c53030", tooltip="FK_numbers_company"]; manifestarticle:article_id:w -> article:in9:n [color="#c53030", fontcolor="#c53030", tooltip="FK_manifestarticle_article"];
companyuser:google_auth_id:se -> google_auth:in0:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_companyuser_google_auth"]; manifestarticle:tax_id:sw -> tax:in4:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_manifestarticle_tax"];
companyuser:company_id:sw -> company:in6:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_companyuser_company"]; numbers:company_id:se -> company:in4:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_numbers_company"];
syncpos:company_id:sw -> company:in5:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_syncpos_company"]; companyuser:google_auth_id:se -> google_auth:in0:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_companyuser_google_auth"];
companysetting:company_id:sw -> company:in7:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_companysetting_company"]; companyuser:company_id:sw -> company:in7:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_companyuser_company"];
posterminal:company_id:e -> company:in2:n [color="#c05621", fontcolor="#c05621", tooltip="FK_posterminal_company"]; syncpos:company_id:sw -> company:in5:n [color="#c05621", fontcolor="#c05621", tooltip="FK_syncpos_company"];
posterminalcategory:posterminal_id:se -> posterminal:in0:n [color="#319795", fontcolor="#319795", tooltip="FK_posterminalcategory_posterminal"]; companysetting:company_id:sw -> company:in6:n [color="#319795", fontcolor="#319795", tooltip="FK_companysetting_company"];
posterminalcategory:category_id:sw -> category:in3:n [color="#97266d", fontcolor="#97266d", tooltip="FK_posterminalcategory_category"]; posterminal:company_id:e -> company:in2:n [color="#97266d", fontcolor="#97266d", tooltip="FK_posterminal_company"];
posterminalcategory:posterminal_id:se -> posterminal:in0:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_posterminalcategory_posterminal"];
posterminalcategory:category_id:sw -> category:in3:n [color="#276749", fontcolor="#276749", tooltip="FK_posterminalcategory_category"];
} }

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -158,6 +158,43 @@ const MENU = {
}, },
], ],
}, },
customergroup: {
label: 'customergroup',
items: [
{ value: 'customergroup.list', label: 'list', hint: 'googleAuthId, companyId → rows' },
{ value: 'customergroup.create', label: 'create', hint: 'googleAuthId, companyId, name → row' },
{ value: 'customergroup.update', label: 'update', hint: 'googleAuthId, companyId, id, name → row' },
{ value: 'customergroup.delete', label: 'delete', hint: 'googleAuthId, companyId, id' },
],
},
entity: {
label: 'entity',
items: [
{ value: 'entity.list', label: 'list', hint: 'googleAuthId, companyId, customergroupId? → rows' },
{ value: 'entity.getById', label: 'getById', hint: 'googleAuthId, companyId, id → row' },
{ value: 'entity.create', label: 'create', hint: 'googleAuthId, companyId, JSON → row' },
{ value: 'entity.update', label: 'update', hint: 'googleAuthId, companyId, id, JSON → row' },
{ value: 'entity.delete', label: 'delete', hint: 'googleAuthId, companyId, id' },
],
},
entityaddress: {
label: 'entityaddress',
items: [
{ value: 'entityaddress.list', label: 'list', hint: 'googleAuthId, companyId, entityId → rows' },
{ value: 'entityaddress.create', label: 'create', hint: 'googleAuthId, companyId, entityId, JSON → row' },
{ value: 'entityaddress.update', label: 'update', hint: 'googleAuthId, companyId, entityId, id, JSON → row' },
{ value: 'entityaddress.delete', label: 'delete', hint: 'googleAuthId, companyId, entityId, id' },
],
},
entitycontact: {
label: 'entitycontact',
items: [
{ value: 'entitycontact.list', label: 'list', hint: 'googleAuthId, companyId, entityId → rows' },
{ value: 'entitycontact.create', label: 'create', hint: 'googleAuthId, companyId, entityId, JSON → row' },
{ value: 'entitycontact.update', label: 'update', hint: 'googleAuthId, companyId, entityId, id, JSON → row' },
{ value: 'entitycontact.delete', label: 'delete', hint: 'googleAuthId, companyId, entityId, id' },
],
},
supplier: { supplier: {
label: 'supplier', label: 'supplier',
items: [ items: [
@@ -681,6 +718,362 @@ async function runMethod(client, action) {
await client.brand.delete(Number(googleAuthId), Number(companyId), id); await client.brand.delete(Number(googleAuthId), Number(companyId), id);
return ['ok', true]; return ['ok', true];
} }
case 'customergroup.list': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
return [
'result[]',
await client.customergroup.list(Number(googleAuthId), Number(companyId)),
];
}
case 'customergroup.create': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const name = await askText('name', {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (name == null) return;
return [
'result',
await client.customergroup.create(Number(googleAuthId), Number(companyId), {
name,
}),
];
}
case 'customergroup.update': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const id = await askInt('customergroup id');
if (id == null) return;
const name = await askText('name', {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (name == null) return;
return [
'result',
await client.customergroup.update(
Number(googleAuthId),
Number(companyId),
Number(id),
{ name },
),
];
}
case 'customergroup.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const id = await askInt('customergroup id');
if (id == null) return;
await client.customergroup.delete(
Number(googleAuthId),
Number(companyId),
Number(id),
);
return ['ok', true];
}
case 'entity.list': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const groupRaw = await askText('customergroup id (empty = all)', {
placeholder: '',
validate: (v) =>
v?.trim() && !/^\d+$/.test(v.trim()) ? 'Integer or empty' : undefined,
});
if (groupRaw == null) return;
const customergroupId = groupRaw.trim()
? Number(groupRaw.trim())
: undefined;
return [
'result[]',
await client.entity.list(Number(googleAuthId), Number(companyId), {
customergroupId,
}),
];
}
case 'entity.getById': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const id = await askInt('entity id');
if (id == null) return;
return [
'result',
await client.entity.getById(
Number(googleAuthId),
Number(companyId),
Number(id),
),
];
}
case 'entity.create': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const bodyJson = await askText('entity JSON', {
placeholder: '{"customergroupId":1,"name":"Acme","firstName":null}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (bodyJson == null) return;
return [
'result',
await client.entity.create(
Number(googleAuthId),
Number(companyId),
JSON.parse(bodyJson),
),
];
}
case 'entity.update': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const id = await askInt('entity id');
if (id == null) return;
const patchJson = await askText('patch JSON', {
placeholder: '{"name":"Acme","firstName":"Ada"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (patchJson == null) return;
return [
'result',
await client.entity.update(
Number(googleAuthId),
Number(companyId),
Number(id),
JSON.parse(patchJson),
),
];
}
case 'entity.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const id = await askInt('entity id');
if (id == null) return;
await client.entity.delete(
Number(googleAuthId),
Number(companyId),
Number(id),
);
return ['ok', true];
}
case 'entityaddress.list': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
return [
'result[]',
await client.entityaddress.list(
Number(googleAuthId),
Number(companyId),
Number(entityId),
),
];
}
case 'entityaddress.create': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const bodyJson = await askText('address JSON', {
placeholder:
'{"addressType":"billing","street":"Main","streetNumber":"1","zip":"1010","city":"Wien"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (bodyJson == null) return;
return [
'result',
await client.entityaddress.create(
Number(googleAuthId),
Number(companyId),
Number(entityId),
JSON.parse(bodyJson),
),
];
}
case 'entityaddress.update': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const id = await askInt('address id');
if (id == null) return;
const patchJson = await askText('patch JSON', {
placeholder: '{"city":"Graz"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (patchJson == null) return;
return [
'result',
await client.entityaddress.update(
Number(googleAuthId),
Number(companyId),
Number(entityId),
Number(id),
JSON.parse(patchJson),
),
];
}
case 'entityaddress.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const id = await askInt('address id');
if (id == null) return;
await client.entityaddress.delete(
Number(googleAuthId),
Number(companyId),
Number(entityId),
Number(id),
);
return ['ok', true];
}
case 'entitycontact.list': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
return [
'result[]',
await client.entitycontact.list(
Number(googleAuthId),
Number(companyId),
Number(entityId),
),
];
}
case 'entitycontact.create': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const bodyJson = await askText('contact JSON', {
placeholder: '{"contactType":"email","contact":"a@b.c"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (bodyJson == null) return;
return [
'result',
await client.entitycontact.create(
Number(googleAuthId),
Number(companyId),
Number(entityId),
JSON.parse(bodyJson),
),
];
}
case 'entitycontact.update': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const id = await askInt('contact id');
if (id == null) return;
const patchJson = await askText('patch JSON', {
placeholder: '{"contact":"b@c.d"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
JSON.parse(v);
} catch {
return 'Invalid JSON';
}
},
});
if (patchJson == null) return;
return [
'result',
await client.entitycontact.update(
Number(googleAuthId),
Number(companyId),
Number(entityId),
Number(id),
JSON.parse(patchJson),
),
];
}
case 'entitycontact.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const entityId = await askInt('entity id');
if (entityId == null) return;
const id = await askInt('contact id');
if (id == null) return;
await client.entitycontact.delete(
Number(googleAuthId),
Number(companyId),
Number(entityId),
Number(id),
);
return ['ok', true];
}
case 'supplier.list': { case 'supplier.list': {
const googleAuthId = await askInt('google_auth id'); const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return; if (googleAuthId == null) return;

View File

@@ -39,6 +39,23 @@ import companysettingDelete from './store/companysetting/delete.js';
import brandList from './store/brand/list.js'; import brandList from './store/brand/list.js';
import brandUpsert from './store/brand/upsert.js'; import brandUpsert from './store/brand/upsert.js';
import brandDelete from './store/brand/delete.js'; import brandDelete from './store/brand/delete.js';
import customergroupList from './store/customergroup/list.js';
import customergroupCreate from './store/customergroup/create.js';
import customergroupUpdate from './store/customergroup/update.js';
import customergroupDelete from './store/customergroup/delete.js';
import entityList from './store/entity/list.js';
import entityGetById from './store/entity/getById.js';
import entityCreate from './store/entity/create.js';
import entityUpdate from './store/entity/update.js';
import entityDelete from './store/entity/delete.js';
import entityaddressList from './store/entityaddress/list.js';
import entityaddressCreate from './store/entityaddress/create.js';
import entityaddressUpdate from './store/entityaddress/update.js';
import entityaddressDelete from './store/entityaddress/delete.js';
import entitycontactList from './store/entitycontact/list.js';
import entitycontactCreate from './store/entitycontact/create.js';
import entitycontactUpdate from './store/entitycontact/update.js';
import entitycontactDelete from './store/entitycontact/delete.js';
import supplierList from './store/supplier/list.js'; import supplierList from './store/supplier/list.js';
import supplierCreate from './store/supplier/create.js'; import supplierCreate from './store/supplier/create.js';
import supplierUpdate from './store/supplier/update.js'; import supplierUpdate from './store/supplier/update.js';
@@ -158,6 +175,35 @@ export default class LibDashStore {
delete: brandDelete(pool), delete: brandDelete(pool),
}; };
this.customergroup = {
list: customergroupList(pool),
create: customergroupCreate(pool),
update: customergroupUpdate(pool),
delete: customergroupDelete(pool),
};
this.entity = {
list: entityList(pool),
getById: entityGetById(pool),
create: entityCreate(pool),
update: entityUpdate(pool),
delete: entityDelete(pool),
};
this.entityaddress = {
list: entityaddressList(pool),
create: entityaddressCreate(pool),
update: entityaddressUpdate(pool),
delete: entityaddressDelete(pool),
};
this.entitycontact = {
list: entitycontactList(pool),
create: entitycontactCreate(pool),
update: entitycontactUpdate(pool),
delete: entitycontactDelete(pool),
};
this.supplier = { this.supplier = {
list: supplierList(pool), list: supplierList(pool),
create: supplierCreate(pool), create: supplierCreate(pool),

View File

@@ -164,6 +164,38 @@ BEGIN
END END
`; `;
export const INIT_ENTITYADDRESS = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityaddress')
BEGIN
CREATE TABLE entityaddress (
id INT IDENTITY(1,1) PRIMARY KEY,
entity_id INT NOT NULL,
first_name NVARCHAR(255) NULL,
name NVARCHAR(255) NULL,
street NVARCHAR(255) NULL,
streetnumber NVARCHAR(50) NULL,
zip NVARCHAR(50) NULL,
city NVARCHAR(255) NULL,
county NVARCHAR(255) NULL,
addresstype NVARCHAR(50) NOT NULL,
CONSTRAINT FK_entityaddress_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
);
END
`;
export const INIT_ENTITYCONTACT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entitycontact')
BEGIN
CREATE TABLE entitycontact (
id INT IDENTITY(1,1) PRIMARY KEY,
entity_id INT NOT NULL,
contacttype NVARCHAR(50) NOT NULL,
contact NVARCHAR(255) NOT NULL,
CONSTRAINT FK_entitycontact_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
);
END
`;
export const INIT_ENTITYHISTORY = ` export const INIT_ENTITYHISTORY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityhistory') IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityhistory')
BEGIN BEGIN
@@ -636,6 +668,8 @@ export const SCHEMA_SCRIPTS = [
INIT_CUSTOMERGROUP, INIT_CUSTOMERGROUP,
INIT_COMPANY_POS_CUSTOMERGROUP_FK, INIT_COMPANY_POS_CUSTOMERGROUP_FK,
INIT_ENTITY, INIT_ENTITY,
INIT_ENTITYADDRESS,
INIT_ENTITYCONTACT,
INIT_UNIT, INIT_UNIT,
INIT_UNITTEXT, INIT_UNITTEXT,
INIT_OPTIONSLIST, INIT_OPTIONSLIST,

View File

@@ -1,6 +1,6 @@
export const CREATE = ` export const CREATE = `
INSERT INTO customergroup (name, company_id) INSERT INTO customergroup (name, company_id)
OUTPUT INSERTED.id OUTPUT INSERTED.id, INSERTED.name
VALUES (@name, @companyId) VALUES (@name, @companyId)
`; `;
@@ -17,7 +17,32 @@ WHERE id = @id
`; `;
export const GET_BY_COMPANY_AND_NAME = ` export const GET_BY_COMPANY_AND_NAME = `
SELECT id SELECT id, name
FROM customergroup FROM customergroup
WHERE company_id = @companyId AND name = @name WHERE company_id = @companyId AND name = @name
`; `;
export const LIST_BY_COMPANY = `
SELECT id, name
FROM customergroup
WHERE company_id = @companyId
ORDER BY name, id
`;
export const GET_BY_COMPANY_AND_ID = `
SELECT id, name
FROM customergroup
WHERE id = @id AND company_id = @companyId
`;
export const UPDATE_NAME = `
UPDATE customergroup
SET name = @name
OUTPUT INSERTED.id, INSERTED.name
WHERE id = @id AND company_id = @companyId
`;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE FROM customergroup
WHERE id = @id AND company_id = @companyId
`;

View File

@@ -1,5 +1,42 @@
export const SUPPLIER_GROUP_NAME = 'Lieferanten'; export const SUPPLIER_GROUP_NAME = 'Lieferanten';
export const ENTITY_COLUMNS = `
e.id,
e.customergroup_id AS customergroupId,
e.first_name AS firstName,
e.name,
e.created_at AS createdAt,
e.updated_at AS updatedAt
`;
export const LIST_BY_COMPANY = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
ORDER BY e.name, e.id
`;
export const LIST_BY_COMPANY_AND_GROUP = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND e.customergroup_id = @customergroupId
ORDER BY e.name, e.id
`;
export const GET_BY_COMPANY_AND_ID = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
`;
export const LIST_SUPPLIERS_BY_COMPANY = ` export const LIST_SUPPLIERS_BY_COMPANY = `
SELECT SELECT
e.id, e.id,
@@ -27,9 +64,31 @@ WHERE e.id = @id
`; `;
export const CREATE = ` export const CREATE = `
INSERT INTO entity (customergroup_id, name) INSERT INTO entity (customergroup_id, first_name, name)
OUTPUT INSERTED.id, INSERTED.name, INSERTED.created_at AS createdAt, INSERTED.updated_at AS updatedAt OUTPUT
VALUES (@customergroupId, @name) INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
VALUES (@customergroupId, @firstName, @name)
`;
export const UPDATE = `
UPDATE entity
SET
first_name = @firstName,
name = @name,
updated_at = SYSUTCDATETIME()
OUTPUT
INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
WHERE id = @id
`; `;
export const UPDATE_NAME = ` export const UPDATE_NAME = `
@@ -40,6 +99,14 @@ WHERE id = @id
AND customergroup_id = @customergroupId AND customergroup_id = @customergroupId
`; `;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE e
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
`;
export const DELETE_BY_ID = ` export const DELETE_BY_ID = `
DELETE e DELETE e
FROM entity e FROM entity e

93
src/sql/entityaddress.js Normal file
View File

@@ -0,0 +1,93 @@
export const ADDRESS_COLUMNS = `
id,
entity_id AS entityId,
first_name AS firstName,
name,
street,
streetnumber AS streetNumber,
zip,
city,
county,
addresstype AS addressType
`;
export const LIST_BY_ENTITY = `
SELECT
${ADDRESS_COLUMNS}
FROM entityaddress
WHERE entity_id = @entityId
ORDER BY id
`;
export const GET_BY_ENTITY_AND_ID = `
SELECT
${ADDRESS_COLUMNS}
FROM entityaddress
WHERE id = @id AND entity_id = @entityId
`;
export const CREATE = `
INSERT INTO entityaddress (
entity_id,
first_name,
name,
street,
streetnumber,
zip,
city,
county,
addresstype
)
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.street,
INSERTED.streetnumber AS streetNumber,
INSERTED.zip,
INSERTED.city,
INSERTED.county,
INSERTED.addresstype AS addressType
VALUES (
@entityId,
@firstName,
@name,
@street,
@streetNumber,
@zip,
@city,
@county,
@addressType
)
`;
export const UPDATE = `
UPDATE entityaddress
SET
first_name = @firstName,
name = @name,
street = @street,
streetnumber = @streetNumber,
zip = @zip,
city = @city,
county = @county,
addresstype = @addressType
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.street,
INSERTED.streetnumber AS streetNumber,
INSERTED.zip,
INSERTED.city,
INSERTED.county,
INSERTED.addresstype AS addressType
WHERE id = @id AND entity_id = @entityId
`;
export const DELETE_BY_ENTITY_AND_ID = `
DELETE FROM entityaddress
WHERE id = @id AND entity_id = @entityId
`;

47
src/sql/entitycontact.js Normal file
View File

@@ -0,0 +1,47 @@
export const CONTACT_COLUMNS = `
id,
entity_id AS entityId,
contacttype AS contactType,
contact
`;
export const LIST_BY_ENTITY = `
SELECT
${CONTACT_COLUMNS}
FROM entitycontact
WHERE entity_id = @entityId
ORDER BY id
`;
export const GET_BY_ENTITY_AND_ID = `
SELECT
${CONTACT_COLUMNS}
FROM entitycontact
WHERE id = @id AND entity_id = @entityId
`;
export const CREATE = `
INSERT INTO entitycontact (entity_id, contacttype, contact)
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.contacttype AS contactType,
INSERTED.contact
VALUES (@entityId, @contactType, @contact)
`;
export const UPDATE = `
UPDATE entitycontact
SET contacttype = @contactType, contact = @contact
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.contacttype AS contactType,
INSERTED.contact
WHERE id = @id AND entity_id = @entityId
`;
export const DELETE_BY_ENTITY_AND_ID = `
DELETE FROM entitycontact
WHERE id = @id AND entity_id = @entityId
`;

View File

@@ -20,6 +20,8 @@ export const TABLES = [
'articleean', 'articleean',
'articlehistory', 'articlehistory',
'entityhistory', 'entityhistory',
'entityaddress',
'entitycontact',
'article', 'article',
'brand', 'brand',
'articlelist', 'articlelist',

View File

@@ -0,0 +1,24 @@
import { CREATE } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
export default function create(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, { name } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
}
await access(googleAuthId, companyId);
const result = await pool
.request()
.input('companyId', companyId)
.input('name', cleaned)
.query(CREATE);
const row = result.recordset[0];
return { id: row.id, name: row.name };
};
}

View File

@@ -0,0 +1,31 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
export default function del(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');
}
await access(googleAuthId, companyId);
try {
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.query(DELETE_BY_COMPANY_AND_ID);
if (!result.rowsAffected[0]) {
throw new Error('Customer group not found');
}
} catch (err) {
if (err.number === 547) {
throw new Error('Customer group in use');
}
throw err;
}
};
}

View File

@@ -0,0 +1,20 @@
import { LIST_BY_COMPANY } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
export default function list(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
const result = await pool
.request()
.input('companyId', companyId)
.query(LIST_BY_COMPANY);
return result.recordset.map((row) => ({
id: row.id,
name: row.name,
}));
};
}

View File

@@ -0,0 +1,22 @@
import { GET_BY_COMPANY_AND_ID } from '../../sql/customergroup.js';
import request from '../request.js';
/** Ensure customergroup belongs to company. Throws Customer group not found. */
export default function resolveCustomerGroupAccess(pool) {
return async (companyId, customergroupId, tx) => {
if (!Number.isFinite(customergroupId)) {
throw new Error('Customer group not found');
}
const result = await request(pool, tx)
.input('id', customergroupId)
.input('companyId', companyId)
.query(GET_BY_COMPANY_AND_ID);
const row = result.recordset[0];
if (!row) {
throw new Error('Customer group not found');
}
return row;
};
}

View File

@@ -0,0 +1,32 @@
import { UPDATE_NAME } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
export default function update(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, id, { name } = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');
}
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
}
await access(googleAuthId, companyId);
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.input('name', cleaned)
.query(UPDATE_NAME);
const row = result.recordset[0];
if (!row) {
throw new Error('Customer group not found');
}
return { id: row.id, name: row.name };
};
}

View File

@@ -0,0 +1,69 @@
import { CREATE } from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
return async (
googleAuthId,
companyId,
{ customergroupId, name, firstName = null } = {},
) => {
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
}
const first =
firstName == null || firstName === ''
? null
: String(firstName).trim() || null;
const { companyuserId } = await access(googleAuthId, companyId);
await groupAccess(companyId, customergroupId);
const tx = pool.transaction();
await tx.begin();
try {
const created = await request(pool, tx)
.input('customergroupId', customergroupId)
.input('firstName', first)
.input('name', cleaned)
.query(CREATE);
const row = created.recordset[0];
await request(pool, tx)
.input('entityId', row.id)
.input('companyuserId', companyuserId)
.input('text', 'Erstellt')
.query(INSERT_HISTORY);
await tx.commit();
return shape(row);
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
}
};
}

View File

@@ -0,0 +1,24 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
export default function del(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
await access(googleAuthId, companyId);
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.query(DELETE_BY_COMPANY_AND_ID);
if (!result.rowsAffected[0]) {
throw new Error('Entity not found');
}
};
}

View File

@@ -0,0 +1,37 @@
import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
export default function getById(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
await access(googleAuthId, companyId);
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.query(GET_BY_COMPANY_AND_ID);
const row = result.recordset[0];
if (!row) {
throw new Error('Entity not found');
}
return shape(row);
};
}

43
src/store/entity/list.js Normal file
View File

@@ -0,0 +1,43 @@
import {
LIST_BY_COMPANY,
LIST_BY_COMPANY_AND_GROUP,
} from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
return async (googleAuthId, companyId, { customergroupId } = {}) => {
await access(googleAuthId, companyId);
if (customergroupId != null) {
await groupAccess(companyId, customergroupId);
const result = await pool
.request()
.input('companyId', companyId)
.input('customergroupId', customergroupId)
.query(LIST_BY_COMPANY_AND_GROUP);
return result.recordset.map(shape);
}
const result = await pool
.request()
.input('companyId', companyId)
.query(LIST_BY_COMPANY);
return result.recordset.map(shape);
};
}

View File

@@ -0,0 +1,22 @@
import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import request from '../request.js';
/** Ensure entity belongs to company. Throws Entity not found. */
export default function resolveEntityAccess(pool) {
return async (companyId, entityId, tx) => {
if (!Number.isFinite(entityId)) {
throw new Error('Entity not found');
}
const result = await request(pool, tx)
.input('id', entityId)
.input('companyId', companyId)
.query(GET_BY_COMPANY_AND_ID);
const row = result.recordset[0];
if (!row) {
throw new Error('Entity not found');
}
return row;
};
}

110
src/store/entity/update.js Normal file
View File

@@ -0,0 +1,110 @@
import { GET_BY_COMPANY_AND_ID, UPDATE } from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
export default function update(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId, companyId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
const hasName = Object.prototype.hasOwnProperty.call(patch, 'name');
const hasFirstName = Object.prototype.hasOwnProperty.call(patch, 'firstName');
if (!hasName && !hasFirstName) {
throw new Error('Name required');
}
const { companyuserId } = await access(googleAuthId, companyId);
const existing = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.query(GET_BY_COMPANY_AND_ID);
const current = existing.recordset[0];
if (!current) {
throw new Error('Entity not found');
}
let name = current.name;
if (hasName) {
name = patch.name?.trim();
if (!name) {
throw new Error('Name required');
}
}
let firstName = current.firstName ?? null;
if (hasFirstName) {
firstName =
patch.firstName == null || patch.firstName === ''
? null
: String(patch.firstName).trim() || null;
}
const historyLines = [];
const oldFirst = current.firstName ?? '';
const newFirst = firstName ?? '';
if (oldFirst !== newFirst) {
historyLines.push(
`Vorname geändert von "${oldFirst}" auf "${newFirst}"`,
);
}
if (current.name !== name) {
historyLines.push(
`Name geändert von "${current.name}" auf "${name}"`,
);
}
if (!historyLines.length) {
return shape(current);
}
const tx = pool.transaction();
await tx.begin();
try {
const updated = await request(pool, tx)
.input('id', id)
.input('firstName', firstName)
.input('name', name)
.query(UPDATE);
const row = updated.recordset[0];
if (!row) {
throw new Error('Entity not found');
}
await request(pool, tx)
.input('entityId', id)
.input('companyuserId', companyuserId)
.input('text', historyLines.join('\n'))
.query(INSERT_HISTORY);
await tx.commit();
return shape(row);
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
}
};
}

View File

@@ -0,0 +1,56 @@
import { CREATE } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
firstName: row.firstName ?? null,
name: row.name ?? null,
street: row.street ?? null,
streetNumber: row.streetNumber ?? null,
zip: row.zip ?? null,
city: row.city ?? null,
county: row.county ?? null,
addressType: row.addressType,
};
}
function optionalString(value) {
if (value == null || value === '') return null;
const s = String(value).trim();
return s || null;
}
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, input = {}) => {
const addressType = input.addressType?.trim?.()
? input.addressType.trim()
: null;
if (!addressType) {
throw new Error('Address type required');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('entityId', entityId)
.input('firstName', optionalString(input.firstName))
.input('name', optionalString(input.name))
.input('street', optionalString(input.street))
.input('streetNumber', optionalString(input.streetNumber))
.input('zip', optionalString(input.zip))
.input('city', optionalString(input.city))
.input('county', optionalString(input.county))
.input('addressType', addressType)
.query(CREATE);
return shape(result.recordset[0]);
};
}

View File

@@ -0,0 +1,27 @@
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.query(DELETE_BY_ENTITY_AND_ID);
if (!result.rowsAffected[0]) {
throw new Error('Address not found');
}
};
}

View File

@@ -0,0 +1,35 @@
import { LIST_BY_ENTITY } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
firstName: row.firstName ?? null,
name: row.name ?? null,
street: row.street ?? null,
streetNumber: row.streetNumber ?? null,
zip: row.zip ?? null,
city: row.city ?? null,
county: row.county ?? null,
addressType: row.addressType,
};
}
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId) => {
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('entityId', entityId)
.query(LIST_BY_ENTITY);
return result.recordset.map(shape);
};
}

View File

@@ -0,0 +1,104 @@
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
firstName: row.firstName ?? null,
name: row.name ?? null,
street: row.street ?? null,
streetNumber: row.streetNumber ?? null,
zip: row.zip ?? null,
city: row.city ?? null,
county: row.county ?? null,
addressType: row.addressType,
};
}
function optionalString(value) {
if (value == null || value === '') return null;
const s = String(value).trim();
return s || null;
}
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const existing = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.query(GET_BY_ENTITY_AND_ID);
const current = existing.recordset[0];
if (!current) {
throw new Error('Address not found');
}
const firstName = Object.prototype.hasOwnProperty.call(patch, 'firstName')
? optionalString(patch.firstName)
: current.firstName ?? null;
const name = Object.prototype.hasOwnProperty.call(patch, 'name')
? optionalString(patch.name)
: current.name ?? null;
const street = Object.prototype.hasOwnProperty.call(patch, 'street')
? optionalString(patch.street)
: current.street ?? null;
const streetNumber = Object.prototype.hasOwnProperty.call(
patch,
'streetNumber',
)
? optionalString(patch.streetNumber)
: current.streetNumber ?? null;
const zip = Object.prototype.hasOwnProperty.call(patch, 'zip')
? optionalString(patch.zip)
: current.zip ?? null;
const city = Object.prototype.hasOwnProperty.call(patch, 'city')
? optionalString(patch.city)
: current.city ?? null;
const county = Object.prototype.hasOwnProperty.call(patch, 'county')
? optionalString(patch.county)
: current.county ?? null;
let addressType = current.addressType;
if (Object.prototype.hasOwnProperty.call(patch, 'addressType')) {
addressType = patch.addressType?.trim?.()
? patch.addressType.trim()
: null;
if (!addressType) {
throw new Error('Address type required');
}
}
const result = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.input('firstName', firstName)
.input('name', name)
.input('street', street)
.input('streetNumber', streetNumber)
.input('zip', zip)
.input('city', city)
.input('county', county)
.input('addressType', addressType)
.query(UPDATE);
const row = result.recordset[0];
if (!row) {
throw new Error('Address not found');
}
return shape(row);
};
}

View File

@@ -0,0 +1,39 @@
import { CREATE } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
contactType: row.contactType,
contact: row.contact,
};
}
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, input = {}) => {
const contactType = input.contactType?.trim?.()
? input.contactType.trim()
: null;
const contact = input.contact?.trim?.() ? input.contact.trim() : null;
if (!contactType || !contact) {
throw new Error('Contact required');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('entityId', entityId)
.input('contactType', contactType)
.input('contact', contact)
.query(CREATE);
return shape(result.recordset[0]);
};
}

View File

@@ -0,0 +1,27 @@
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.query(DELETE_BY_ENTITY_AND_ID);
if (!result.rowsAffected[0]) {
throw new Error('Contact not found');
}
};
}

View File

@@ -0,0 +1,29 @@
import { LIST_BY_ENTITY } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
contactType: row.contactType,
contact: row.contact,
};
}
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId) => {
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const result = await pool
.request()
.input('entityId', entityId)
.query(LIST_BY_ENTITY);
return result.recordset.map(shape);
};
}

View File

@@ -0,0 +1,67 @@
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
return {
id: row.id,
entityId: row.entityId,
contactType: row.contactType,
contact: row.contact,
};
}
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');
}
await access(googleAuthId, companyId);
await entityAccess(companyId, entityId);
const existing = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.query(GET_BY_ENTITY_AND_ID);
const current = existing.recordset[0];
if (!current) {
throw new Error('Contact not found');
}
let contactType = current.contactType;
if (Object.prototype.hasOwnProperty.call(patch, 'contactType')) {
contactType = patch.contactType?.trim?.()
? patch.contactType.trim()
: null;
}
let contact = current.contact;
if (Object.prototype.hasOwnProperty.call(patch, 'contact')) {
contact = patch.contact?.trim?.() ? patch.contact.trim() : null;
}
if (!contactType || !contact) {
throw new Error('Contact required');
}
const result = await pool
.request()
.input('id', id)
.input('entityId', entityId)
.input('contactType', contactType)
.input('contact', contact)
.query(UPDATE);
const row = result.recordset[0];
if (!row) {
throw new Error('Contact not found');
}
return shape(row);
};
}

View File

@@ -23,6 +23,7 @@ export default function create(pool) {
const created = await request(pool, tx) const created = await request(pool, tx)
.input('customergroupId', customergroupId) .input('customergroupId', customergroupId)
.input('firstName', null)
.input('name', cleaned) .input('name', cleaned)
.query(CREATE); .query(CREATE);